add sborka

This commit is contained in:
2026-03-31 10:27:04 +03:00
commit f5e5f56c84
2345 changed files with 382127 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
-- Garry's Mod Binder - Main Client Logic
include("binder/sh_lang.lua")
include("binder/core/sh_config.lua")
include("binder/vgui/cl_frame.lua")
include("binder/vgui/cl_profiles_tab.lua")
include("binder/vgui/cl_keybind_tab.lua")
include("binder/vgui/cl_radial_tab.lua")
include("binder/vgui/cl_radial_hud.lua")
include("binder/vgui/cl_settings_tab.lua")
Binder = Binder or {}
Binder.Profiles = Binder.Profiles or {}
Binder.F6Pressed = false
surface.CreateFont("Binder_Key", {
font = "Roboto",
size = 12,
weight = 700,
extended = true,
})
CreateClientConVar("binder_show_feedback", "1", true, false)
CreateClientConVar("binder_dark_theme", "1", true, false)
CreateClientConVar("binder_radial_key", tostring(Binder.Config.DefaultRadialKey), true, false)
-- Networking
net.Receive("Binder_SyncProfiles", function()
local length = net.ReadUInt(32)
local compressed = net.ReadData(length)
local data = util.Decompress(compressed)
if data then
Binder.Profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {})
if IsValid(Binder.Frame) then
Binder.Frame:RefreshActiveTab()
end
end
end)
function Binder.SaveToServer()
local data = util.TableToJSON(Binder.Profiles)
local compressed = util.Compress(data)
net.Start("Binder_UpdateProfile")
net.WriteUInt(#compressed, 32)
net.WriteData(compressed, #compressed)
net.SendToServer()
end
-- Input Hook for Binds (Client-side Think for Singleplayer support)
local PrevBindState = {}
hook.Add("Think", "Binder_CheckBindsThink", function()
local activeProfile
for _, p in ipairs(Binder.Profiles or {}) do
if p.Active then activeProfile = p break end
end
if not activeProfile or not activeProfile.Binds then return end
-- Don't trigger new presses if in console, ESC menu, typing in chat, or focused on a VGUI text entry
local blockNewInput = gui.IsConsoleVisible() or gui.IsGameUIVisible() or (vgui.GetKeyboardFocus() ~= nil) or LocalPlayer():IsTyping()
for key, commandStr in pairs(activeProfile.Binds) do
local isDown = input.IsButtonDown(key)
local wasDown = PrevBindState[key] or false
if isDown and not wasDown then
if not blockNewInput then
PrevBindState[key] = true
if string.sub(commandStr, 1, 1) == "/" then
LocalPlayer():ConCommand("say " .. commandStr)
else
LocalPlayer():ConCommand(commandStr)
end
end
elseif not isDown and wasDown then
PrevBindState[key] = false
-- Automatically release + commands
local cmd = string.Explode(" ", commandStr)[1]
if cmd and string.sub(cmd, 1, 1) == "+" then
local minusCmd = "-" .. string.sub(cmd, 2)
LocalPlayer():ConCommand(minusCmd)
end
end
end
end)
concommand.Add("binder_menu", function()
if Binder.Frame and IsValid(Binder.Frame) then
Binder.Frame:Remove()
end
Binder.Frame = vgui.Create("Binder_Frame")
end)
-- Open menu on F6
hook.Add("Think", "Binder_F6Check", function()
if input.IsKeyDown(KEY_F6) and not Binder.F6Pressed then
Binder.F6Pressed = true
RunConsoleCommand("binder_menu")
elseif not input.IsKeyDown(KEY_F6) then
Binder.F6Pressed = false
end
end)

View File

@@ -0,0 +1,65 @@
-- Garry's Mod Binder - Shared Configuration
Binder = Binder or {}
Binder.Config = {
AccentColor = Color(0, 67, 28), -- Dark green (#00431c)
BgColor = Color(30, 30, 30, 240),
HeaderColor = Color(0, 67, 28),
TabActiveColor = Color(255, 255, 255, 20),
TabHoverColor = Color(255, 255, 255, 10),
Font = "Inter", -- Assuming Inter is available or we'll define it
DefaultRadialKey = KEY_N,
}
function Binder.SanitizeProfiles(profiles)
for _, p in ipairs(profiles or {}) do
if p.Binds then
local newBinds = {}
for k, v in pairs(p.Binds) do
newBinds[tonumber(k) or k] = v
end
p.Binds = newBinds
end
if p.Radial then
local newRadial = {}
for k, v in pairs(p.Radial) do
newRadial[tonumber(k) or k] = v
end
p.Radial = newRadial
end
end
return profiles
end
if CLIENT then
surface.CreateFont("Binder_Title", {
font = "Roboto",
size = 24,
weight = 800,
extended = true,
antialias = true,
})
surface.CreateFont("Binder_Tab", {
font = "Roboto",
size = 18,
weight = 600,
extended = true,
antialias = true,
})
surface.CreateFont("Binder_Main", {
font = "Roboto",
size = 18,
weight = 500,
extended = true,
antialias = true,
})
surface.CreateFont("Binder_Small", {
font = "Roboto",
size = 14,
weight = 500,
extended = true,
antialias = true,
})
end

View File

@@ -0,0 +1,37 @@
-- Garry's Mod Binder - Shared Localization (Russian)
Binder = Binder or {}
Binder.Lang = {
title = "[BindMenu]",
profiles = "Профили",
keybinds = "Клавиши",
radial = "Радиальное меню",
settings = "Настройки",
profile_management = "Управление профилями",
profile_desc = "Выберите профиль для редактирования или создайте новый. Ранги Premium и Loyalty открывают дополнительные слоты.",
create_profile = "Создать профиль",
raiding_profile = "Рейд Профиль",
banker_profile = "Банкир Профиль",
binds_count = "%s биндов",
general_settings = "Общие настройки",
show_feedback = "Показывать сообщения обратной связи",
auto_scale = "Автоматическое масштабирование",
dark_theme = "Темная тема",
import_defaults = "Импорт стандартных биндов",
import_btn = "Импортировать бинды",
appearance_settings = "Настройки внешнего вида",
accent_color = "Акцентный цвет:",
change_color = "Изменить цвет",
preset_colors = "Предустановленные цвета:",
radial_config = "Настройка радиального меню",
radial_desc = "Нажмите на сегмент ниже для настройки бинда",
radial_center = "Радиальное",
slot = "Слот %s",
save = "Сохранить",
delete = "Удалить",
cancel = "Отмена",
}
function Binder.GetPhrase(key, ...)
local phrase = Binder.Lang[key] or key
return string.format(phrase, ...)
end

View File

@@ -0,0 +1,71 @@
-- Garry's Mod Binder - Main Server Logic
include("binder/sh_lang.lua")
include("binder/core/sh_config.lua")
util.AddNetworkString("Binder_SyncProfiles")
util.AddNetworkString("Binder_UpdateProfile")
util.AddNetworkString("Binder_DeleteProfile")
util.AddNetworkString("Binder_SetActiveProfile")
Binder = Binder or {}
Binder.Profiles = Binder.Profiles or {}
-- Default structure
-- Profile = { Name = "Profile1", Binds = { [KEY_X] = "cmd" }, Radial = { [1] = { Label = "L", Cmd = "cmd" } }, Active = true }
function Binder.SaveProfiles(ply)
local data = util.TableToJSON(Binder.Profiles[ply:SteamID64()] or {})
ply:SetPData("Binder_Profiles", data)
end
function Binder.LoadProfiles(ply)
local data = ply:GetPData("Binder_Profiles", "[]")
local profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {})
-- Ensure at least one default profile if empty
if table.Count(profiles) == 0 then
table.insert(profiles, {
Name = Binder.GetPhrase("raiding_profile"),
Binds = {},
Radial = {},
Active = true
})
end
Binder.Profiles[ply:SteamID64()] = profiles
-- Sync to client
net.Start("Binder_SyncProfiles")
local compressed = util.Compress(util.TableToJSON(profiles))
net.WriteUInt(#compressed, 32)
net.WriteData(compressed, #compressed)
net.Send(ply)
end
hook.Add("PlayerInitialSpawn", "Binder_LoadOnSpawn", function(ply)
Binder.LoadProfiles(ply)
end)
-- Receive entire profile updates from client (e.g., binds changed)
net.Receive("Binder_UpdateProfile", function(len, ply)
local length = net.ReadUInt(32)
local compressed = net.ReadData(length)
local data = util.Decompress(compressed)
if data then
local profiles = Binder.SanitizeProfiles(util.JSONToTable(data) or {})
if profiles then
Binder.Profiles[ply:SteamID64()] = profiles
Binder.SaveProfiles(ply)
end
end
end)
-- Chat Command to open the Binder
hook.Add("PlayerSay", "Binder_ChatCommand", function(ply, text, teamTalk)
local lowerText = string.lower(text)
if string.sub(lowerText, 1, 5) == "/bind" or string.sub(lowerText, 1, 5) == "!bind" then
ply:ConCommand("binder_menu")
return "" -- Hide the command from chat
end
end)

View File

@@ -0,0 +1,138 @@
-- Garry's Mod Binder - Custom Frame
local PANEL = {}
function PANEL:Init()
self:SetSize(ScrW() * 0.8, ScrH() * 0.8)
self:Center()
self:MakePopup()
self.AccentColor = Binder.Config.AccentColor
self.CurrentTab = "Profiles"
self.HeaderHeight = 60
self.TabHeight = 40
-- Tabs definitions
self.Tabs = {
{id = "Profiles", name = Binder.GetPhrase("profiles")},
{id = "Keybinds", name = Binder.GetPhrase("keybinds")},
{id = "Radial", name = Binder.GetPhrase("radial")},
{id = "Settings", name = Binder.GetPhrase("settings")},
}
-- Close Button
self.CloseBtn = vgui.Create("DButton", self)
self.CloseBtn:SetText("")
self.CloseBtn:SetFont("Binder_Main")
self.CloseBtn:SetTextColor(Color(255, 255, 255, 150))
self.CloseBtn.DoClick = function() self:Remove() end
self.CloseBtn.Paint = function(s, w, h)
if s:IsHovered() then
s:SetTextColor(Color(255, 255, 255, 255))
else
s:SetTextColor(Color(255, 255, 255, 150))
end
end
-- Tab Content Container
self.Content = vgui.Create("DPanel", self)
self.Content:Dock(FILL)
self.Content:DockMargin(0, self.HeaderHeight + self.TabHeight, 0, 0)
self.Content.Paint = function(s, w, h) end
self:SwitchTab("Profiles")
end
include("binder/vgui/cl_profiles_tab.lua")
include("binder/vgui/cl_keybind_tab.lua")
include("binder/vgui/cl_radial_tab.lua")
include("binder/vgui/cl_settings_tab.lua")
function PANEL:SwitchTab(id)
self.CurrentTab = id
self.Content:Clear()
if id == "Profiles" then
self.ActiveTab = vgui.Create("Binder_ProfilesTab", self.Content)
elseif id == "Keybinds" then
self.ActiveTab = vgui.Create("Binder_KeybindsTab", self.Content)
elseif id == "Radial" then
self.ActiveTab = vgui.Create("Binder_RadialTab", self.Content)
elseif id == "Settings" then
self.ActiveTab = vgui.Create("Binder_SettingsTab", self.Content)
else
local label = vgui.Create("DLabel", self.Content)
label:SetText("Content for " .. id)
label:SetFont("Binder_Title")
label:SizeToContents()
label:Center()
end
end
function PANEL:RefreshActiveTab()
if IsValid(self.ActiveTab) and self.ActiveTab.Refresh then
self.ActiveTab:Refresh()
elseif self.CurrentTab == "Profiles" and IsValid(self.ActiveTab) then
-- Fallback if the tab logic doesn't have a specific refresh method,
-- though we should add :Refresh to all of them.
self:SwitchTab(self.CurrentTab)
end
end
function PANEL:PerformLayout(w, h)
if IsValid(self.CloseBtn) then
self.CloseBtn:SetSize(30, 30)
self.CloseBtn:SetPos(w - 40, 15)
end
end
function PANEL:Paint(w, h)
-- Background with blur
draw.RoundedBox(8, 0, 0, w, h, Color(30, 30, 30, 200))
-- Header
draw.RoundedBoxEx(8, 0, 0, w, self.HeaderHeight, self.AccentColor, true, true, false, false)
draw.SimpleText(Binder.GetPhrase("title"), "Binder_Title", 20, self.HeaderHeight / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
-- Tabs Bar
draw.RoundedBox(0, 0, self.HeaderHeight, w, self.TabHeight, Color(45, 45, 45))
local tabWidth = w / #self.Tabs
for i, tab in ipairs(self.Tabs) do
local x = (i - 1) * tabWidth
local isActive = self.CurrentTab == tab.id
if isActive then
draw.RoundedBox(0, x, self.HeaderHeight, tabWidth, self.TabHeight, Color(255, 255, 255, 10))
draw.RoundedBox(0, x + 20, self.HeaderHeight + self.TabHeight - 3, tabWidth - 40, 3, Color(255, 255, 255))
end
local isHovered = gui.MouseX() >= self:GetPos() + x and gui.MouseX() < self:GetPos() + x + tabWidth and
gui.MouseY() >= self:GetY() + self.HeaderHeight and gui.MouseY() < self:GetY() + self.HeaderHeight + self.TabHeight
if isHovered and not isActive then
draw.RoundedBox(0, x, self.HeaderHeight, tabWidth, self.TabHeight, Color(255, 255, 255, 5))
end
draw.SimpleText(tab.name, "Binder_Tab", x + tabWidth / 2, self.HeaderHeight + self.TabHeight / 2,
isActive and Color(255, 255, 255) or Color(150, 150, 150),
TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
-- Hidden button for tab click
if not self.TabButtons then self.TabButtons = {} end
if not self.TabButtons[i] then
local btn = vgui.Create("DButton", self)
btn:SetText("")
btn.Paint = function() end
btn.DoClick = function() self:SwitchTab(tab.id) end
self.TabButtons[i] = btn
end
self.TabButtons[i]:SetSize(tabWidth, self.TabHeight)
self.TabButtons[i]:SetPos(x, self.HeaderHeight)
end
end
vgui.Register("Binder_Frame", PANEL, "EditablePanel")

View File

@@ -0,0 +1,204 @@
-- Garry's Mod Binder - Keybinds Tab (Virtual Keyboard)
local PANEL = {}
local KEY_LAYOUT = {
{"ESC", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12"},
{"~", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "BKSP"},
{"TAB", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\\"},
{"CAPS", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "ENTER"},
{"SHIFT", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "SHIFT"},
{"CTRL", "WIN", "ALT", "SPACE", "ALT", "WIN", "MENU"}
}
local KEY_SPECIAL = {
{"PRSC", "SCRLK", "PAUSE"},
{"INS", "HOME", "PGUP"},
{"DEL", "END", "PGDN"},
{""},
{"", "", ""}
}
local NUM_PAD = {
{"NUM", "/", "*", "-"},
{"7", "8", "9", "+"},
{"4", "5", "6"},
{"1", "2", "3", "ENT"},
{"0", "."}
}
local STRING_TO_KEY = {
["ESC"] = KEY_ESCAPE, ["~"] = KEY_TILDE, ["-"] = KEY_MINUS, ["="] = KEY_EQUAL, ["BKSP"] = KEY_BACKSPACE, ["TAB"] = KEY_TAB,
["["] = KEY_LBRACKET, ["]"] = KEY_RBRACKET, ["\\"] = KEY_BACKSLASH, ["CAPS"] = KEY_CAPSLOCK, [";"] = KEY_SEMICOLON,
["'"] = KEY_APOSTROPHE, ["ENTER"] = KEY_ENTER, ["SHIFT"] = KEY_LSHIFT, [","] = KEY_COMMA, ["."] = KEY_PERIOD,
["/"] = KEY_SLASH, ["CTRL"] = KEY_LCONTROL, ["WIN"] = KEY_LWIN, ["ALT"] = KEY_LALT, ["SPACE"] = KEY_SPACE,
["MENU"] = KEY_APP,
["PRSC"] = KEY_SYSRQ, ["SCRLK"] = KEY_SCROLLLOCK, ["PAUSE"] = KEY_BREAK, ["INS"] = KEY_INSERT, ["HOME"] = KEY_HOME,
["PGUP"] = KEY_PAGEUP, ["DEL"] = KEY_DELETE, ["END"] = KEY_END, ["PGDN"] = KEY_PAGEDOWN,
[""] = KEY_UP, [""] = KEY_LEFT, [""] = KEY_DOWN, [""] = KEY_RIGHT,
["NUM"] = KEY_NUMLOCK, ["/"] = KEY_PAD_DIVIDE, ["*"] = KEY_PAD_MULTIPLY, ["-"] = KEY_PAD_MINUS, ["+"] = KEY_PAD_PLUS,
["ENT"] = KEY_PAD_ENTER, ["."] = KEY_PAD_DECIMAL,
["1"] = KEY_1, ["2"] = KEY_2, ["3"] = KEY_3, ["4"] = KEY_4, ["5"] = KEY_5, ["6"] = KEY_6, ["7"] = KEY_7, ["8"] = KEY_8, ["9"] = KEY_9, ["0"] = KEY_0,
}
-- Add letters and F-keys
for i = 1, 26 do STRING_TO_KEY[string.char(64 + i)] = _G["KEY_" .. string.char(64 + i)] end
for i = 1, 12 do STRING_TO_KEY["F" .. i] = _G["KEY_F" .. i] end
function PANEL:Init()
self:Dock(FILL)
self:DockMargin(20, 20, 20, 20)
self.Paint = function() end
self.InfoBar = vgui.Create("DPanel", self)
self.InfoBar:Dock(TOP)
self.InfoBar:SetHeight(40)
self.InfoBar.Paint = function(s, w, h)
draw.RoundedBox(0, 0, 0, w, h, Color(255, 255, 255, 5))
local activeProf = self:GetActiveProfile()
local name = activeProf and activeProf.Name or "No Active Profile"
local bindCount = activeProf and table.Count(activeProf.Binds or {}) or 0
draw.SimpleText(name, "Binder_Main", 50, h / 2, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
draw.SimpleText(Binder.GetPhrase("binds_count", bindCount), "Binder_Small", w - 20, h / 2, Color(255, 255, 255, 100), TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER)
end
self.KeyboardContainer = vgui.Create("DPanel", self)
self.KeyboardContainer:Dock(FILL)
self.KeyboardContainer:DockMargin(0, 40, 0, 0)
self.KeyboardContainer.Paint = function() end
self.KeyButtons = {}
self:BuildKeyboard()
self:Refresh()
end
function PANEL:GetActiveProfile()
for _, p in ipairs(Binder.Profiles or {}) do
if p.Active then return p end
end
return nil
end
function PANEL:Refresh()
local profile = self:GetActiveProfile()
local binds = profile and profile.Binds or {}
for _, btnData in ipairs(self.KeyButtons) do
local isBound = binds[btnData.enum] != nil
btnData.btn.IsActiveNode = isBound
end
end
function PANEL:PromptBind(keyStr, keyEnum)
local profile = self:GetActiveProfile()
if not profile then return end
local currentCmd = profile.Binds[keyEnum] or ""
Derma_StringRequest(
"Bind " .. keyStr,
"Enter console command to execute on press (e.g., 'say /raid'):\nLeave blank to unbind.",
currentCmd,
function(text)
if text == "" then
profile.Binds[keyEnum] = nil
else
profile.Binds[keyEnum] = text
end
Binder.SaveToServer()
self:Refresh()
end
)
end
function PANEL:CreateKey(parent, text, x, y, w, h)
local btn = vgui.Create("DButton", parent)
btn:SetSize(w or 35, h or 35)
btn:SetPos(x, y)
btn:SetText("")
btn.IsActiveNode = false
local keyEnum = STRING_TO_KEY[text]
btn.Paint = function(s, bw, bh)
local bgColor = s.IsActiveNode and Binder.Config.AccentColor or Color(40, 40, 40)
if s:IsHovered() then
bgColor = Color(bgColor.r + 20, bgColor.g + 20, bgColor.b + 20)
end
draw.RoundedBox(4, 0, 0, bw, bh, bgColor)
draw.SimpleText(text, "Binder_Key", bw / 2, bh / 2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
btn.DoClick = function()
if keyEnum then
self:PromptBind(text, keyEnum)
else
print("Binder: Unmapped key - " .. text)
end
end
table.insert(self.KeyButtons, {btn = btn, sysName = text, enum = keyEnum})
end
function PANEL:BuildKeyboard()
local startX, startY = 10, 10
local keySize = 38
local spacing = 4
-- Main Block
for r, row in ipairs(KEY_LAYOUT) do
local rx = startX
local ry = startY + (r - 1) * (keySize + spacing)
-- Adjustments
if r == 1 then ry = ry - 5 end
for k, key in ipairs(row) do
local kw = keySize
if key == "BKSP" then kw = keySize * 2 + spacing
elseif key == "TAB" then kw = keySize * 1.5
elseif key == "CAPS" then kw = keySize * 1.8
elseif key == "ENTER" then kw = keySize * 1.8
elseif key == "SHIFT" then kw = keySize * 2.3
elseif key == "SPACE" then kw = keySize * 5 + spacing * 4
elseif key == "CTRL" or key == "ALT" or key == "WIN" then kw = keySize * 1.2
end
self:CreateKey(self.KeyboardContainer, key, rx, ry, kw, keySize)
rx = rx + kw + spacing
end
end
-- Special Keys Block
local specX = startX + 15 * (keySize + spacing)
for r, row in ipairs(KEY_SPECIAL) do
local rx = specX
local ry = startY + (r - 1) * (keySize + spacing)
if r == 4 then rx = rx + keySize + spacing end
for k, key in ipairs(row) do
self:CreateKey(self.KeyboardContainer, key, rx, ry, keySize, keySize)
rx = rx + keySize + spacing
end
end
-- Num Pad
local numX = specX + 4 * (keySize + spacing)
for r, row in ipairs(NUM_PAD) do
local rx = numX
local ry = startY + (r - 1) * (keySize + spacing)
for k, key in ipairs(row) do
local kh = keySize
if key == "+" or key == "ENT" then kh = keySize * 2 + spacing end
local kw = keySize
if key == "0" then kw = keySize * 2 + spacing end
self:CreateKey(self.KeyboardContainer, key, rx, ry, kw, kh)
rx = rx + kw + spacing
end
end
end
vgui.Register("Binder_KeybindsTab", PANEL, "EditablePanel")

View File

@@ -0,0 +1,104 @@
-- Garry's Mod Binder - Profiles Tab
local PANEL = {}
function PANEL:Init()
self:Dock(FILL)
self:DockMargin(40, 20, 40, 20)
self.Paint = function() end
-- Title and Description
self.Header = vgui.Create("DPanel", self)
self.Header:Dock(TOP)
self.Header:SetHeight(50)
self.Header.Paint = function(s, w, h)
draw.SimpleText(Binder.GetPhrase("profile_management"), "Binder_Title", 0, 0, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
end
-- Profile Grid
self.Grid = vgui.Create("DIconLayout", self)
self.Grid:Dock(FILL)
self.Grid:SetSpaceX(20)
self.Grid:SetSpaceY(20)
self:Refresh()
end
function PANEL:Refresh()
self.Grid:Clear()
local profiles = Binder.Profiles or {}
for i, profile in ipairs(profiles) do
local card = self.Grid:Add("DButton")
card:SetSize(200, 150)
card:SetText("")
card.Paint = function(s, w, h)
local bgColor = profile.Active and Binder.Config.AccentColor or Color(40, 40, 40)
draw.RoundedBox(8, 0, 0, w, h, bgColor)
draw.SimpleText(profile.Name, "Binder_Main", w / 2, h / 2 - 10, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
local bindCount = table.Count(profile.Binds or {})
draw.SimpleText(Binder.GetPhrase("binds_count", bindCount), "Binder_Small", w / 2, h / 2 + 15, Color(255, 255, 255, 100), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
if profile.Active then
draw.RoundedBox(4, w - 15, 10, 8, 8, Color(0, 255, 0)) -- Active dot
end
end
card.DoClick = function()
-- Set active
for _, p in ipairs(Binder.Profiles) do p.Active = false end
profile.Active = true
Binder.SaveToServer()
self:Refresh()
end
card.DoRightClick = function()
-- Delete profile
if #Binder.Profiles > 1 then
local menu = DermaMenu()
menu:AddOption(Binder.GetPhrase("delete"), function()
table.remove(Binder.Profiles, i)
-- ensure one is active
local hasActive = false
for _, p in ipairs(Binder.Profiles) do if p.Active then hasActive = true break end end
if not hasActive and #Binder.Profiles > 0 then Binder.Profiles[1].Active = true end
Binder.SaveToServer()
self:Refresh()
end):SetIcon("icon16/delete.png")
menu:Open()
end
end
end
-- Create New Profile Buttons (to fill up to 6 as in screenshot)
if #profiles < 6 then
local card = self.Grid:Add("DButton")
card:SetSize(200, 150)
card:SetText("")
card.Paint = function(s, w, h)
draw.RoundedBox(8, 0, 0, w, h, Color(40, 40, 40))
draw.SimpleText("+", "Binder_Title", w / 2, h / 2 - 15, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
draw.SimpleText(Binder.GetPhrase("create_profile"), "Binder_Main", w / 2, h / 2 + 20, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
card.DoClick = function()
Derma_StringRequest(
Binder.GetPhrase("create_profile"),
"Enter profile name:",
"New Profile",
function(text)
table.insert(Binder.Profiles, {
Name = text,
Binds = {},
Radial = {},
Active = false
})
Binder.SaveToServer()
self:Refresh()
end
)
end
end
end
vgui.Register("Binder_ProfilesTab", PANEL, "EditablePanel")

View File

@@ -0,0 +1,94 @@
-- Garry's Mod Binder - Radial HUD
local IsRadialOpen = false
local SelectedSlot = 0
local function GetActiveProfile()
for _, p in ipairs(Binder.Profiles or {}) do
if p.Active then return p end
end
return nil
end
hook.Add("HUDPaint", "Binder_DrawRadial", function()
if not IsRadialOpen then return end
local profile = GetActiveProfile()
if not profile or not profile.Radial then return end
local cx, cy = ScrW() / 2, ScrH() / 2
local radius = 150
-- Draw Center
draw.NoTexture()
surface.SetDrawColor(Color(30, 30, 30, 240))
surface.DrawCircle(cx, cy, 50, 255, 255, 255, 255)
draw.SimpleText(Binder.GetPhrase("radial_center"), "Binder_Main", cx, cy, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
local mouseX, mouseY = gui.MousePos()
if mouseX == 0 and mouseY == 0 then
mouseX, mouseY = cx, cy -- fallback if not capturing properly
end
local dist = math.Dist(cx, cy, mouseX, mouseY)
local angle = math.deg(math.atan2(mouseY - cy, mouseX - cx)) + 90
if angle < 0 then angle = angle + 360 end
-- Determine selected segment based on angle (8 segments, 45 deg each)
if dist > 50 then
SelectedSlot = math.floor((angle + 22.5) / 45) + 1
if SelectedSlot > 8 then SelectedSlot = 1 end
else
SelectedSlot = 0
end
-- Draw segments
for i = 1, 8 do
local slotData = profile.Radial[i]
if slotData then
local segAngle = (i - 1) * 45 - 90
local rad = math.rad(segAngle)
local lx, ly = cx + math.cos(rad) * radius, cy + math.sin(rad) * radius
local boxColor = (SelectedSlot == i) and Binder.Config.AccentColor or Color(40, 40, 45, 240)
draw.RoundedBox(8, lx - 40, ly - 20, 80, 40, boxColor)
draw.SimpleText(slotData.Label, "Binder_Main", lx, ly, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
end
-- Draw cursor line
surface.SetDrawColor(255, 255, 255, 50)
surface.DrawLine(cx, cy, mouseX, mouseY)
end)
local PrevRadialState = false
hook.Add("Think", "Binder_RadialThink", function()
local cvar = GetConVar("binder_radial_key")
local radialKey = cvar and cvar:GetInt() or Binder.Config.DefaultRadialKey
local isDown = input.IsButtonDown(radialKey)
local blockNewInput = gui.IsConsoleVisible() or gui.IsGameUIVisible() or (vgui.GetKeyboardFocus() ~= nil) or (LocalPlayer() and LocalPlayer():IsValid() and LocalPlayer():IsTyping())
if isDown and not PrevRadialState then
if not blockNewInput then
PrevRadialState = true
IsRadialOpen = true
gui.EnableScreenClicker(true)
SelectedSlot = 0
end
elseif not isDown and PrevRadialState then
PrevRadialState = false
if IsRadialOpen then
IsRadialOpen = false
gui.EnableScreenClicker(false)
if SelectedSlot > 0 then
local profile = GetActiveProfile()
if profile and profile.Radial and profile.Radial[SelectedSlot] then
LocalPlayer():ConCommand(profile.Radial[SelectedSlot].Command)
end
end
end
end
end)

View File

@@ -0,0 +1,150 @@
-- Garry's Mod Binder - Radial Menu Tab
local PANEL = {}
function PANEL:Init()
self:Dock(FILL)
self:DockMargin(40, 20, 40, 20)
self.Paint = function() end
self.Header = vgui.Create("DPanel", self)
self.Header:Dock(TOP)
self.Header:SetHeight(80)
self.Header.Paint = function(s, w, h)
draw.SimpleText(Binder.GetPhrase("radial_config"), "Binder_Title", 0, 10, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
draw.SimpleText(Binder.GetPhrase("radial_desc"), "Binder_Small", 0, 40, Color(255, 255, 255, 100), TEXT_ALIGN_LEFT, TEXT_ALIGN_TOP)
end
self.RadialContainer = vgui.Create("DPanel", self)
self.RadialContainer:Dock(FILL)
self.RadialContainer.Paint = function(s, w, h)
local cx, cy = w / 2, h / 2
draw.NoTexture()
surface.SetDrawColor(Color(255, 255, 255, 10))
surface.DrawCircle(cx, cy, 40, 255, 255, 255, 255)
draw.SimpleText(Binder.GetPhrase("radial_center"), "Binder_Small", cx, cy, Color(255, 255, 255, 150), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
self.Segments = {}
self:BuildSegments()
self:Refresh()
end
function PANEL:GetActiveProfile()
for _, p in ipairs(Binder.Profiles or {}) do
if p.Active then return p end
end
return nil
end
function PANEL:BuildSegments()
local cx, cy = 0, 0 -- Will be updated in PerformLayout
local radius = 120
for i = 1, 8 do
local btn = vgui.Create("DButton", self.RadialContainer)
btn:SetSize(80, 80)
btn:SetText("")
btn.Slot = i
btn.Paint = function(s, w, h)
local bgColor = s:IsHovered() and Binder.Config.AccentColor or Color(40, 40, 45)
draw.RoundedBox(20, 0, 0, w, h, bgColor)
local activeProf = self:GetActiveProfile()
local text = Binder.GetPhrase("slot", i)
if activeProf and activeProf.Radial and activeProf.Radial[i] then
text = activeProf.Radial[i].Label
end
draw.SimpleText(text, "Binder_Small", w/2, h/2, Color(255, 255, 255), TEXT_ALIGN_CENTER, TEXT_ALIGN_CENTER)
end
btn.DoClick = function(s)
self:PromptSegment(s.Slot)
end
table.insert(self.Segments, btn)
end
end
function PANEL:Refresh()
-- Nothing inherently needed here unless we drastically change profile
end
function PANEL:PerformLayout(w, h)
local cx, cy = w / 2, (h - 80) / 2
local radius = 120
for i, btn in ipairs(self.Segments) do
local angle = (i - 1) * (360 / 8) - 90 -- Start at top
local rad = math.rad(angle)
local lx, ly = cx + math.cos(rad) * radius, cy + math.sin(rad) * radius
btn:SetPos(lx - 40, ly - 40)
end
end
function PANEL:PromptSegment(slot)
local profile = self:GetActiveProfile()
if not profile then return end
profile.Radial = profile.Radial or {}
local currentLabel = profile.Radial[slot] and profile.Radial[slot].Label or ""
local currentCmd = profile.Radial[slot] and profile.Radial[slot].Command or ""
local frame = vgui.Create("DFrame")
frame:SetTitle("Configure Radial Slot " .. slot)
frame:SetSize(300, 150)
frame:Center()
frame:MakePopup()
local lbl1 = vgui.Create("DLabel", frame)
lbl1:SetPos(10, 30)
lbl1:SetText("Label (Text):")
lbl1:SizeToContents()
local txtLabel = vgui.Create("DTextEntry", frame)
txtLabel:SetPos(10, 50)
txtLabel:SetSize(280, 20)
txtLabel:SetText(currentLabel)
local lbl2 = vgui.Create("DLabel", frame)
lbl2:SetPos(10, 80)
lbl2:SetText("Console Command:")
lbl2:SizeToContents()
local txtCmd = vgui.Create("DTextEntry", frame)
txtCmd:SetPos(10, 100)
txtCmd:SetSize(280, 20)
txtCmd:SetText(currentCmd)
local saveBtn = vgui.Create("DButton", frame)
saveBtn:SetPos(10, 125)
saveBtn:SetSize(135, 20)
saveBtn:SetText("Save")
saveBtn.DoClick = function()
local l = txtLabel:GetValue()
local c = txtCmd:GetValue()
if l == "" or c == "" then
profile.Radial[slot] = nil
else
profile.Radial[slot] = { Label = l, Command = c }
end
Binder.SaveToServer()
self:Refresh()
frame:Remove()
end
local clearBtn = vgui.Create("DButton", frame)
clearBtn:SetPos(155, 125)
clearBtn:SetSize(135, 20)
clearBtn:SetText("Clear Slot")
clearBtn.DoClick = function()
profile.Radial[slot] = nil
Binder.SaveToServer()
self:Refresh()
frame:Remove()
end
end
vgui.Register("Binder_RadialTab", PANEL, "EditablePanel")

View File

@@ -0,0 +1,182 @@
-- Garry's Mod Binder - Settings Tab
local PANEL = {}
function PANEL:Init()
self:Dock(FILL)
self:DockMargin(40, 20, 40, 20)
self.Paint = function() end
self.Scroll = vgui.Create("DScrollPanel", self)
self.Scroll:Dock(FILL)
local sbar = self.Scroll:GetVBar()
sbar:SetWide(6)
sbar:SetHideButtons(true)
sbar.Paint = function(s, w, h) end
sbar.btnGrip.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, Color(255, 255, 255, 30))
end
self:AddCategory(Binder.GetPhrase("general_settings"), {
{type = "check", label = Binder.GetPhrase("show_feedback"), convar = "binder_show_feedback"},
{type = "check", label = Binder.GetPhrase("dark_theme"), convar = "binder_dark_theme"},
})
self:AddCategory("Radial Menu Key", {
{type = "keybind", label = "Activation Key", convar = "binder_radial_key"},
})
self:AddCategory(Binder.GetPhrase("import_defaults"), {
{type = "button", label = Binder.GetPhrase("import_btn"), btnText = Binder.GetPhrase("import_btn"),
onClick = function()
Binder.Profiles = {}
table.insert(Binder.Profiles, {Name = Binder.GetPhrase("raiding_profile"), Binds = {}, Radial = {}, Active = true})
Binder.SaveToServer()
if Binder.Frame and IsValid(Binder.Frame) then Binder.Frame:RefreshActiveTab() end
end},
})
self:AddCategory(Binder.GetPhrase("appearance_settings"), {
{type = "color", label = Binder.GetPhrase("accent_color")},
{type = "presets", label = Binder.GetPhrase("preset_colors")},
})
end
function PANEL:AddCategory(title, items)
local cat = self.Scroll:Add("DPanel")
cat:Dock(TOP)
cat:DockMargin(0, 0, 0, 20)
cat:SetHeight(40 + #items * 40)
cat.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, 30, Binder.Config.AccentColor)
draw.SimpleText(title, "Binder_Main", 10, 15, Color(255, 255, 255), TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER)
draw.RoundedBoxEx(4, 0, 30, w, h - 30, Color(40, 40, 40), false, false, true, true)
end
local itemContainer = vgui.Create("DPanel", cat)
itemContainer:Dock(FILL)
itemContainer:DockMargin(10, 35, 10, 5)
itemContainer.Paint = function() end
for _, item in ipairs(items) do
local row = vgui.Create("DPanel", itemContainer)
row:Dock(TOP)
row:SetHeight(35)
row.Paint = function() end
if item.type == "check" and item.convar then
local chk = vgui.Create("DCheckBox", row)
chk:SetPos(0, 10)
chk:SetConVar(item.convar)
local lbl = vgui.Create("DLabel", row)
lbl:SetText(item.label)
lbl:SetFont("Binder_Small")
lbl:SetPos(25, 8)
lbl:SizeToContents()
elseif item.type == "keybind" and item.convar then
local lbl = vgui.Create("DLabel", row)
lbl:SetText(item.label)
lbl:SetFont("Binder_Small")
lbl:SetPos(0, 8)
lbl:SizeToContents()
local binderBtn = vgui.Create("DBinder", row)
binderBtn:SetSize(120, 20)
binderBtn:SetPos(150, 8)
binderBtn:SetConVar(item.convar)
binderBtn.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, Color(60, 60, 60))
draw.SimpleText(input.GetKeyName(s:GetValue()) or "NONE", "Binder_Small", w/2, h/2, Color(255,255,255), 1, 1)
end
elseif item.type == "button" then
local btn = vgui.Create("DButton", row)
btn:SetText(item.btnText)
btn:SetFont("Binder_Small")
btn:SetSize(150, 25)
btn:SetPos(0, 5)
btn:SetTextColor(Color(255, 255, 255))
btn.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, Binder.Config.AccentColor)
end
if item.onClick then btn.DoClick = item.onClick end
elseif item.type == "color" then
local lbl = vgui.Create("DLabel", row)
lbl:SetText(item.label)
lbl:SetFont("Binder_Small")
lbl:SetPos(0, 8)
lbl:SizeToContents()
local colPreview = vgui.Create("DPanel", row)
colPreview:SetSize(30, 20)
colPreview:SetPos(150, 8)
colPreview.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, Binder.Config.AccentColor)
end
local btn = vgui.Create("DButton", row)
btn:SetText(Binder.GetPhrase("change_color"))
btn:SetSize(100, 20)
btn:SetPos(190, 8)
btn:SetFont("Binder_Small")
btn:SetTextColor(Color(255, 255, 255))
btn.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, Color(60, 60, 60))
end
btn.DoClick = function()
local frame = vgui.Create("DFrame")
frame:SetTitle("Select Theme Color")
frame:SetSize(300, 250)
frame:Center()
frame:MakePopup()
local mixer = vgui.Create("DColorMixer", frame)
mixer:Dock(FILL)
mixer:SetPalette(true)
mixer:SetAlphaBar(false)
mixer:SetWangs(true)
mixer:SetColor(Binder.Config.AccentColor)
local apply = vgui.Create("DButton", frame)
apply:Dock(BOTTOM)
apply:SetText("Apply")
apply.DoClick = function()
Binder.Config.AccentColor = mixer:GetColor()
if IsValid(Binder.Frame) then Binder.Frame.AccentColor = Binder.Config.AccentColor end
frame:Remove()
end
end
elseif item.type == "presets" then
local lbl = vgui.Create("DLabel", row)
lbl:SetText(item.label)
lbl:SetFont("Binder_Small")
lbl:SetPos(0, 8)
lbl:SizeToContents()
local presets = {Color(0, 67, 28), Color(52, 152, 219), Color(46, 204, 113), Color(231, 76, 60), Color(155, 89, 182), Color(230, 126, 34)}
for i, col in ipairs(presets) do
local p = vgui.Create("DButton", row)
p:SetSize(20, 20)
p:SetPos(150 + (i-1)*25, 8)
p:SetText("")
p.Paint = function(s, w, h)
draw.RoundedBox(4, 0, 0, w, h, col)
if Binder.Config.AccentColor == col then
surface.SetDrawColor(255, 255, 255)
surface.DrawOutlinedRect(0, 0, w, h, 2)
end
end
p.DoClick = function()
Binder.Config.AccentColor = col
if IsValid(Binder.Frame) then Binder.Frame.AccentColor = col end
end
end
end
end
end
vgui.Register("Binder_SettingsTab", PANEL, "EditablePanel")