Files
wnsrc/lua/pac3/editor/client/panels/tree.lua

594 lines
14 KiB
Lua
Raw Normal View History

2024-08-04 22:55:00 +03:00
--[[
| 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 PANEL = {}
PANEL.ClassName = "tree"
PANEL.Base = "pac_dtree"
function PANEL:Init()
pace.pac_dtree.Init(self)
self:SetLineHeight(18)
self:SetIndentSize(10)
self.parts = {}
self:Populate()
pace.tree = self
end
do
local function get_added_nodes(self)
local added_nodes = {}
for i,v in ipairs(self.added_nodes) do
if v.part and v:IsVisible() and v:IsExpanded() then
table.insert(added_nodes, v)
end
end
table.sort(added_nodes, function(a, b) return select(2, a:LocalToScreen()) < select(2, b:LocalToScreen()) end)
return added_nodes
end
local function scroll_to_node(self, node)
timer.Simple(0.1, function()
if not self:IsValid() then return end
if not node:IsValid() then return end
local _, y = self:LocalToScreen()
local h = self:GetTall()
local _, node_y = node:LocalToScreen()
if node_y > y + h or node_y < y then
self:ScrollToChild(node)
end
end)
end
local info_image = {
pace.MiscIcons.info,
pace.MiscIcons.warning,
pace.MiscIcons.error,
}
function PANEL:Think(...)
if not pace.current_part:IsValid() then return end
if
pace.current_part.pace_tree_node and
pace.current_part.pace_tree_node:IsValid() and not
(
pace.BusyWithProperties:IsValid() or
pace.ActiveSpecialPanel:IsValid() or
pace.editing_viewmodel or
pace.editing_hands or
pace.properties.search:HasFocus()
) and
not gui.IsConsoleVisible()
then
if input.IsKeyDown(KEY_LEFT) then
pace.Call("VariableChanged", pace.current_part, "EditorExpand", false)
elseif input.IsKeyDown(KEY_RIGHT) then
pace.Call("VariableChanged", pace.current_part, "EditorExpand", true)
end
if input.IsKeyDown(KEY_UP) or input.IsKeyDown(KEY_PAGEUP) then
local added_nodes = get_added_nodes(self)
local offset = input.IsKeyDown(KEY_PAGEUP) and 10 or 1
if not self.scrolled_up or self.scrolled_up < os.clock() then
for i,v in ipairs(added_nodes) do
if v == pace.current_part.pace_tree_node then
local node = added_nodes[i - offset] or added_nodes[1]
if node then
node:DoClick()
scroll_to_node(self, node)
break
end
end
end
self.scrolled_up = self.scrolled_up or os.clock() + 0.4
end
else
self.scrolled_up = nil
end
if input.IsKeyDown(KEY_DOWN) or input.IsKeyDown(KEY_PAGEDOWN) then
local added_nodes = get_added_nodes(self)
local offset = input.IsKeyDown(KEY_PAGEDOWN) and 10 or 1
if not self.scrolled_down or self.scrolled_down < os.clock() then
for i,v in ipairs(added_nodes) do
if v == pace.current_part.pace_tree_node then
local node = added_nodes[i + offset] or added_nodes[#added_nodes]
if node then
node:DoClick()
--scroll_to_node(self, node)
break
end
end
end
self.scrolled_down = self.scrolled_down or os.clock() + 0.4
end
else
self.scrolled_down = nil
end
end
for _, part in pairs(pac.GetLocalParts()) do
local node = part.pace_tree_node
if not node or not node:IsValid() then continue end
if node.add_button then
node.add_button:SetVisible(false)
end
if part.Info then
local info = part.Info
node.info:SetTooltip(info.message)
node.info:SetImage(info_image[info.type])
node.info:SetVisible(true)
else
node.info:SetVisible(false)
end
if part.ClassName == "event" then
if part.is_active then
node.Icon:SetImage("icon16/clock_red.png")
else
node.Icon:SetImage(part.Icon)
end
end
if part.ClassName == "custom_animation" then
local anim = part:GetLuaAnimation()
if anim then
node:SetText(part:GetName() .. " [" .. string.format("%.2f", anim.Frame + anim.FrameDelta) .. "]")
end
end
if (part.ClassName == "proxy" or part.ClassName == "event") and part.Name == "" then
node:SetText(pace.pac_show_uniqueid:GetBool() and string.format("%s (%s)", part:GetName(), part:GetPrintUniqueID()) or part:GetName())
end
if part:IsHiddenCached() then
if not node.Icon.event_icon then
local pnl = vgui.Create("DImage", node.Icon)
pnl:SetImage("icon16/clock_red.png")
pnl:SetSize(8, 8)
pnl:SetPos(8, 8)
pnl:SetVisible(false)
node.Icon.event_icon = pnl
end
node.Icon.event_icon:SetVisible(true)
else
if node.Icon.event_icon then
node.Icon.event_icon:SetVisible(false)
end
end
end
local pnl = vgui.GetHoveredPanel() or NULL
if pnl:IsValid() then
local pnl = pnl:GetParent()
if IsValid(pnl) and IsValid(pnl.part) then
pace.Call("HoverPart", pnl.part)
if pnl.add_button then
pnl.add_button:SetVisible(true)
end
end
end
end
end
function PANEL:OnMouseReleased(mc)
if mc == MOUSE_RIGHT then
pace.Call("PartMenu")
end
end
function PANEL:SetModel(path)
if not file.Exists(path, "GAME") then
path = player_manager.TranslatePlayerModel(path)
if not file.Exists(path, "GAME") then
print(path, "is invalid")
return
end
end
local pnl = vgui.Create("SpawnIcon", self)
pnl:SetModel(path or "")
pnl:SetSize(16, 16)
self.Icon:Remove()
self.Icon = pnl
self.ModelPath = path
end
function PANEL:GetModel()
return self.ModelPath
end
local function install_drag(node)
node:SetDraggableName("pac3")
local old = node.OnDrop
function node:OnDrop(child, ...)
-- we're hovering on the label, not the actual node
-- so get the parent node instead
if not child.part then
child = child:GetParent()
end
if child.part and child.part:IsValid() then
if self.part and self.part:IsValid() and self.part:GetParent() ~= child.part then
pace.RecordUndoHistory()
self.part:SetParent(child.part)
pace.RecordUndoHistory()
end
elseif self.part and self.part:IsValid() then
if self.part.ClassName ~= "group" then
pace.RecordUndoHistory()
local group = pac.CreatePart("group", self.part:GetPlayerOwner())
group:SetEditorExpand(true)
self.part:SetParent(group)
pace.RecordUndoHistory()
pace.TrySelectPart()
else
pace.RecordUndoHistory()
self.part:SetParent()
pace.RecordUndoHistory()
pace.RefreshTree(true)
end
end
return old(self, child, ...)
end
function node:DroppedOn(child)
if not child.part then
child = child:GetParent()
end
self:InsertNode(child)
self:SetExpanded(true)
if child.part and child.part:IsValid() then
if self.part and self.part:IsValid() and child.part:GetParent() ~= self.part then
pace.RecordUndoHistory()
child.part:SetParent(self.part)
pace.RecordUndoHistory()
end
end
end
end
local function install_expand(node)
local old = node.SetExpanded
node.SetExpanded = function(self, b, ...)
if self.part and self.part:IsValid() then
self.part:SetEditorExpand(b)
return old(self, b, ...)
end
end
local old = node.Expander.OnMousePressed
node.Expander.OnMousePressed = function(pnl, code, ...)
old(pnl, code, ...)
if code == MOUSE_RIGHT then
local menu = DermaMenu()
menu:SetPos(input.GetCursorPos())
menu:MakePopup()
menu:AddOption(L"collapse all", function()
node.part:CallRecursive('SetEditorExpand', false)
pace.RefreshTree(true)
end):SetImage('icon16/arrow_in.png')
menu:AddOption(L"expand all", function()
node.part:CallRecursive('SetEditorExpand', true)
pace.RefreshTree(true)
end):SetImage('icon16/arrow_down.png')
end
end
end
local fix_folder_funcs = function(tbl)
tbl.MakeFolder = function() end
tbl.FilePopulateCallback = function() end
tbl.FilePopulate = function() end
tbl.PopulateChildren = function() end
tbl.ChildExpanded = function() end
tbl.PopulateChildrenAndSelf = function() end
return tbl
end
local function node_layout(self, ...)
pace.pac_dtree_node.PerformLayout(self, ...)
if self.Label then
self.Label:SetFont(pace.CurrentFont)
--self.Label:SetTextColor(derma.Color("text_dark", self, color_black))
end
if self.add_button then
local x = self.Label:GetPos() + self.Label:GetTextInset() + 4
surface.SetFont(pace.CurrentFont)
local w = surface.GetTextSize(self.Label:GetText())
self.add_button:SetPos(x + w, (self.Label:GetTall() - self.add_button:GetTall()) / 2)
end
if self.info then
local is_adding = self.add_button:IsVisible()
local x = self.Label:GetPos() + self.Label:GetTextInset() + (is_adding and self.add_button:GetWide() + 4 or 4)
surface.SetFont(pace.CurrentFont)
local w = surface.GetTextSize(self.Label:GetText())
self.info:SetPos(x + w, (self.Label:GetTall() - self.info:GetTall()) / 2)
end
end
local function add_parts_menu(node)
pace.Call("AddPartMenu", node.part)
end
-- a hack, because creating a new node button will mess up the layout
function PANEL:AddNode(...)
if self.RootNode then
install_drag(self.RootNode)
end
local node = fix_folder_funcs((self.RootNode and pace.pac_dtree.AddNode or pace.pac_dtree_node.AddNode)(self, ...))
install_expand(node)
install_drag(node)
local add_button = node:Add("DImageButton")
add_button:SetImage(pace.MiscIcons.new)
add_button:SetSize(16, 16)
add_button:SetVisible(false)
add_button.DoClick = function() add_parts_menu(node) pace.Call("PartSelected", node.part) end
add_button.DoRightClick = function() node:DoRightClick() end
node.add_button = add_button
node.SetModel = self.SetModel
node.GetModel = self.GetModel
node.AddNode = PANEL.AddNode
node.PerformLayout = node_layout
local info = node:Add("DImageButton")
info:SetImage(pace.MiscIcons.info)
info:SetSize(16, 16)
info:SetVisible(false)
info.DoClick = function() pace.MessagePrompt(info:GetTooltip()) end
node.info = info
return node
end
local enable_model_icons = CreateClientConVar("pac_editor_model_icons", "1")
function PANEL:PopulateParts(node, parts, children)
fix_folder_funcs(node)
local temp = {}
for k,v in pairs(parts) do
table.insert(temp, v)
end
parts = temp
local tbl = {}
table.sort(parts, function(a,b)
return a and b and a:GetName() < b:GetName()
end)
for key, val in pairs(parts) do
if not val:HasChildren() then
table.insert(tbl, val)
end
end
for key, val in pairs(parts) do
if val:HasChildren() then
table.insert(tbl, val)
end
end
for key, part in pairs(tbl) do
key = part.Id
if not part:GetShowInEditor() then goto CONTINUE end
if not part:HasParent() or children then
local part_node
if IsValid(part.pace_tree_node) then
part_node = part.pace_tree_node
elseif IsValid(self.parts[key]) then
part_node = self.parts[key]
else
part_node = node:AddNode(pace.pac_show_uniqueid:GetBool() and string.format("%s (%s)", part:GetName(), part:GetPrintUniqueID()) or part:GetName())
end
fix_folder_funcs(part_node)
if part.Description then part_node:SetTooltip(L(part.Description)) end
part.pace_tree_node = part_node
part_node.part = part
self.parts[key] = part_node
part_node.DoClick = function()
if not part:IsValid() then return end
pace.Call("PartSelected", part)
--part_node.add_button:SetVisible(true)
return true
end
part_node.DoRightClick = function()
if not part:IsValid() then return end
pace.Call("PartMenu", part)
pace.Call("PartSelected", part)
part_node:InternalDoClick()
return true
end
if
enable_model_icons:GetBool() and
part.is_model_part and
part.GetModel and
part:GetOwner():IsValid()
then
part_node:SetModel(part:GetOwner():GetModel(), part.Icon)
elseif isstring(part.Icon) then
part_node.Icon:SetImage(part.Icon)
end
self:PopulateParts(part_node, part:GetChildren(), true)
if part.newly_created then
part_node:SetSelected(true)
local function expand(part)
if part:HasParent() and part.Parent.pace_tree_node then
part.Parent.pace_tree_node:SetExpanded(true)
expand(part.Parent)
end
end
expand(part)
part_node:SetSelected(true)
part.newly_created = nil
else
part_node:SetSelected(false)
part_node:SetExpanded(part:GetEditorExpand())
end
end
::CONTINUE::
end
end
function PANEL:SelectPart(part)
for key, node in pairs(self.parts) do
if not node.part or not node.part:IsValid() then
node:Remove()
self.parts[key] = nil
else
if node.part == part then
node:SetSelected(true)
else
node:SetSelected(false)
end
end
end
self:InvalidateLayout()
end
function PANEL:Populate(reset)
self:SetLineHeight(18)
self:SetIndentSize(2)
for key, node in pairs(self.parts) do
if reset or (not node.part or not node.part:IsValid()) then
node:Remove()
self.parts[key] = nil
end
end
--[[self.m_pSelectedItem = nil
for key, node in pairs(self:GetItems()) do
node:Remove()
end]]
self:PopulateParts(self, pac.GetLocalParts())
self:InvalidateLayout()
end
pace.RegisterPanel(PANEL)
local function remove_node(part)
if not part:GetShowInEditor() then return end
if (part.pace_tree_node or NULL):IsValid() then
part.pace_tree_node:SetForceShowExpander()
part.pace_tree_node:GetRoot().m_pSelectedItem = nil
part.pace_tree_node:Remove()
pace.RefreshTree()
end
end
pac.AddHook("pac_OnPartRemove", "pace_remove_tree_nodes", remove_node)
local last_refresh = 0
local function refresh(part)
if last_refresh > SysTime() then return end
if not part:GetShowInEditor() then return end
last_refresh = SysTime() + 0.1
timer.Simple(0, function()
if not part:IsValid() then return end
if not part:GetShowInEditor() then return end
pace.RefreshTree(true)
end)
end
pac.AddHook("pac_OnWoreOutfit", "pace_refresh_tree_nodes", refresh)
pac.AddHook("pac_OnPartParent", "pace_refresh_tree_nodes", refresh)
pac.AddHook("pac_OnPartCreated", "pace_refresh_tree_nodes", refresh)
pac.AddHook("pace_OnVariableChanged", "pace_create_tree_nodes", function(part, key, val)
if key == "EditorExpand" then
local node = part.pace_tree_node
if IsValid(node) then
node:SetExpanded(val)
end
end
end)
function pace.RefreshTree(reset)
if pace.tree:IsValid() then
timer.Create("pace_refresh_tree", 0.01, 1, function()
if pace.tree:IsValid() then
pace.tree:Populate(reset)
pace.tree.RootNode:SetExpanded(true, true) -- why do I have to do this?
pace.TrySelectPart()
end
end)
end
end
if Entity(1):IsPlayer() and not PAC_RESTART and not VLL2_FILEDEF then
pace.OpenEditor()
pace.RefreshTree(true)
end