diff --git a/lua/cbox/cl_chatbox.lua b/lua/cbox/cl_chatbox.lua index 50901df..379377c 100644 --- a/lua/cbox/cl_chatbox.lua +++ b/lua/cbox/cl_chatbox.lua @@ -33,6 +33,7 @@ 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 {} cbox.chatbox.modes = cbox.chatbox.modes or {} @@ -43,9 +44,6 @@ local CHATBOX_FADE = CreateClientConVar("cbox_chatbox_fade", "1", true, false, " local CHATBOX_MODE1 = CreateClientConVar("cbox_chatbox_mode1", "say", true, false, "Default mode when pressing messagemode") local CHATBOX_MODE2 = CreateClientConVar("cbox_chatbox_mode2", "say_team", true, false, "Default mode when pressing messagemode2") -local INPUT_TEXT_COLOR = Color(221, 221, 221) -local INPUT_HIGHLIGHT_COLOR = Color(192, 28, 0, 140) - ---@param height number ---@return number local function _ScreenScaleH(height) @@ -61,6 +59,19 @@ 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 + ---Add a new chatbox send mode ---@param key string Internal unique name ---@param name string Display name @@ -137,12 +148,13 @@ local function CreateChatbox() frame:SetDeleteOnClose(false) frame:SetSizable(true) frame:SetScreenLock(true) - frame:DockPadding(16, 20, 16, 12) + frame:DockPadding(8, 8, 8, 8) local dx, dy, dw, dh = GetDefaultBounds() - frame:SetMinWidth(math.min(256, dw)) - frame:SetMinHeight(math.min(128, dh)) + -- TODO: make this configurable + frame:SetMinWidth(dw) + frame:SetMinHeight(dh) local x = frame:GetCookie("pos_x", dx) local y = frame:GetCookie("pos_y", dy) @@ -201,53 +213,6 @@ local function CreateChatbox() end end - function frame:OnMousePressed(button) - local screenX, screenY = self:LocalToScreen( 0, 0 ) - - if self.m_bSizable and gui.MouseX() > (screenX + self:GetWide() - 20) and gui.MouseY() > (screenY + self:GetTall() - 20) then - self.Sizing = {gui.MouseX() - self:GetWide(), gui.MouseY() - self:GetTall()} - self:MouseCapture(true) - return - end - - if self:GetDraggable() and gui.MouseY() < (screenY + 16) then - if button == MOUSE_LEFT then - self.Dragging = {gui.MouseX() - self.x, gui.MouseY() - self.y} - self:MouseCapture(true) - elseif button == MOUSE_RIGHT then - local menu = DermaMenu() - - local settings = menu:AddOption("Settings", function() end) - settings:SetIcon("icon16/cog.png") - - local metrics = menu:AddOption("Reset Metrics", function() - RunConsoleCommand("cbox_chatbox_reset_metrics") - if IsValid(cbox.chatbox.panels.input) then - cbox.chatbox.panels.input:RequestFocus() - end - end) - metrics:SetIcon("icon16/arrow_out.png") - - menu:AddSpacer() - - local reload = menu:AddOption("Reload Chatbox", function() - RunConsoleCommand((input.IsKeyDown(KEY_LSHIFT) or input.IsKeyDown(KEY_RSHIFT)) and "_cbox_chatbox_fullreload" or "cbox_chatbox_reload") - end) - reload:SetIcon("icon16/arrow_refresh.png") - - menu:AddSpacer() - - local close = menu:AddOption("Close", function() - cbox.chatbox.Close() - end) - close:SetIcon("icon16/cross.png") - - menu:Open() - end - return - end - end - function frame:Paint(w, h) local alpha = CHATBOX_ALPHA:GetInt() @@ -314,127 +279,61 @@ local function CreateChatbox() cbox.chatbox.panels.frame = frame - local wrapper = vgui.Create("EditablePanel", frame) - wrapper:Dock(FILL) + local tabs = vgui.Create("DPropertySheet", frame, "cbox.chatbox.tabs") + tabs:Dock(FILL) + tabs:SetPadding(0) - -- TODO: custom richtext panel - local history = vgui.Create("RichText", wrapper) - history:Dock(FILL) - function history:Paint(w, h) - surface_SetDrawColor(0, 0, 0, 128) - surface_DrawRect(0, 0, w, h) + function tabs:Paint(w, h) end + + function tabs:OnActiveTabChanged( old, new ) + if new:GetPanel().cbox_id == "\1chat" then + cbox.chatbox.panels.input:RequestFocus() + end end - cbox.chatbox.panels.history = history - function history:PerformLayout() + cbox.chatbox.panels.tabs = tabs + + 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 + + local sheet = tabs:AddSheet(tab.name, ret, tab.icon) + sheet.Panel.cbox_id = id + + function sheet.Tab:ApplySchemeSettings() + local ExtraInset = 8 + + if self.Image then + ExtraInset = ExtraInset + self.Image:GetWide() + end + + self:SetTextInset(ExtraInset, 2) + local w, h = self:GetContentSize() + h = self:GetTabHeight() + + self:SetSize(w + 8, h) + + DLabel.ApplySchemeSettings( self ) + end -- TODO: configurable font - history:SetFontInternal("ChatFont") - end + sheet.Tab:SetFont("ChatFont") - local input_wrapper = vgui.Create("EditablePanel", wrapper) - input_wrapper:SetHeight(20) - input_wrapper:DockMargin(0, 8, 0, 0) - input_wrapper:Dock(BOTTOM) - - local input = vgui.Create("DTextEntry", input_wrapper) - input:DockMargin(4, 0, 0, 0) - input:Dock(FILL) - input:SetFont("ChatFont") - cbox.chatbox.panels.input = input - - function input:Paint(w, h) - surface_SetDrawColor(0, 0, 0, 128) - surface_DrawRect(0, 0, w, h) - - self:DrawTextEntryText(INPUT_TEXT_COLOR, INPUT_HIGHLIGHT_COLOR, INPUT_TEXT_COLOR) - end - - local mode_switch = vgui.Create("DButton", input_wrapper) - mode_switch:SetTextColor(INPUT_TEXT_COLOR) - mode_switch:Dock(LEFT) - cbox.chatbox.panels.mode_switch = mode_switch - - function mode_switch:UpdateMode(mode) - local mode_data = cbox.chatbox.modes[mode] - if not mode_data then - self.current_mode = "say" - mode_data = cbox.chatbox.modes.say - end - - self.current_mode = mode - self:SetText(mode_data.name .. ":") - end - - function mode_switch:Paint(w, h) - surface_SetDrawColor(0, 0, 0, 128) - surface_DrawRect(0, 0, w, h) - end - - function mode_switch:ApplySchemeSettings() - local ExtraInset = 8 - - self:SetTextInset(ExtraInset, 0) - local w, h = self:GetContentSize() - - self:SetSize(w + 8, 20) - - DLabel.ApplySchemeSettings(self) - end - mode_switch:SetFont("ChatFont") - mode_switch:UpdateMode("say") - - function mode_switch:NextMode() - local keys = table.GetKeys(cbox.chatbox.modes) - table.sort(keys) - - local _, next_mode = next(keys, table.KeyFromValue(keys, self.current_mode)) - if not next_mode then next_mode = keys[1] end - - self:UpdateMode(next_mode) - end - - function mode_switch:DoClick() - self:NextMode() - end - function mode_switch:DoRightClick() - local menu = DermaMenu() - - for mode, data in next, cbox.chatbox.modes do - menu:AddOption(data.name, function() - self:UpdateMode(mode) - end) - end - - menu:Open() - end - - function input:OnKeyCodeTyped(key) - --[[if key == KEY_ESCAPE then - cbox.chatbox.Close() - gui.HideGameUI() - else--]] - if key == KEY_BACKQUOTE then - gui.HideGameUI() - elseif key == KEY_ENTER then - local text = self:GetText():Trim() - if text ~= "" then - cbox.chatbox.modes[mode_switch.current_mode].callback(text) - end - - cbox.chatbox.Close() - elseif key == KEY_TAB then - if #self:GetText() == 0 then - mode_switch:NextMode() + function sheet.Tab:Paint(w, h) + if self:IsActive() then + surface_SetDrawColor(0, 0, 0, 128) + surface_DrawRect(0, 0, w, 20) else - local tab_text = hook.Run("OnChatTab", self:GetText()) - self:SetText(tab_text) + surface_SetDrawColor(0, 0, 0, 64) + surface_DrawRect(0, 2, w, 18) end - timer.Simple(0, function() - self:RequestFocus() - self:SetCaretPos(self:GetText():len()) - end) - else - hook.Run("ChatTextChanged", self:GetText()) end end end @@ -442,6 +341,12 @@ end local function Init() include("cbox/cl_modes.lua") + local tab_files = file.Find("cbox/tabs/*", "LUA") + for _, name in ipairs(tab_files) do + cbox.utils.RealmPrint("Loading chatbox tab:", name) + include("cbox/tabs/" .. name) + end + if not IsValid(cbox.chatbox.panels.frame) then CreateChatbox() end @@ -454,7 +359,6 @@ local function Init() end if bind ~= "messagemode" and bind ~= "messagemode2" then return end if not pressed then return end - if input.IsKeyDown(KEY_LALT) or input.IsKeyDown(KEY_RALT) then return end cbox.chatbox.Open(bind == "messagemode2") @@ -491,7 +395,7 @@ function cbox.chatbox.Open(alt) frame:MakePopup() end - if not IsValid(cbox.chatbox.panels.input) or not IsValid(cbox.chatbox.panels.history) then + if not IsValid(cbox.chatbox.panels.input) or not IsValid(cbox.chatbox.panels.tabs) then -- attempt to reinit if IsValid(frame) then frame:Remove() @@ -499,8 +403,8 @@ function cbox.chatbox.Open(alt) Init() end - if not IsValid(cbox.chatbox.panels.input) or not IsValid(cbox.chatbox.panels.history) then - cbox.utils.RealmError("Input or history aren't valid, chat failed to load, bailing!") + if not IsValid(cbox.chatbox.panels.input) or not IsValid(cbox.chatbox.panels.tabs) then + cbox.utils.RealmError("Input or tabs aren't valid, chat tab failed to load, bailing!") if IsValid(frame) then frame:Remove() end @@ -508,20 +412,20 @@ function cbox.chatbox.Open(alt) return end + for _, tab in ipairs(cbox.chatbox.panels.tabs:GetItems()) do + if tab.Panel.cbox_id == "\1chat" and cbox.chatbox.panels.tabs:GetActiveTab() ~= tab.Tab then + cbox.chatbox.panels.tabs:SetActiveTab(tab.Tab) + end + end + cbox.chatbox.panels.input:RequestFocus() if IsValid(cbox.chatbox.panels.mode_switch) then - local mode1 = CHATBOX_MODE1:GetString() - local mode2 = CHATBOX_MODE2:GetString() - - if not cbox.chatbox.modes[mode1] then - mode1 = "say" + if alt then + cbox.chatbox.panels.mode_switch:UpdateMode(CHATBOX_MODE2:GetString()) + else + cbox.chatbox.panels.mode_switch:UpdateMode(CHATBOX_MODE1:GetString()) end - if not cbox.chatbox.modes[mode2] then - mode2 = "say_team" - end - - cbox.chatbox.panels.mode_switch:UpdateMode(alt and mode2 or mode1) end hook.Run("StartChat") @@ -553,47 +457,10 @@ end hook.Add("Initialize", "cbox.chatbox", Init) -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 - history:InsertColorChange(160, 160, 160, 255) - history:AppendText("???") - end - end - end - - history:AppendText("\n") -end) - concommand.Add("cbox_chatbox_reload", function() CreateChatbox() end, nil, "Reloads the chatbox") -concommand.Add("cbox_chatbox_reset_metrics", function() - if IsValid(cbox.chatbox.panels.frame) then - local frame = cbox.chatbox.panels.frame - local dx, dy, dw, dh = GetDefaultBounds() - - frame:SetCookie("pos_x", dx) - frame:SetCookie("pos_y", dy) - frame:SetCookie("width", dw) - frame:SetCookie("height", dh) - - frame:SetPos(dx, dy) - frame:SetSize(dw, dh) - end -end, nil, "Resets the metrics (size and position) of the chatbox") - concommand.Add("_cbox_chatbox_fullreload", function() if IsValid(cbox.chatbox.panels.frame) then cbox.chatbox.panels.frame:Remove() diff --git a/lua/cbox/cl_modes.lua b/lua/cbox/cl_modes.lua index 9fc3c2f..fbc417b 100644 --- a/lua/cbox/cl_modes.lua +++ b/lua/cbox/cl_modes.lua @@ -7,13 +7,6 @@ cbox.chatbox.AddMode("say_team", "Team", function(text) end) cbox.chatbox.AddMode("cmd", "Console", function(text) - cbox.utils.RealmPrint(Format("Running command %q", text)) - - if IsConCommandBlocked(text) then - LocalPlayer():ChatPrint("Tried to run blocked command.") - return - end - local function catch(err) LocalPlayer():ChatPrint("Failed to run command: " .. err) end diff --git a/lua/cbox/sh_init.lua b/lua/cbox/sh_init.lua index a2a4a46..9495d9e 100644 --- a/lua/cbox/sh_init.lua +++ b/lua/cbox/sh_init.lua @@ -8,6 +8,11 @@ if SERVER then AddCSLuaFile("cbox/cl_hooks.lua") AddCSLuaFile("cbox/cl_chatbox.lua") AddCSLuaFile("cbox/cl_modes.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") diff --git a/lua/cbox/sh_utils.lua b/lua/cbox/sh_utils.lua index 82a817e..fd33bc6 100644 --- a/lua/cbox/sh_utils.lua +++ b/lua/cbox/sh_utils.lua @@ -14,10 +14,10 @@ local color_black = Color(0, 0, 0) local utils = {} utils.colors = { BRAND = Color(132, 206, 181), - CLIENT = Color(230, 218, 112), - SERVER = Color(143, 218, 230), - SHARED = Color(93, 200, 92 ), - ERROR = Color(255, 90, 90 ), + CLIENT = Color(143, 218, 230), + SERVER = Color(230, 218, 112), + SHARED = Color( 93, 200, 92), + ERROR = Color(255, 90, 90), } ---Parses a string into separate numbers diff --git a/lua/cbox/tabs/chat.lua b/lua/cbox/tabs/chat.lua new file mode 100644 index 0000000..b0be0e5 --- /dev/null +++ b/lua/cbox/tabs/chat.lua @@ -0,0 +1,154 @@ +local Color = Color + +local surface = surface + +local surface_DrawRect = surface.DrawRect +local surface_SetDrawColor = surface.SetDrawColor + +local INPUT_TEXT_COLOR = Color(221, 221, 221) +local INPUT_HIGHLIGHT_COLOR = Color(192, 28, 0, 140) + +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) + function history:Paint(w, h) + surface_SetDrawColor(0, 0, 0, 128) + surface_DrawRect(0, 0, w, h) + end + 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:DockMargin(0, 8, 0, 0) + input_wrapper:Dock(BOTTOM) + + local input = vgui.Create("DTextEntry", input_wrapper) + input:DockMargin(4, 0, 0, 0) + input:Dock(FILL) + input:SetFont("ChatFont") + cbox.chatbox.panels.input = input + + function input:Paint(w, h) + surface_SetDrawColor(0, 0, 0, 128) + surface_DrawRect(0, 0, w, h) + + self:DrawTextEntryText(INPUT_TEXT_COLOR, INPUT_HIGHLIGHT_COLOR, INPUT_TEXT_COLOR) + end + + local mode_switch = vgui.Create("DButton", input_wrapper) + mode_switch:SetTextColor(INPUT_TEXT_COLOR) + mode_switch:Dock(LEFT) + cbox.chatbox.panels.mode_switch = mode_switch + + function mode_switch:UpdateMode(mode) + local mode_data = cbox.chatbox.modes[mode] + if not mode_data then + self.current_mode = "say" + mode_data = cbox.chatbox.modes.say + end + + self.current_mode = mode + self:SetText(mode_data.name) + end + + function mode_switch:Paint(w, h) + surface_SetDrawColor(0, 0, 0, 128) + surface_DrawRect(0, 0, w, h) + end + + function mode_switch:ApplySchemeSettings() + local ExtraInset = 8 + + self:SetTextInset(ExtraInset, 0) + local w, h = self:GetContentSize() + + self:SetSize(w + 8, 20) + + DLabel.ApplySchemeSettings(self) + end + mode_switch:SetFont("ChatFont") + mode_switch:UpdateMode("say") + + function mode_switch:NextMode() + local keys = table.GetKeys(cbox.chatbox.modes) + table.sort(keys) + + local _, next_mode = next(keys, table.KeyFromValue(keys, self.current_mode)) + if not next_mode then next_mode = keys[1] end + + self:UpdateMode(next_mode) + end + + function mode_switch:DoClick() + self:NextMode() + end + function mode_switch:DoRightClick() + local menu = DermaMenu() + + for mode, data in SortedPairs(cbox.chatbox.modes) do + menu:AddOption(data.name, function() + self:UpdateMode(mode) + end) + end + + menu:Open() + end + + function input:OnKeyCodeTyped(key) + --[[if key == KEY_ESCAPE then + cbox.chatbox.Close() + gui.HideGameUI() + else--]] + if key == KEY_BACKQUOTE then + gui.HideGameUI() + elseif key == KEY_ENTER then + local text = self:GetText():Trim() + if text ~= "" then + cbox.chatbox.modes[mode_switch.current_mode].callback(text) + end + + cbox.chatbox.Close() + elseif key == KEY_TAB then + if #self:GetText() == 0 then + mode_switch:NextMode() + else + -- TODO: autocomplete + end + timer.Simple(0, function() self:RequestFocus() 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 + history:InsertColorChange(160, 160, 160, 255) + history:AppendText("???") + end + end + end + + history:AppendText("\n") +end) diff --git a/lua/cbox/tabs/settings.lua b/lua/cbox/tabs/settings.lua new file mode 100644 index 0000000..3de4997 --- /dev/null +++ b/lua/cbox/tabs/settings.lua @@ -0,0 +1,14 @@ +local surface = surface + +local surface_DrawRect = surface.DrawRect +local surface_SetDrawColor = surface.SetDrawColor + +cbox.chatbox.AddTab("zzzzzzzsettings", "Settings", "icon16/cog.png", function() + local wrapper = vgui.Create("EditablePanel") + function wrapper:Paint(w, h) + surface_SetDrawColor(0, 0, 0, 128) + surface_DrawRect(0, 0, w, h) + end + + return wrapper +end)