From 27d4dda2a20036d748e472ceaf1fdf5eb5b0d3f3 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 17 Dec 2023 23:59:16 -0700 Subject: [PATCH] basic chatbox functionality --- lua/cbox/cl_chatbox.lua | 180 ++++++++++++++++++++++++++++++++++++++++ lua/cbox/cl_hooks.lua | 7 +- lua/cbox/sh_init.lua | 9 +- lua/cbox/sh_utils.lua | 8 +- lua/cbox/tabs/chat.lua | 83 ++++++++++++++++++ 5 files changed, 280 insertions(+), 7 deletions(-) create mode 100644 lua/cbox/cl_chatbox.lua create mode 100644 lua/cbox/tabs/chat.lua diff --git a/lua/cbox/cl_chatbox.lua b/lua/cbox/cl_chatbox.lua new file mode 100644 index 0000000..0a05c1c --- /dev/null +++ b/lua/cbox/cl_chatbox.lua @@ -0,0 +1,180 @@ +local DisableClipping = DisableClipping +local ScrW = ScrW +local ScrH = ScrH + +local render = render +local string = string +local surface = surface + +local render_UpdateScreenEffectTexture = render.UpdateScreenEffectTexture +local surface_DrawRect = surface.DrawRect +local surface_DrawTexturedRect = surface.DrawTexturedRect +local surface_SetDrawColor = surface.SetDrawColor +local surface_SetMaterial = surface.SetMaterial + +cbox.chatbox = cbox.chatbox or {} +cbox.chatbox.tabs = cbox.chatbox.tabs or {} +cbox.chatbox.panels = cbox.chatbox.panels or {} + + +local CHATBOX_COLOR = CreateClientConVar("cbox_chatbox_color", "160 160 160", true, false, "Chatbox background color") +local CHATBOX_ALPHA = CreateClientConVar("cbox_chatbox_alpha", "128", true, false, "Chatbox background alpha") +local CHATBOX_BLUR = CreateClientConVar("cbox_chatbox_blur", "0", true, false, "Chatbox background is blurred") + +---@param height number +---@return number +local function _ScreenScaleH(height) + return height * (ScrH() / 480) +end +local ScreenScaleH = ScreenScaleH or _ScreenScaleH + +---@return number x +---@return number y +---@return number w +---@return number h +local function GetDefaultBounds() + return ScreenScaleH(10), ScreenScaleH(275), ScreenScaleH(320), ScreenScaleH(120) +end + +---Add a new chatbox tab +---@param key string Internal unique name +---@param name string Display name +---@param icon string Path to icon +---@param callback fun(): Panel What to do when the tab is created +function cbox.chatbox.AddTab(key, name, icon, callback) + cbox.chatbox.tabs[key] = { + name = name, + icon = icon, + callback = callback, + } +end + +local MATERIAL_BLUR = Material("pp/blurscreen") + +local oldSkin = SKIN +SKIN = derma.GetDefaultSkin() +local CLOSE_BUTTON = GWEN.CreateTextureNormal(32, 452, 29, 17) +local CLOSE_BUTTON_HOVER = GWEN.CreateTextureNormal(64, 452, 29, 17) +local CLOSE_BUTTON_DOWN = GWEN.CreateTextureNormal(96, 452, 29, 17) +SKIN = oldSkin + +local COLOR_DISABLED = Color(255, 255, 255, 50) + +local function CreateChatbox() + if IsValid(cbox.chatbox.panels.frame) then + cbox.chatbox.panels.frame:Remove() + end + + local frame = vgui.Create("DFrame", nil, "cbox.chatbox") + frame:SetCookieName("cbox_chatbox") + frame:SetDeleteOnClose(false) + frame:SetSizable(true) + frame:SetScreenLock(true) + frame:DockPadding(4, 4, 4, 4) + + frame.btnMinim:SetVisible(false) + frame.btnMaxim:SetVisible(false) + frame.lblTitle:SetVisible(false) + + function frame:Paint(w, h) + if CHATBOX_BLUR:GetBool() then + local x, y = self:LocalToScreen(0, 0) + + surface_SetMaterial(MATERIAL_BLUR) + surface_SetDrawColor(255, 255, 255, 255) + + for i = 0.33, 1, 0.33 do + MATERIAL_BLUR:SetFloat("$blur", 5 * i) + MATERIAL_BLUR:Recompute() + render_UpdateScreenEffectTexture() + surface_DrawTexturedRect(x * -1, y * -1, ScrW(), ScrH()) + end + end + + local color = string.Explode(" ", CHATBOX_COLOR:GetString()) + surface_SetDrawColor(tonumber(color[1]), tonumber(color[2]), tonumber(color[3]), CHATBOX_ALPHA:GetInt()) + surface_DrawRect(0, 0, w, h) + end + + local dx, dy, dw, dh = GetDefaultBounds() + + -- TODO: make this configurable + frame:SetMinWidth(dw) + frame:SetMinWidth(dh) + + -- TODO: save resizing/moving + frame:SetPos(dx, dy) + frame:SetSize(dw, dh) + + frame:SetVisible(false) + + cbox.chatbox.panels.frame = frame + + local tabs = vgui.Create("DPropertySheet", frame, "cbox.chatbox.tabs") + tabs:Dock(FILL) + tabs:SetPadding(0) + + function tabs:Paint(w, h) + surface_SetDrawColor(0, 0, 0, 72) + surface_DrawRect(0, 20, w, h - 20) + end + + for id, tab in next, cbox.chatbox.tabs do + local function catch(err) + cbox.utils.RealmError(("Failed to create tab %q:"):format(id), err) + end + + local ok, ret = xpcall(tab.callback, catch) + if not ok then continue end + if not ispanel(ret) then + cbox.utils.RealmError(("Got non-panel for tab %q!"):format(id)) + continue + end + + tabs:AddSheet(tab.name, ret, tab.icon) + end + + frame.btnClose:SetZPos(99) +end + +if not IsValid(cbox.chatbox.panels.frame) then + CreateChatbox() +end + +---Opens the chatbox +---@param alt? boolean Was this request to open made by messagemode2 (team chat) +function cbox.chatbox.Open(alt) + alt = alt ~= nil and alt or false + + if not IsValid(cbox.chatbox.panels.frame) then + CreateChatbox() + end + + cbox.chatbox.panels.frame:SetVisible(true) + cbox.chatbox.panels.frame:MakePopup() + cbox.chatbox.panels.input:RequestFocus() + + hook.Run("StartChat") +end + +---Closes the chatbox +function cbox.chatbox.Close() + cbox.chatbox.panels.frame:Close() + + hook.Run("FinishChat") + + cbox.chatbox.panels.input:SetText("") + hook.Run("ChatTextChanged", "") +end + +hook.Add("PlayerBindPress", "cbox.chatbox", function(ply, bind, pressed) + if bind ~= "messagemode" and bind ~= "messagemode2" then return end + + cbox.chatbox.Open(bind == "messagemode2") + + return true +end) + +concommand.Add("cbox_chatbox_reload", function() + CreateChatbox() +end, nil, "Reloads the chatbox") diff --git a/lua/cbox/cl_hooks.lua b/lua/cbox/cl_hooks.lua index 20e9495..caca9c0 100644 --- a/lua/cbox/cl_hooks.lua +++ b/lua/cbox/cl_hooks.lua @@ -11,8 +11,11 @@ function cbox.hooks.GetTable() return hookTable end +---@alias ValidHooks +---| '"PreChatAddText"' # Runs before the true chat.AddText is called, allows modification to arguments + ---Adds a hook ----@param name "PreChatAddText" +---@param name ValidHooks ---@param identifier string ---@param callback function function cbox.hooks.Add(name, identifier, callback) @@ -21,7 +24,7 @@ function cbox.hooks.Add(name, identifier, callback) end ---Removes a hook ----@param name "PreChatAddText" +---@param name ValidHooks ---@param identifier string function cbox.hooks.Remove(name, identifier) if not hookTable[name] then return end diff --git a/lua/cbox/sh_init.lua b/lua/cbox/sh_init.lua index 5917927..e4bbd48 100644 --- a/lua/cbox/sh_init.lua +++ b/lua/cbox/sh_init.lua @@ -6,8 +6,15 @@ include("cbox/sh_utils.lua") if SERVER then AddCSLuaFile("cbox/cl_hooks.lua") + AddCSLuaFile("cbox/cl_chatbox.lua") + + local tab_files = file.Find("cbox/tabs/*", "LUA") + for _, name in ipairs(tab_files) do + AddCSLuaFile("cbox/tabs/" .. name) + end elseif CLIENT then include("cbox/cl_hooks.lua") + include("cbox/cl_chatbox.lua") end local module_files = file.Find("cbox/modules/*", "LUA") @@ -21,7 +28,7 @@ for _, name in ipairs(module_files) do include(full_path) end elseif name:StartsWith("sv_") and SERVER then - cbox.utils.RealmPrint("Loading module:", name) + cbox.utils.RealmPrint("Loading module:", name) include(full_path) else if SERVER then diff --git a/lua/cbox/sh_utils.lua b/lua/cbox/sh_utils.lua index dc0a62e..c2dd6a3 100644 --- a/lua/cbox/sh_utils.lua +++ b/lua/cbox/sh_utils.lua @@ -40,7 +40,7 @@ function utils.ParseColorString(str, alpha) end ---Prints to console with realm prefix ----@vararg any +---@param ... any function utils.RealmPrint(...) local args = {...} for i, arg in ipairs(args) do @@ -65,7 +65,7 @@ function utils.RealmPrint(...) end ---Prints to console with the "shared" prefix ----@vararg any +---@param ... any function utils.SharedPrint(...) local args = {...} for i, arg in ipairs(args) do @@ -82,7 +82,7 @@ function utils.SharedPrint(...) end ---Prints to console with realm prefix, but in red ----@vararg any +---@param ... any function utils.RealmError(...) local args = {...} for i, arg in ipairs(args) do @@ -107,7 +107,7 @@ function utils.RealmError(...) end ---Prints to console with the "shared" prefix, but in red ----@vararg any +---@param ... any function utils.SharedError(...) local args = {...} for i, arg in ipairs(args) do diff --git a/lua/cbox/tabs/chat.lua b/lua/cbox/tabs/chat.lua new file mode 100644 index 0000000..37063de --- /dev/null +++ b/lua/cbox/tabs/chat.lua @@ -0,0 +1,83 @@ +cbox.chatbox.AddTab("\1chat", "Chat", "icon16/comments.png", function() + local wrapper = vgui.Create("EditablePanel") + + -- TODO: custom richtext panel + local history = vgui.Create("RichText", wrapper) + history:Dock(FILL) + cbox.chatbox.panels.history = history + + function history:PerformLayout() + -- TODO: configurable font + history:SetFontInternal("ChatFont") + end + + local input_wrapper = vgui.Create("EditablePanel", wrapper) + input_wrapper:SetHeight(20) + input_wrapper:Dock(BOTTOM) + + local input = vgui.Create("DTextEntry", input_wrapper) + input:Dock(FILL) + cbox.chatbox.panels.input = input + + local mode_switch = vgui.Create("DButton", input_wrapper) + mode_switch:SetText("Say") + mode_switch:SizeToContents() + mode_switch:Dock(LEFT) + cbox.chatbox.panels.mode_switch = mode_switch + + function mode_switch:DoClick() + -- TODO + end + function mode_switch:DoRightClick() + -- TODO + end + + function input:OnKeyCodeTyped(key) + if key == KEY_ESCAPE then + cbox.chatbox.Close() + gui.HideGameUI() + elseif key == KEY_BACKQUOTE then + gui.HideGameUI() + elseif key == KEY_ENTER then + local text = self:GetText():Trim() + if text ~= "" then + RunConsoleCommand("say", text) + end + + cbox.chatbox.Close() + elseif key == KEY_TAB then + if #self:GetText() == 0 then + -- TODO: mode switch + else + -- TODO: autocomplete + end + else + hook.Run("ChatTextChanged", self:GetText()) + end + end + + return wrapper +end) + +hook.Add("OnChatAddText", "cbox.chatbox.history", function(args) + local history = cbox.chatbox.panels.history + for _, arg in ipairs(args) do + if IsColor(arg) then + history:InsertColorChange(arg.r, arg.g, arg.b, 255) + elseif isstring(arg) then + history:AppendText(arg) + elseif isentity(arg) then + if IsValid(arg) and arg:IsPlayer() then + local col = hook.Run("GetTeamColor", arg) + history:InsertColorChange(col.r, col.g, col.b, 255) + history:AppendText(arg:Name()) + else + local col = cbox.utils.colors.BRAND + history:InsertColorChange(col.r, col.g, col.b, 255) + history:AppendText("???") + end + end + end + + history:AppendText("\n") +end)