Files
wnsrc/lua/pac3/editor/client/panels/properties.lua
lifestorm 73479cff9e Upload
2024-08-04 22:55:00 +03:00

1580 lines
37 KiB
Lua

--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]
local L = pace.LanguageString
local languageID = CreateClientConVar("pac_editor_languageid", 1, true, false, "Whether we should show the language indicator inside of editable text entries.")
function pace.ShowSpecial(pnl, parent, size)
size = size or 150
pnl:SetPos(pace.Editor:GetWide(), select(2, parent:LocalToScreen()) - size + 25)
pnl:SetSize(size, size)
pnl:MakePopup()
end
function pace.FixMenu(menu)
menu:SetMaxHeight(500)
menu:InvalidateLayout(true, true)
menu:SetPos(pace.Editor:GetPos() + pace.Editor:GetWide(), gui.MouseY() - (menu:GetTall() * 0.5))
end
local function DefineMoreOptionsLeftClick(self, callFuncLeft, callFuncRight)
local btn = vgui.Create("DButton", self)
btn:SetSize(16, 16)
btn:Dock(RIGHT)
btn:SetText("...")
btn.DoClick = function() callFuncLeft(self, self.CurrentKey) end
if callFuncRight then
btn.DoRightClick = function() callFuncRight(self, self.CurrentKey) end
else
btn.DoRightClick = btn.DoClick
end
if self.OnMoreOptionsLeftClickButton then
self:OnMoreOptionsLeftClickButton(btn)
end
return btn
end
function pace.CreateSearchList(property, key, name, add_columns, get_list, get_current, add_line, select_value, select_value_search)
select_value = select_value or function(val, key) return val end
select_value_search = select_value_search or select_value
pace.SafeRemoveSpecialPanel()
local frame = vgui.Create("DFrame")
frame:SetTitle(L(name))
frame:SetSize(300, 300)
frame:Center()
frame:SetSizable(true)
local list = vgui.Create("DListView", frame)
list:Dock(FILL)
list:SetMultiSelect(false)
add_columns(list)
list.OnRowSelected = function(_, id, line)
local val = select_value(line.list_val, line.list_key)
if property and property:IsValid() then
property:SetValue(val)
property.OnValueChanged(val)
else
if pace.current_part:IsValid() and pace.current_part["Set" .. key] then
pace.Call("VariableChanged", pace.current_part, key, val)
end
end
end
local first = NULL
local function build(find)
list:Clear()
local cur = get_current()
local newList = {}
for k, v in pairs(get_list()) do
table.insert(newList, {k, v, tostring(k), tostring(v)})
end
table.sort(newList, function(a, b) return a[1] < b[1] end)
if find then find = find:lower() end
for i, data in ipairs(newList) do
local key, val, keyFriendly, valFriendly = data[1], data[2], data[3], data[4]
if (not find or find == "") or tostring(select_value_search(valFriendly, keyFriendly)):lower():find(find, nil, true) then
local pnl = add_line(list, key, val)
pnl.list_key = key
pnl.list_val = val
if not first:IsValid() then
first = pnl
end
if cur == name then
list:SelectItem(pnl)
end
end
end
end
local search = vgui.Create("DTextEntry", frame)
search:Dock(BOTTOM)
search.OnTextChanged = function() build(search:GetValue()) end
search.OnEnter = function() if first:IsValid() then list:SelectItem(first) end frame:Remove() end
search:RequestFocus()
frame:MakePopup()
build()
pace.ActiveSpecialPanel = frame
return frame
end
pace.ActiveSpecialPanel = NULL
pace.extra_populates = {}
function pace.SafeRemoveSpecialPanel()
if pace.ActiveSpecialPanel:IsValid() then
pace.ActiveSpecialPanel:Remove()
end
end
pac.AddHook("GUIMousePressed", "pace_SafeRemoveSpecialPanel", function()
local pnl = pace.ActiveSpecialPanel
if pnl:IsValid() then
local x,y = input.GetCursorPos()
local _x, _y = pnl:GetPos()
if x < _x or y < _y or x > _x + pnl:GetWide() or y > _y + pnl:GetTall() then
pnl:Remove()
end
end
end)
do -- container
local PANEL = {}
PANEL.ClassName = "properties_container"
PANEL.Base = "DPanel"
function PANEL:Paint(w, h)
--surface.SetDrawColor(255, 255, 255, 255)
--surface.DrawRect(0,0,w,h)
--self:GetSkin().tex.CategoryList.Outer(0, 0, w, h)
--self:GetSkin().tex.MenuBG(0, 0, w + (self.right and -1 or 3), h + 1)
if not self.right then
--surface.SetDrawColor(self:GetSkin().Colours.Properties.Border)
--surface.DrawRect(0,0,w+5,h)
else
--surface.SetDrawColor(self:GetSkin().Colours.Properties.Border)
--surface.DrawRect(0,0,w,h)
end
self.AltLine = self.alt_line
derma.SkinHook( "Paint", "CategoryButton", self, w, h )
end
function PANEL:SetContent(pnl)
pnl:SetParent(self)
self.content = pnl
end
function PANEL:PerformLayout()
local pnl = self.content or NULL
if pnl:IsValid() then
pnl:SetPos(0, 0)
pnl:SetSize(self:GetSize())
end
end
pace.RegisterPanel(PANEL)
end
do -- list
local PANEL = {}
PANEL.ClassName = "properties"
PANEL.Base = "Panel"
AccessorFunc(PANEL, "item_height", "ItemHeight")
function PANEL:Init()
local search = vgui.Create("DTextEntry", self)
search:Dock(TOP)
search.Kill = function()
search:SetVisible(false)
search.searched_something = false
search:SetText("")
search:SetEnabled(false)
for i,v in ipairs(self.List) do
v.left:SetVisible(true)
v.right:SetVisible(true)
end
end
search.OnEnter = search.Kill
search.OnTextChanged = function()
self.scr:SetScroll(0)
local pattern = search:GetValue()
if pattern == "" and search.searched_something then
search:Kill()
search:KillFocus()
pace.Editor:KillFocus()
pace.Editor:MakePopup()
else
search.searched_something = true
local group
for i,v in ipairs(self.List) do
local found = false
if v.panel then
if v.panel:GetText():find(pattern) then
found = true
end
if v.left:GetValue():find(pattern) then
found = true
end
elseif v.left and v.left.text then
group = v.left.text
end
if group and group:find(pattern) then
found = true
end
if not found and v.panel then
v.left:SetVisible(false)
v.right:SetVisible(false)
end
end
for i,v in ipairs(self.List) do
if not v.panel then
local hide_group = true
for i = i+1, #self.List do
local val = self.List[i]
if not val.panel then
break
end
if val.left:IsVisible() then
hide_group = false
break
end
end
if hide_group then
v.left:SetVisible(false)
v.right:SetVisible(false)
end
end
end
end
end
search:SetVisible(false)
self.search = search
self.List = {}
local divider = vgui.Create("DHorizontalDivider", self)
local left = vgui.Create("DPanelList", divider)
divider:SetLeft(left)
self.left = left
local right = vgui.Create("DPanelList", divider)
divider:SetRight(right)
self.right = right
divider:SetDividerWidth(3)
surface.SetFont(pace.CurrentFont)
local w,h = surface.GetTextSize("W")
local size = h + 2
self:SetItemHeight(size)
self.div = divider
function divider:PerformLayout()
DHorizontalDivider.PerformLayout(self)
if self.m_pLeft then
self.m_pLeft:SetWide( self.m_iLeftWidth + self.m_iDividerWidth )
end
end
local scroll = vgui.Create("DVScrollBar", self)
scroll:Dock(RIGHT)
self.scr = scroll
left.OnMouseWheeled = function(_, delta) scroll:OnMouseWheeled(delta) end
--right.OnMouseWheeled = function(_, delta) scroll:OnMouseWheeled(delta) end
end
function PANEL:GetHeight(hack)
return (self.item_height * (#self.List+(hack or 1))) - (self.div:GetDividerWidth() + 1)
end
function PANEL:PerformLayout()
self.scr:SetSize(10, self:GetHeight())
self.scr:SetUp(self:GetTall(), self:GetHeight() - 10)
self.search:SetZPos(1)
self.div:SetPos(0, (self.search:IsVisible() and self.search:GetTall() or 0) + self.scr:GetOffset())
local w, h = self:GetSize()
local scroll_width = self.scr.Enabled and self.scr:GetWide() or 0
self.div:SetLeftWidth((w/2) - scroll_width)
self.div:SetSize(w - scroll_width, self:GetHeight())
end
function PANEL:Paint(w, h)
self:GetSkin().tex.CategoryList.Outer(0, 0, w, h)
end
pace.CollapsedProperties = pace.luadata.ReadFile("pac3_editor/collapsed.txt") or {}
function PANEL:AddCollapser(name)
assert(name)
for i,v in ipairs(self.List) do
if v.group == name then
return
end
end
local left = vgui.Create("DButton", self)
left:SetTall(self:GetItemHeight())
left:SetText("")
left.text = name
self.left:AddItem(left)
left.DoClick = function()
pace.CollapsedProperties[name] = not pace.CollapsedProperties[name]
pace.PopulateProperties(pace.current_part)
pace.Editor:InvalidateLayout()
pace.luadata.WriteFile("pac3_editor/collapsed.txt", pace.CollapsedProperties)
end
left.GetValue = function() return name end
local right = vgui.Create("DButton", self)
right:SetTall(self:GetItemHeight())
right:SetText("")
self.right:AddItem(right)
right.DoClick = left.DoClick
left.Paint = function(_, w, h)
--surface.SetDrawColor(left:GetSkin().Colours.Category.Header)
--surface.DrawRect(0,0,w*2,h)
left:GetSkin().tex.CategoryList.Header( 0, 0, w*2, h )
surface.SetFont(pace.CurrentFont)
local txt = L(name)
local _, _h = surface.GetTextSize(txt)
local middle = h/2 - _h/2
--surface.SetTextPos(11, middle)
--surface.SetTextColor(derma.Color("text_dark", self, color_black))
--surface.SetFont(pace.CurrentFont)
--surface.DrawText(txt)
draw.TextShadow({text = txt, font = pace.CurrentFont, pos = {11, middle}, color = left:GetSkin().Colours.Category.Header}, 1, 100)
local txt = (pace.CollapsedProperties[name] and "+" or "-")
local w = surface.GetTextSize(txt)
draw.TextShadow({text = txt, font = pace.CurrentFont, pos = {6-w*0.5, middle}, color = left:GetSkin().Colours.Category.Header}, 1, 100)
end
right.Paint = function(_,w,h)
left:GetSkin().tex.CategoryList.Header(-w,0,w*2,h)
end
table.insert(self.List, {left = left, right = right, panel = var, key = key, group = name})
return #self.List
end
function PANEL:AddKeyValue(key, var, pos, obj, udata, group)
local btn = pace.CreatePanel("properties_label")
btn:SetTall(self:GetItemHeight())
do
local key = key
if key:EndsWith("UID") then
key = key:sub(1, -4)
end
btn:SetValue(L((udata and udata.editor_friendly or key):gsub("%u", " %1"):lower()):Trim())
end
if obj then
btn.key_name = key
btn.part_namepart_name = obj.ClassName
end
local pnl = pace.CreatePanel("properties_container")
pnl:SetTall(self:GetItemHeight())
pnl.right = true
pnl.alt_line = #self.List%2 == 1
btn.alt_line = pnl.alt_line
if ispanel(var) then
pnl:SetContent(var)
end
self.left:AddItem(btn)
self.right:AddItem(pnl)
local pos
if group then
for i, v in ipairs(self.List) do
if v.group == group then
for i = i + 1, #self.List do
local v = self.List[i]
if v.group or not v then
pos = i
break
end
end
end
end
end
if pos then
table.insert(self.left.Items, pos, table.remove(self.left.Items))
table.insert(self.right.Items, pos, table.remove(self.right.Items))
table.insert(self.List, pos, {left = btn, right = pnl, panel = var, key = key})
else
table.insert(self.List, {left = btn, right = pnl, panel = var, key = key})
end
end
function PANEL:Clear()
for key, data in pairs(self.List) do
data.left:Remove()
data.right:Remove()
end
self.left:Clear()
self.right:Clear()
self.List = {}
end
local function FlatListToGroups(list)
local temp = {}
for _, prop in ipairs(list) do
if prop.udata.hidden then continue end
local group = prop.udata.group or "generic"
temp[group] = temp[group] or {}
table.insert(temp[group], prop)
end
return temp
end
local function SortGroups(groups)
local out = {}
local temp = {}
table.Add(temp, pac.GroupOrder[pace.current_part.ClassName] or {})
table.Add(temp, pac.GroupOrder.none)
local done = {}
for i, name in ipairs(temp) do
for group, props in pairs(groups) do
if group == name then
if not done[group] then
table.insert(out, {group = group, props = props})
done[group] = true
end
end
end
end
for group, props in pairs(groups) do
if not done[group] then
table.insert(out, {group = group, props = props})
end
end
return out
end
function PANEL:Populate(flat_list)
self:Clear()
for _, data in ipairs(SortGroups(FlatListToGroups(flat_list))) do
self:AddCollapser(data.group or "generic")
for pos, prop in ipairs(data.props) do
if prop.udata and prop.udata.hide_in_editor then
continue
end
local val = prop.get()
local T = type(val):lower()
if prop.udata and prop.udata.editor_panel then
T = prop.udata.editor_panel or T
elseif pace.PanelExists("properties_" .. prop.key:lower()) then
T = prop.key:lower()
elseif not pace.PanelExists("properties_" .. T) then
T = "string"
end
if pace.CollapsedProperties[prop.udata.group] ~= nil and pace.CollapsedProperties[prop.udata.group] then goto CONTINUE end
local pnl = pace.CreatePanel("properties_" .. T)
if pnl.PostInit then
pnl:PostInit()
end
if prop.udata and prop.udata.description then
pnl:SetTooltip(L(prop.udata.description))
end
local part = pace.current_part
part.pace_properties = part.pace_properties or {}
part.pace_properties[prop.key] = pnl
pnl.part = part
pnl.udata = prop.udata
if prop.udata.enums then
DefineMoreOptionsLeftClick(pnl, function(self)
pace.CreateSearchList(
self,
self.CurrentKey,
L(prop.key),
function(list)
list:AddColumn("enum")
end,
function()
local tbl
if isfunction(prop.udata.enums) then
if pace.current_part:IsValid() then
tbl = prop.udata.enums(pace.current_part)
end
else
tbl = prop.udata.enums
end
local enums = {}
if tbl then
for k, v in pairs(tbl) do
if not isstring(v) then
v = k
end
if not isstring(k) then
k = v
end
enums[k] = v
end
end
return enums
end,
function()
return pace.current_part[prop.key]
end,
function(list, key, val)
return list:AddLine(key)
end,
function(val, key)
return val
end
)
end)
end
if prop.udata.editor_sensitivity or prop.udata.editor_clamp or prop.udata.editor_round then
pnl.LimitValue = function(self, num)
if prop.udata.editor_sensitivity then
self.sens = prop.udata.editor_sensitivity
end
if prop.udata.editor_clamp then
num = math.Clamp(num, unpack(prop.udata.editor_clamp))
end
if prop.udata.editor_round then
num = math.Round(num)
end
return num
end
elseif prop.udata.editor_onchange then
pnl.LimitValue = prop.udata.editor_onchange
end
pnl.CurrentKey = prop.key
if pnl.ExtraPopulate then
table.insert(pace.extra_populates, {pnl = pnl, func = pnl.ExtraPopulate})
pnl:Remove()
goto CONTINUE
end
pnl:SetValue(val)
pnl.OnValueChanged = function(val)
if T == "number" then
val = tonumber(val) or 0
elseif T == "string" then
val = tostring(val)
end
pace.Call("VariableChanged", pace.current_part, prop.key, val)
end
self:AddKeyValue(prop.key, pnl, pos, flat_list, prop.udata)
::CONTINUE::
end
end
end
pace.RegisterPanel(PANEL)
end
do -- non editable string
local DTooltip = _G.DTooltip
if DTooltip and DTooltip.PositionTooltip then
pace_Old_PositionTooltip = pace_Old_PositionTooltip or DTooltip.PositionTooltip
function DTooltip.PositionTooltip(self, ...)
if self.TargetPanel.pac_tooltip_hack then
local args = {pace_Old_PositionTooltip(self, ...)}
if ( not IsValid( self.TargetPanel ) ) then
self:Remove()
return;
end
self:PerformLayout()
local x, y = input.GetCursorPos()
local w, h = self:GetSize()
local lx, ly = self.TargetPanel:LocalToScreen( 0, 0 )
y = math.min( y, ly - h )
self:SetPos( x, y )
return unpack(args)
end
return pace_Old_PositionTooltip(self, ...)
end
end
local PANEL = {}
PANEL.ClassName = "properties_label"
PANEL.Base = "pace_properties_container"
function PANEL:SetValue(str)
local lbl = vgui.Create("DLabel")
lbl:SetTextColor(self.alt_line and self:GetSkin().Colours.Category.AltLine.Text or self:GetSkin().Colours.Category.Line.Text)
lbl:SetFont(pace.CurrentFont)
lbl:SetText(str)
lbl:SetTextInset(10, 0)
lbl:SizeToContents()
lbl.pac_tooltip_hack = true
self.lbl = lbl
self:SetContent(lbl)
if self.part_name and self.key_name then
lbl.OnCursorEntered = function()
if lbl.wiki_info then
lbl:SetTooltip(lbl.wiki_info)
return
end
if not lbl.fetching_wiki then
lbl:SetCursor("waitarrow")
pace.GetPropertyDescription(self.part_name, self.key_name, function(str)
if lbl:IsValid() then
lbl:SetTooltip(str)
ChangeTooltip(lbl)
lbl.wiki_info = str
lbl:SetCursor("arrow")
end
end)
lbl.fetching_wiki = true
end
end
end
end
function PANEL:GetValue()
return self.lbl:GetValue()
end
pace.RegisterPanel(PANEL)
end
do -- base editable
local PANEL = {}
PANEL.ClassName = "properties_base_type"
PANEL.Base = "DLabel"
PANEL.SingleClick = true
function PANEL:OnCursorMoved()
self:SetCursor("hand")
end
function PANEL:OnValueChanged()
end
function PANEL:Init(...)
if DLabel and DLabel.Init then
local status = DLabel.Init(self, ...)
self:SetText('')
self:SetMouseInputEnabled(true)
return status
end
return status
end
function PANEL:PostInit()
if self.MoreOptionsLeftClick then
self:DefineMoreOptionsLeftClick(self.MoreOptionsLeftClick, self.MoreOptionsRightClick)
end
end
function PANEL:DefineMoreOptionsLeftClick(callFuncLeft, callFuncRight)
return DefineMoreOptionsLeftClick(self, callFuncLeft, callFuncRight)
end
function PANEL:SetValue(var, skip_encode)
if self.editing then return end
local value = skip_encode and var or self:Encode(var)
if isnumber(value) then
-- visually round numbers so 0.6 doesn't show up as 0.600000000001231231 on wear
value = math.Round(value, 7)
end
local str = tostring(value)
self:SetTextColor(self.alt_line and self:GetSkin().Colours.Category.AltLine.Text or self:GetSkin().Colours.Category.Line.Text)
self:SetFont(pace.CurrentFont)
self:SetText(" " .. str) -- ugh
self:SizeToContents()
if #str > 10 then
self:SetTooltip(str)
else
self:SetTooltip()
end
self.original_str = str
self.original_var = var
if self.OnValueSet then
self:OnValueSet(var)
end
end
-- kind of a hack
local last_focus = NULL
function PANEL:OnMousePressed(mcode)
if last_focus:IsValid() then
last_focus:Reset()
last_focus = NULL
end
if mcode == MOUSE_LEFT then
--if input.IsKeyDown(KEY_R) then
-- self:Restart()
--else
self.MousePressing = true
if self:MousePress(true) == false then return end
if self.SingleClick or (self.last_press or 0) > RealTime() then
self:EditText()
self:DoubleClick()
self.last_press = 0
last_focus = self
else
self.last_press = RealTime() + 0.2
end
--end
end
if mcode == MOUSE_RIGHT then
local menu = DermaMenu()
menu:SetPos(input.GetCursorPos())
menu:MakePopup()
self:PopulateContextMenu(menu)
end
end
function PANEL:PopulateContextMenu(menu)
menu:AddOption(L"copy", function()
pace.clipboard = pac.CopyValue(self:GetValue())
end):SetImage(pace.MiscIcons.copy)
menu:AddOption(L"paste", function()
self:SetValue(pac.CopyValue(pace.clipboard))
self.OnValueChanged(self:GetValue())
end):SetImage(pace.MiscIcons.paste)
--left right swap available on strings (and parts)
if type(self:GetValue()) == 'string' then
menu:AddSpacer()
menu:AddOption(L"change sides", function()
local var
local part
if self.udata and self.udata.editor_panel == "part" then
part = pac.GetPartFromUniqueID(pac.Hash(pac.LocalPlayer), self:GetValue())
var = part:IsValid() and part:GetName()
else
var = self:GetValue()
end
local var_flip
if string.match(var, "left") != nil then
var_flip = string.gsub(var,"left","right")
elseif string.match(var, "right") != nil then
var_flip = string.gsub(var,"right","left")
end
if self.udata and self.udata.editor_panel == "part" then
local target = pac.FindPartByName(pac.Hash(pac.LocalPlayer), var_flip or var, pace.current_part)
self:SetValue(target or part)
self.OnValueChanged(target or part)
else
self:SetValue(var_flip or var)
self.OnValueChanged(var_flip or var)
end
end):SetImage("icon16/arrow_switch.png")
--numeric sign flip available on numbers
elseif type(self:GetValue()) == 'number' then
menu:AddSpacer()
menu:AddOption(L"flip sign (+/-)", function()
local val = self:GetValue()
self:SetValue(-val)
self.OnValueChanged(self:GetValue())
end):SetImage("icon16/arrow_switch.png")
end
menu:AddSpacer()
menu:AddOption(L"reset", function()
if pace.current_part and pace.current_part.DefaultVars[self.CurrentKey] then
local val = pac.CopyValue(pace.current_part.DefaultVars[self.CurrentKey])
self:SetValue(val)
self.OnValueChanged(val)
end
end):SetImage(pace.MiscIcons.clear)
end
function PANEL:OnMouseReleased()
self:MousePress(false)
self.MousePressing = false
end
function PANEL:IsMouseDown()
if not input.IsMouseDown(MOUSE_LEFT) then
self.MousePressing = false
end
return self.MousePressing
end
function PANEL:DoubleClick()
end
function PANEL:MousePress()
end
function PANEL:Restart()
self:SetValue(self:Decode(""))
self.OnValueChanged(self:Decode(""))
end
function PANEL:EncodeEdit(str)
return str
end
function PANEL:DecodeEdit(str)
return str
end
function PANEL:EditText()
local oldText = self:GetText()
self:SetText("")
local pnl = vgui.Create("DTextEntry")
self.editing = pnl
pnl:SetFont(pace.CurrentFont)
pnl:SetDrawBackground(false)
pnl:SetDrawBorder(false)
pnl:SetText(self:EncodeEdit(self.original_str or ""))
pnl:SetKeyboardInputEnabled(true)
pnl:SetDrawLanguageID(languageID:GetBool())
pnl:RequestFocus()
pnl:SelectAllOnFocus(true)
pnl.OnTextChanged = function() oldText = pnl:GetText() end
local hookID = tostring({})
local textEntry = pnl
local delay = os.clock() + 0.1
pac.AddHook('Think', hookID, function(code)
if not IsValid(self) or not IsValid(textEntry) then return pac.RemoveHook('Think', hookID) end
if textEntry:IsHovered() or self:IsHovered() then return end
if delay > os.clock() then return end
if not input.IsMouseDown(MOUSE_LEFT) and not input.IsKeyDown(KEY_ESCAPE) then return end
pac.RemoveHook('Think', hookID)
self.editing = false
pace.BusyWithProperties = NULL
textEntry:Remove()
self:SetText(oldText)
pnl:OnEnter()
end)
--local x,y = pnl:GetPos()
--pnl:SetPos(x+3,y-4)
--pnl:Dock(FILL)
local x, y = self:LocalToScreen()
local inset_x = self:GetTextInset()
pnl:SetPos(x+5 + inset_x, y)
pnl:SetSize(self:GetSize())
pnl:SetWide(ScrW())
pnl:MakePopup()
pnl.OnEnter = function()
pace.BusyWithProperties = NULL
self.editing = false
pnl:Remove()
self:SetText(tostring(self:Encode(self:DecodeEdit(pnl:GetText() or ""))), true)
self.OnValueChanged(self:Decode(self:GetText()))
end
local old = pnl.Paint
pnl.Paint = function(...)
if not self:IsValid() then pnl:Remove() return end
surface.SetFont(pnl:GetFont())
local w = surface.GetTextSize(pnl:GetText()) + 6
surface.DrawRect(0, 0, w, pnl:GetTall())
surface.SetDrawColor(self:GetSkin().Colours.Properties.Border)
surface.DrawOutlinedRect(0, 0, w, pnl:GetTall())
pnl:SetWide(w)
old(...)
end
pace.BusyWithProperties = pnl
end
pace.BusyWithProperties = NULL
local function click()
if not input.IsMouseDown(MOUSE_LEFT) then return end
local pnl = pace.BusyWithProperties
if pnl and pnl ~= true and pnl:IsValid() then
local x, y = input.GetCursorPos()
local _x, _y = pnl:GetParent():LocalToScreen()
if x < _x or y < _y or x > _x + pnl:GetParent():GetWide() or y > _y + pnl:GetParent():GetTall() then
pnl:OnEnter()
end
end
end
pac.AddHook("GUIMousePressed", "pace_property_text_edit", click)
pac.AddHook("VGUIMousePressed", "pace_property_text_edit", click)
function PANEL:Reset()
if IsValid(self.editing) then
self.editing:OnEnter()
self.editing = false
else
self:SetValue(self.original_var)
self.OnValueChanged(self.original_var)
end
end
function PANEL:GetValue()
return self.original_var
end
function PANEL:Encode(var)
return var
end
function PANEL:Decode(var)
return var
end
function PANEL:PerformLayout()
self:SetSize(self:GetParent():GetSize())
end
pace.RegisterPanel(PANEL)
end
do -- string
local PANEL = {}
PANEL.ClassName = "properties_string"
PANEL.Base = "pace_properties_base_type"
PANEL.SingleClick = true
pace.RegisterPanel(PANEL)
end
do -- vector
local function VECTOR(ctor, type, arg1, arg2, arg3, encode, special_callback, sens)
local PANEL = {}
PANEL.ClassName = "properties_" .. type
PANEL.Base = "pace_properties_container"
PANEL.vector_type = type
function PANEL:Init(...)
self.vector = ctor(0,0,0)
local left = pace.CreatePanel("properties_number", self)
local middle = pace.CreatePanel("properties_number", self)
local right = pace.CreatePanel("properties_number", self)
left.PopulateContextMenu = function(_, menu) self:PopulateContextMenu(menu) end
middle.PopulateContextMenu = function(_, menu) self:PopulateContextMenu(menu) end
right.PopulateContextMenu = function(_, menu) self:PopulateContextMenu(menu) end
if encode then
left.Encode = encode
middle.Encode = encode
right.Encode = encode
end
if sens then
left.sens = sens
middle.sens = sens
right.sens = sens
end
local function on_change(arg1, arg2, arg3)
local restart = 0
return function(num)
self.vector[arg1] = num
if input.IsKeyDown(KEY_R) then
self:Restart()
restart = os.clock() + 0.1
elseif input.IsKeyDown(KEY_LSHIFT) then
middle:SetValue(num)
self.vector[arg2] = num
right:SetValue(num)
self.vector[arg3] = num
end
if restart > os.clock() then
self:Restart()
return
end
self.OnValueChanged(self.vector * 1)
self:InvalidateLayout()
if self.OnValueSet then
self:OnValueSet(self.vector * 1)
end
end
end
left:SetMouseInputEnabled(true)
left.OnValueChanged = on_change(arg1, arg2, arg3)
middle:SetMouseInputEnabled(true)
middle.OnValueChanged = on_change(arg2, arg1, arg3)
right:SetMouseInputEnabled(true)
right.OnValueChanged = on_change(arg3, arg2, arg1)
self.left = left
self.middle = middle
self.right = right
if self.MoreOptionsLeftClick then
local btn = vgui.Create("DButton", self)
btn:SetSize(16, 16)
btn:Dock(RIGHT)
btn:SetText("...")
btn.DoClick = function() self:MoreOptionsLeftClick(self.CurrentKey) end
btn.DoRightClick = self.MoreOptionsRightClick and function() self:MoreOptionsRightClick(self.CurrentKey) end or btn.DoClick
if type == "color" or type == "color2" then
btn:SetText("")
btn.Paint = function(_,w,h)
if type == "color2" then
surface.SetDrawColor(self.vector.x*255, self.vector.y*255, self.vector.z*255, 255)
else
surface.SetDrawColor(self.vector.x, self.vector.y, self.vector.z, 255)
end
surface.DrawRect(0,0,w,h)
surface.SetDrawColor(self:GetSkin().Colours.Properties.Border)
surface.DrawOutlinedRect(0,0,w,h)
end
end
end
self.Paint = function() end
end
PANEL.MoreOptionsLeftClick = special_callback
function PANEL:Restart()
if pace.current_part and pace.current_part.DefaultVars[self.CurrentKey] then
self.vector = pac.CopyValue(pace.current_part.DefaultVars[self.CurrentKey])
else
self.vector = ctor(0,0,0)
end
self.left:SetValue(self.vector[arg1])
self.middle:SetValue(self.vector[arg2])
self.right:SetValue(self.vector[arg3])
self.OnValueChanged(self.vector * 1)
end
function PANEL:PopulateContextMenu(menu)
menu:AddOption(L"copy", function()
pace.clipboard = pac.CopyValue(self.vector)
end):SetImage(pace.MiscIcons.copy)
menu:AddOption(L"paste", function()
local val = pac.CopyValue(pace.clipboard)
if isnumber(val) then
val = ctor(val, val, val)
elseif isvector(val) and type == "angle" then
val = ctor(val.x, val.y, val.z)
elseif isangle(val) and type == "vector" then
val = ctor(val.p, val.y, val.r)
end
if _G.type(val):lower() == type or type == "color" then
self:SetValue(val)
self.OnValueChanged(self.vector * 1)
end
end):SetImage(pace.MiscIcons.paste)
menu:AddSpacer()
menu:AddOption(L"reset", function()
if pace.current_part and pace.current_part.DefaultVars[self.CurrentKey] then
local val = pac.CopyValue(pace.current_part.DefaultVars[self.CurrentKey])
self:SetValue(val)
self.OnValueChanged(val)
end
end):SetImage(pace.MiscIcons.clear)
end
function PANEL:SetValue(vec)
self.vector = vec * 1
self.left:SetValue(math.Round(vec[arg1], 4))
self.middle:SetValue(math.Round(vec[arg2], 4))
self.right:SetValue(math.Round(vec[arg3], 4))
end
function PANEL:PerformLayout()
self.left:SizeToContents()
self.left:SetWide(math.max(self.left:GetWide(), 22))
self.middle:SizeToContents()
self.middle:SetWide(math.max(self.middle:GetWide(), 22))
self.right:SizeToContents()
self.right:SetWide(math.max(self.right:GetWide(), 22))
self.middle:MoveRightOf(self.left, 10)
self.right:MoveRightOf(self.middle, 10)
end
function PANEL:OnValueChanged(vec)
end
pace.RegisterPanel(PANEL)
end
VECTOR(Vector, "vector", "x", "y", "z")
VECTOR(Angle, "angle", "p", "y", "r")
local function tohex(vec, color2)
return color2 and ("#%.2X%.2X%.2X"):format(vec.x * 255, vec.y * 255, vec.z * 255) or ("#%.2X%.2X%.2X"):format(vec.x, vec.y, vec.z)
end
local function fromhex(str)
local r, g, b
if #str <= 4 then -- Supports "#xxx" and "xxx"
r, g, b = str:match("#?(.)(.)(.)")
if r and g and b then
r, g, b = r .. r, g .. g, b .. b
end
elseif #str <= 7 then -- Supports "#xxxxxx" and "xxxxxx"
r, g, b = str:match("#?(..)(..)(..)")
end
if r and g and b then
return Color(tonumber(r, 16) or 255, tonumber(g, 16) or 255, tonumber(b, 16) or 255)
end
end
local function fromColorStr(str)
local r1, g1, b1 = str:match("([0-9]+), *([0-9]+), *([0-9]+)")
local r2, g2, b2 = str:match("([0-9]+) +([0-9]+) +([0-9]+)")
if r1 and g1 and b1 then
return Color(tonumber(r1) or 255, tonumber(g1) or 255, tonumber(b1) or 255)
elseif r2 and g2 and b2 then
return Color(tonumber(r2) or 255, tonumber(g2) or 255, tonumber(b2) or 255)
end
end
local function uncodeValue(valIn)
local fromHex = fromhex(valIn)
local fromShareXColorStr = fromColorStr(valIn)
return fromHex or fromShareXColorStr
end
VECTOR(Vector, "color", "x", "y", "z",
function(self, num) -- this function needs second argument
local pnum = tonumber(num)
if not pnum then
local uncode = uncodeValue(num)
if uncode then
timer.Simple(0, function()
local parent = self:GetParent()
parent.left:SetValue(uncode.r, true)
parent.middle:SetValue(uncode.g, true)
parent.right:SetValue(uncode.b, true)
parent.left.OnValueChanged(uncode.r)
parent.middle.OnValueChanged(uncode.g)
parent.right.OnValueChanged(uncode.b)
end)
return '0'
end
return '0'
end
return tostring(math.Clamp(math.Round(pnum or 0), 0, 255))
end,
function(self)
pace.SafeRemoveSpecialPanel()
local dlibbased = vgui.GetControlTable("DLibColorMixer")
local frm = vgui.Create("DFrame")
frm:SetTitle("Color")
pace.ShowSpecial(frm, self, 300)
if dlibbased then
frm:SetWide(500)
end
local clr = vgui.Create(dlibbased and "DLibColorMixer" or "DColorMixer", frm)
clr:Dock(FILL)
clr:SetAlphaBar(false) -- Alpha isn't needed
clr:SetColor(Color(self.vector.x, self.vector.y, self.vector.z))
local html_color
if not dlibbased then
html_color = vgui.Create("DTextEntry", frm)
html_color:Dock(BOTTOM)
html_color:SetText(tohex(self.vector))
html_color.OnEnter = function()
local valGet = uncodeValue(html_color:GetValue())
if valGet then
clr:SetColor(valGet)
end
end
end
function clr.ValueChanged(_, newColor) -- Only update values when the Color mixer value changes
local vec = Vector(newColor.r, newColor.g, newColor.b)
self.OnValueChanged(vec)
self:SetValue(vec)
if not dlibbased then
html_color:SetText(tohex(vec))
end
end
pace.ActiveSpecialPanel = frm
end,
10
)
VECTOR(Vector, "color2", "x", "y", "z",
function(_, num)
num = tonumber(num) or 0
if input.IsKeyDown(KEY_LCONTROL) then
num = math.Round(num)
end
return tostring(num)
end,
function(self)
pace.SafeRemoveSpecialPanel()
local dlibbased = vgui.GetControlTable("DLibColorMixer")
local frm = vgui.Create("DFrame")
frm:SetTitle("color")
pace.ShowSpecial(frm, self, 300)
if dlibbased then
frm:SetWide(500)
end
local clr = vgui.Create(dlibbased and "DLibColorMixer" or "DColorMixer", frm)
clr:Dock(FILL)
clr:SetAlphaBar(false)
clr:SetColor(Color(self.vector.x * 255, self.vector.y * 255, self.vector.z * 255))
local html_color
if not dlibbased then
html_color = vgui.Create("DTextEntry", frm)
html_color:Dock(BOTTOM)
html_color:SetText(tohex(self.vector, true))
html_color.OnEnter = function()
local col = uncodeValue(html_color:GetValue())
if col then
local vec = col:ToVector()
clr:SetColor(col)
self.OnValueChanged(vec)
self:SetValue(vec)
end
end
end
function clr.ValueChanged(_, newcolor)
local vec = Vector(newcolor.r / 255, newcolor.g / 255, newcolor.b / 255)
self.OnValueChanged(vec)
self:SetValue(vec)
if not dlibbased then
html_color:SetText(tohex(vec, true))
end
end
pace.ActiveSpecialPanel = frm
end,
0.25
)
end
do -- number
local PANEL = {}
PANEL.ClassName = "properties_number"
PANEL.Base = "pace_properties_base_type"
PANEL.sens = 1
PANEL.SingleClick = false
function PANEL:MousePress(bool)
if bool then
self.mousey = gui.MouseY()
self.mousex = gui.MouseX()
self.oldval = tonumber(self:GetValue()) or 0
else
self.mousey = nil
end
end
function PANEL:OnCursorMoved()
self:SetCursor("sizens")
end
function PANEL:SetNumberValue(val)
if self.LimitValue then
val = self:LimitValue(val) or val
end
val = self:Encode(val)
self:SetValue(val)
self.OnValueChanged(tonumber(val))
end
function PANEL:OnMouseWheeled(delta)
if not input.IsKeyDown(KEY_LCONTROL) then delta = delta / 10 end
if input.IsKeyDown(KEY_LALT) then delta = delta / 10 end
local val = self:GetValue() + (delta * self.sens)
self:SetNumberValue(val)
end
function PANEL:Think()
if self:IsMouseDown() then
local sens = self.sens
if input.IsKeyDown(KEY_LALT) then
sens = sens / 10
end
local delta = (self.mousey - gui.MouseY()) / 10
local val = (self.oldval or 0) + (delta * sens)
if input.IsKeyDown(KEY_R) then
if pace.current_part and pace.current_part.DefaultVars[self.CurrentKey] then
val = pace.current_part.DefaultVars[self.CurrentKey]
end
end
self:SetNumberValue(val)
if gui.MouseY()+1 >= ScrH() then
self.mousey = 0
self.oldval = val
input.SetCursorPos(gui.MouseX(), 0)
elseif gui.MouseY() <= 0 then
self.mousey = ScrH()
self.oldval = val
input.SetCursorPos(gui.MouseX(), ScrH())
end
end
end
function PANEL:Encode(num)
if not tonumber(num) then
local ok, res = pac.CompileExpression(num)
if ok then
num = res() or 0
end
end
num = tonumber(num) or 0
if self:IsMouseDown() then
if input.IsKeyDown(KEY_LCONTROL) then
num = math.Round(num)
elseif input.IsKeyDown(KEY_PAD_MINUS) or input.IsKeyDown(KEY_MINUS) then
num = -num
end
if input.IsKeyDown(KEY_LALT) then
num = math.Round(num, 5)
else
num = math.Round(num, 3)
end
end
return num
end
function PANEL:Decode(str)
return tonumber(str) or 0
end
pace.RegisterPanel(PANEL)
end
do -- boolean
local PANEL = {}
PANEL.ClassName = "properties_boolean"
PANEL.Base = "pace_properties_container"
function PANEL:Init()
local chck = vgui.Create("DCheckBox", self)
chck.OnChange = function()
if self.during_change then return end
local b = chck:GetChecked()
self.OnValueChanged(b)
self.lbl:SetText(L(tostring(b)))
end
self.chck = chck
local lbl = vgui.Create("DLabel", self)
lbl:SetFont(pace.CurrentFont)
lbl:SetTextColor(self.alt_line and self:GetSkin().Colours.Category.AltLine.Text or self:GetSkin().Colours.Category.Line.Text)
self.lbl = lbl
end
function PANEL:Paint() end
function PANEL:SetValue(b)
self.during_change = true
self.chck:SetChecked(b)
self.chck:Toggle()
self.chck:Toggle()
self.lbl:SetText(L(tostring(b)))
self.during_change = false
end
function PANEL:OnValueChanged()
end
function PANEL:PerformLayout()
self.BaseClass.PerformLayout(self)
local s = 4
self.chck:SetPos(s*0.5, s*0.5+1)
self.chck:SetSize(self:GetTall()-s, self:GetTall()-s)
self.lbl:MoveRightOf(self.chck, 5)
self.lbl:CenterVertical()
local w,h = self:GetParent():GetSize()
self:SetSize(w-2,h)
end
pace.RegisterPanel(PANEL)
end