Files
wnsrc/lua/pac3/editor/client/asset_browser.lua

1525 lines
38 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/
--]]
-- based on starfall
CreateClientConVar("pac_asset_browser_close_on_select", "1")
CreateClientConVar("pac_asset_browser_remember_layout", "1")
local function table_tolist(tbl, sort)
local list = {}
for key, val in pairs(tbl) do
table.insert(list, {key = key, val = val})
end
return list
end
local function table_sortedpairs(tbl, sort)
local list = table_tolist(tbl)
table.sort(list, sort)
local i = 0
return function()
i = i + 1
if list[i] then
return list[i].key, list[i].val
end
end
end
local file_Exists
do
local cache = {}
file_Exists = function(path, id)
local key = path .. id
if cache[key] == nil then
cache[key] = file.Exists(path, id)
end
return cache[key]
end
end
local get_material_keyvalues
do
local cache = {}
get_material_keyvalues = function(path)
if cache[path] == nil then
cache[path] = Material(path):GetKeyValues()
end
return cache[path]
end
end
local L = pace.LanguageString
local function install_click(icon, path, pattern, on_menu, pathid)
local old = icon.OnMouseReleased
icon.OnMouseReleased = function(_, code)
if code == MOUSE_LEFT then
pace.model_browser_callback(path, pathid)
elseif code == MOUSE_RIGHT then
local menu = DermaMenu()
menu:AddOption(L"copy path", function()
if pattern then
for _, pattern in ipairs(isstring(pattern) and {pattern} or pattern) do
local test = path:match(pattern)
if test then
path = test
break
end
end
end
SetClipboardText(path)
end)
if on_menu then on_menu(menu) end
menu:Open()
end
return old(_, code)
end
end
local function get_unlit_mat(path)
if path:find("%.png$") then
return Material(path:match("materials/(.+)"))
elseif path:find("%.vmt$") then
local tex = Material(path:match("materials/(.+)%.vmt")):GetTexture("$basetexture")
if tex then
local mat = CreateMaterial(path .. "_pac_asset_browser", "UnlitGeneric")
mat:SetTexture("$basetexture", tex)
return mat
end
end
return CreateMaterial(path .. "_pac_asset_browser", "UnlitGeneric", {["$basetexture"] = path:match("materials/(.+)%.vtf")})
end
local next_generate_icon = 0
local max_generating = 5
local function setup_paint(panel, generate_cb, draw_cb)
local old = panel.Paint
panel.Paint = function(self,w,h)
if not self.ready_to_draw then return end
if not self.setup_material then
generate_cb(self)
self.setup_material = true
end
draw_cb(self, w, h)
end
local old = panel.OnRemove
panel.OnRemove = function(...)
next_generate_icon = math.max(next_generate_icon - 1, 0)
if old then
old(...)
end
end
end
local function create_texture_icon(path, pathid)
local icon = vgui.Create("DButton")
icon:SetTooltip(path)
icon:SetSize(128,128)
icon:SetWrap(true)
icon:SetText("")
install_click(icon, path, {"^materials/(.+)%.vtf$", "^materials/(.+%.png)$"}, nil, pathid)
setup_paint(
icon,
function(self)
self.mat = get_unlit_mat(path)
self.realwidth = self.mat:Width()
self.realheight = self.mat:Height()
end,
function(self, W, H)
if self.mat then
local w = math.min(W, self.realwidth)
local h = math.min(H, self.realheight)
surface.SetDrawColor(255,255,255,255)
surface.SetMaterial(self.mat)
surface.DrawTexturedRect(W/2 - w/2, H/2 - h/2, w, h)
end
end
)
return icon
end
surface.CreateFont("pace_asset_browser_fixed_width", {
font = "dejavu sans mono",
})
local bad_materials = {}
local function create_material_icon(path, grid_panel)
if #pace.model_browser_browse_types_tbl == 1 then
if bad_materials[path] ~= nil then
local str = file.Read(path, "GAME")
if str then
local shader = str:match("^(.-){"):Trim():gsub("%p", ""):lower()
if not (shader == "vertexlitgeneric" or shader == "unlitgeneric" or shader == "eyerefract" or shader == "refract") then
bad_materials[path] = true
else
bad_materials[path] = false
end
end
end
if bad_materials[path] then
return
end
end
local mat_path = path:match("materials/(.+)%.vmt")
local icon = vgui.Create("DButton")
icon:SetTooltip(path)
icon:SetSize(128,128)
icon:SetWrap(true)
icon:SetText("")
setup_paint(
icon,
function(self)
self:SetupMaterial()
end,
function(self, w, h)
surface.SetDrawColor(0,0,0,240)
surface.DrawRect(0,0,w,h)
end
)
function icon:SetupMaterial()
local mat = Material(mat_path)
local shader = mat:GetShader():lower()
if shader == "vertexlitgeneric" then
local pnl = vgui.Create("DModelPanel", icon)
pnl:SetMouseInputEnabled(false)
pnl:Dock(FILL)
pnl:SetLookAt( Vector( 0, 0, 0 ) )
pnl:SetFOV(1)
pnl:SetModel("models/hunter/plates/plate1x1.mdl")
pnl:SetCamPos(Vector(1,0,1) * 1825)
pnl.mouseover = false
local m = Matrix()
m:Scale(Vector(1.37,0.99,0.01))
pnl.Entity:EnableMatrix("RenderMultiply", m)
--[[
local old = icon.OnCursorEntered
function icon:OnCursorEntered(...)
if pace.current_part:IsValid() and pace.current_part.Materialm then
pace.asset_browser_old_mat = pace.asset_browser_old_mat or pace.current_part.Materialm
pace.current_part.Materialm = mat
end
old(self, ...)
end
local old = icon.OnCursorExited
function icon:OnCursorExited(...)
if pace.current_part:IsValid() and pace.current_part.Materialm then
pace.current_part.Materialm = pace.asset_browser_old_mat
end
old(self, ...)
end
]]
local unlit_mat = get_unlit_mat(path)
function pnl:Paint( w, h )
local x, y = self:ScreenToLocal(gui.MouseX(), gui.MouseY())
if (x > w*8 or y > h*8) or (x < -w*4 or y < -h*4) then
surface.SetDrawColor(255,255,255,255)
surface.SetMaterial(unlit_mat)
surface.DrawTexturedRect(0,0,w,h)
return
end
x = x / w
y = y / h
x = x * 50
y = y * 50
x = x - 25
y = y - 55
local light_pos = Vector(y, x, 30)
local pos_x, pos_y = self:LocalToScreen( 0, 0 )
cam.Start3D( self.vCamPos, Angle(45, 180, 0), self.fFOV, pos_x, pos_y, w, h, 5, self.FarZ )
render.SuppressEngineLighting( true )
render.SetColorModulation( 1, 1, 1 )
render.SetBlend(1)
render.SetLocalModelLights({{
color = Vector(1,1,1),
pos = self.Entity:GetPos() + light_pos,
}})
self:DrawModel()
render.SuppressEngineLighting( false )
cam.End3D()
end
pnl.PreDrawModel = function() render.ModelMaterialOverride(mat) end
pnl.PostDrawModel = function() render.ModelMaterialOverride() end
elseif shader == "lightmappedgeneric" or shader == "spritecard" then
local pnl = vgui.Create("DPanel", icon)
pnl:SetMouseInputEnabled(false)
pnl:Dock(FILL)
local mat = get_unlit_mat(path)
pnl.Paint = function(self,w,h)
surface.SetDrawColor(255,255,255,255)
surface.SetMaterial(mat)
surface.DrawTexturedRect(0,0,w,h)
end
else
local pnl = vgui.Create("DImage", icon)
pnl:SetMouseInputEnabled(false)
pnl:Dock(FILL)
pnl:SetImage(mat_path)
end
end
install_click(icon, path, "^materials/(.+)%.vmt$", function(menu)
local function create_text_view(str)
local frame = vgui.Create("DFrame")
frame:SetTitle(path)
frame:SetSize(500, 500)
frame:Center()
frame:SetSizable(true)
local scroll = vgui.Create("DScrollPanel", frame)
scroll:Dock(FILL)
scroll:DockMargin( 0, 5, 5, 5 )
local text = vgui.Create("DTextEntry", scroll)
text:SetMultiline(true)
text:SetFont("pace_asset_browser_fixed_width")
text:SetText(str)
surface.SetFont(text:GetFont())
local _,h = surface.GetTextSize(str)
text:SetTall(h+50)
text:SetWide(frame:GetWide())
frame:MakePopup()
end
menu:AddOption("view .vmt", function()
create_text_view(file.Read(path, "GAME"):gsub("\t", " "))
end)
menu:AddOption("view keyvalues", function()
local tbl = {}
for k,v in pairs(get_material_keyvalues(mat_path)) do
table.insert(tbl, {k = k, v = v})
end
table.sort(tbl, function(a,b) return a.k < b.k end)
local str = ""
for _, v in ipairs(tbl) do
str = str .. v.k:sub(2) .. ":\n" .. tostring(v.v) .. "\n\n"
end
create_text_view(str)
end)
end)
grid_panel:Add(icon)
return icon
end
local function create_model_icon(path, pathid)
local icon = vgui.Create("SpawnIcon")
icon:SetSize(64, 64)
icon:SetModel(path)
icon:SetTooltip(path)
if path:StartWith("addons/") then
path = path:match("^addons/.-/(.+)") or path
end
install_click(icon, path, nil, nil, pathid)
return icon
end
local function update_title(info)
if info then
info = " - " .. info
pace.model_browser:SetTitle(pace.model_browser.title .. info)
else
pace.model_browser:SetTitle(pace.model_browser.title)
end
end
do
local PANEL = {}
local BaseClass = baseclass.Get( "DPanel" )
function PANEL:Init()
self.zoom = pace.model_browser:GetCookieNumber("zoom", 100)
self.ContentContainers = {}
self:SetWide(102)
self:SetPaintBackground(false)
self.ZoomOut = vgui.Create("DButton", self)
self.ZoomOut:Dock(RIGHT)
self.ZoomOut:DockMargin(2, 2, 2, 2)
self.ZoomOut:SetText("-")
self.ZoomOut:SetWide(20)
self.ZoomOut.DoClick = function()
self:SetZoom(self.zoom - 10)
end
self.ZoomText = vgui.Create("DTextEntry", self)
self.ZoomText:Dock(RIGHT)
self.ZoomText:DockMargin(2, 2, 2, 2)
self.ZoomText:SetWide(50)
self:SetZoomText(self.zoom)
self.ZoomText.OnValueChange = function(value)
local new_zoom = tonumber(value:GetText())
if new_zoom then
self:SetZoom(new_zoom)
else
self:SetZoomText(self.zoom)
end
end
self.ZoomIn = vgui.Create("DButton", self)
self.ZoomIn:Dock(RIGHT)
self.ZoomIn:DockMargin(2, 2, 2, 2)
self.ZoomIn:SetText("+")
self.ZoomIn:SetWide(20)
self.ZoomIn.DoClick = function()
self:SetZoom(self.zoom + 10)
end
end
function PANEL:AddContentContainer(pnl)
if not table.HasValue(self.ContentContainers, pnl) then
table.insert(self.ContentContainers, pnl)
end
end
function PANEL:VisibilityCheck()
local zoomUsable = false
for i,v in ipairs(self.ContentContainers) do
if v and v:IsVisible() then
zoomUsable = true
break
end
end
self:SetVisible(zoomUsable)
end
function PANEL:SetZoom(num)
self.zoom = math.Clamp(num, 16, 512)
pace.model_browser:SetCookie("zoom", self.zoom)
self:SetZoomText(self.zoom)
local toDelete = {}
for i,v in ipairs(self.ContentContainers) do
if not (v and v:IsValid()) then
table.insert(toDelete, i)
elseif v:IsVisible() then
v:CalcZoom()
end
end
// Clean up any panels that are invalid for what ever reason
for i,v in ipairs(toDelete) do
table.remove(self.ContentContainers, v)
end
end
function PANEL:SetZoomText(num)
self.ZoomText:SetText(math.Round(num, 1) .. "%")
end
vgui.Register( "pac_AssetBrowser_ZoomControls", PANEL, "DPanel" )
end
do
local PANEL = {}
local BaseClass = baseclass.Get( "DScrollPanel" )
function PANEL:Init()
self:SetPaintBackground( false )
self.IconList = vgui.Create( "DPanel", self:GetCanvas())
self.IconList:Dock( TOP )
function self.IconList:PerformLayout()
if not self.invalidate then return end
self.invalidate = nil
local x, y = 0, 0
local max_width = self:GetWide()
local height = 0
local total_width
for _, child in ipairs(self:GetChildren()) do
height = math.max(height, child:GetTall())
if x + child:GetWide() > max_width then
total_width = x - max_width
x = 0
y = y + height
height = 0
end
child:SetPos(x, y)
x = x + child:GetWide()
end
if total_width then
for _, child in ipairs(self:GetChildren()) do
local x, y = child:GetPos()
child:SetPos(x - total_width/2, y)
end
end
self:SetTall(y + height)
end
end
function PANEL:Add(pnl)
pnl.ready_to_draw = true
pnl.original_size = {w=pnl:GetWide(),h=pnl:GetTall()}
if self.ZoomControls then
pnl:SetSize(pnl.original_size.w * self.ZoomControls.zoom * 0.01, pnl.original_size.h * self.ZoomControls.zoom * 0.01)
end
self.IconList:Add(pnl)
self.IconList.invalidate = true
end
function PANEL:SetZoomControls(pnl)
self.ZoomControls = pnl
pnl:AddContentContainer(self)
end
function PANEL:CalcZoom()
if self.ZoomControls then
for i,v in ipairs(self.IconList:GetChildren()) do
v:SetSize(v.original_size.w * self.ZoomControls.zoom * 0.01, v.original_size.h * self.ZoomControls.zoom * 0.01)
end
self.IconList.invalidate = true
end
end
function PANEL:OnMouseWheeled(delta)
if input.IsControlDown() and self.ZoomControls then
self.ZoomControls:SetZoom(self.ZoomControls.zoom + delta * 4)
return
end
return BaseClass.OnMouseWheeled(self, delta)
end
function PANEL:Clear()
for k,v in ipairs(self.IconList:GetChildren()) do
v:Remove()
end
end
vgui.Register( "pac_AssetBrowser_ContentContainer", PANEL, "DScrollPanel" )
end
function pace.AssetBrowser(callback, browse_types_str, part_key)
browse_types_str = browse_types_str or "models;materials;textures;sound"
local browse_types = browse_types_str:Split(";")
if not pac.asset_browser_cache then
if file.Exists("pac3_cache/pac_asset_browser_index.txt", "DATA") then
pac.asset_browser_cache = util.JSONToTable(file.Read("pac3_cache/pac_asset_browser_index.txt", "DATA")) or {}
else
pac.asset_browser_cache = {}
end
end
local texture_view = false
local material_view = table.HasValue(browse_types, "materials")
local sound_view = table.HasValue(browse_types, "sound")
if table.RemoveByValue(browse_types, "textures") then
texture_view = true
if not material_view then
table.insert(browse_types, "materials")
end
end
if pace.model_browser_browse_types ~= browse_types_str and pace.model_browser and pace.model_browser:IsValid() then
pace.model_browser:Remove()
end
local addModel
pace.model_browser_callback = function(...)
callback = callback or print
if callback(...) == false then return end
if GetConVar("pac_asset_browser_close_on_select"):GetBool() then
pace.model_browser:SetVisible(false)
end
end
pace.model_browser_browse_types = browse_types_str
pace.model_browser_browse_types_tbl = browse_types
pace.model_browser_part_key = part_key
if pace.model_browser and pace.model_browser:IsValid() then
pace.model_browser:SetVisible(true)
pace.model_browser:MakePopup()
return
end
local divider
local frame = vgui.Create("DFrame")
frame.title = L"asset browser" .. " - " .. (browse_types_str:gsub(";", " "))
if GetConVar("pac_asset_browser_remember_layout"):GetBool() then
frame:SetCookieName("pac_asset_browser")
end
local x = frame:GetCookieNumber("x", ScrW() - ScrW()/2.75)
local y = frame:GetCookieNumber("y", 0)
local w = frame:GetCookieNumber("w", ScrW()/2.75)
local h = frame:GetCookieNumber("h", ScrH())
x = math.Clamp(x, 0, ScrW())
y = math.Clamp(y, 0, ScrH())
w = math.Clamp(w, 50, ScrW())
h = math.Clamp(h, 50, ScrH())
frame:SetPos(x, y)
frame:SetSize(w, h)
frame:SetDeleteOnClose(false)
frame:SetSizable(true)
local last_x
local last_y
local last_w
local last_h
local div_x
local last_div_x
local old_think = frame.Think
frame.Think = function(...)
local x,y = frame:GetPos()
local w,h = frame:GetSize()
div_x = divider:GetLeftWidth()
if x ~= last_x then frame:SetCookie("x", x) last_x = x end
if y ~= last_y then frame:SetCookie("y", y) last_y = y end
if w ~= last_w then frame:SetCookie("w", w) last_w = w end
if h ~= last_h then frame:SetCookie("h", h) last_h = h end
if div_x ~= last_div_x then frame:SetCookie("div", div_x) last_div_x = div_x end
return old_think(...)
end
pace.model_browser = frame
update_title()
function frame:OnClose()
self:SetVisible(false)
end
local menu_bar = vgui.Create("DMenuBar", frame)
menu_bar:Dock(TOP)
local file_menu = menu_bar:AddMenu(L"file")
file_menu:AddOption(L"clear search cache", function()
Derma_Query(
L"Are you sure you want to clear? A good time to clear is when there is a big TF2 update or you've decided to permanently unmount some games to avoid them showing up in the search results.",
L"clear search cache",
L"clear", function()
file.Delete("pac3_cache/pac_asset_browser_index.txt")
pac.asset_browser_cache = {}
end,
L"cancel", function()
end
)
end):SetImage(pace.MiscIcons.clear)
local options_menu = menu_bar:AddMenu(L"options")
options_menu:SetDeleteSelf(false)
options_menu:AddCVar(L"close browser on select", "pac_asset_browser_close_on_select", "1", "0")
options_menu:AddCVar(L"remember layout", "pac_asset_browser_remember_layout", "1", "0")
local zoom_controls = vgui.Create("pac_AssetBrowser_ZoomControls", menu_bar)
zoom_controls:Dock(RIGHT)
--[[
local tool_bar = vgui.Create("DPanel", frame)
tool_bar:Dock(TOP)
local browse_back = vgui.Create("DImageButton", tool_bar)
browse_back:SetImage("icon16/arrow_left.png")
browse_back:SizeToContents()
browse_back.DoClick = function()
end
]]
local left_panel = vgui.Create("DPanel", frame)
left_panel:Dock(LEFT)
left_panel:SetSize(190, 10)
left_panel:DockMargin(0, 0, 4, 0)
left_panel.Paint = function () end
local tree = vgui.Create("DTree", left_panel)
tree:Dock(FILL)
tree:DockMargin(0, 0, 0, 0)
tree:SetBackgroundColor(Color(240, 240, 240))
frame.tree = tree
tree.OnNodeSelected = function (self, node)
if not IsValid(node.propPanel) then return end
if IsValid(frame.PropPanel.selected) then
frame.PropPanel.selected:SetVisible(false)
frame.PropPanel.selected = nil
end
frame.PropPanel.selected = node.propPanel
frame.dir = node.dir
frame.pathid = node.pathid or node.GetPathID and node:GetPathID() or "GAME"
frame.PropPanel.selected:Dock(FILL)
frame.PropPanel.selected:SetVisible(true)
zoom_controls:VisibilityCheck()
divider:SetRight(frame.PropPanel.selected)
if node.dir then
local pathid = frame.pathid or "GAME"
if pathid == "GAME" then pathid = "all" end
update_title("browsing " .. pathid .. "/" .. node.dir .. "/*")
else
update_title()
end
end
--local root_node = tree:AddNode("content", "icon16/folder_database.png")
--root_node:SetExpanded(true)
local root_node = tree
frame.PropPanel = vgui.Create("DPanel", frame)
frame.PropPanel:Dock(FILL)
function frame.PropPanel:Paint (w, h)
draw.RoundedBox(0, 0, 0, w, h, Color(240, 240, 240))
end
divider = vgui.Create("DHorizontalDivider", frame)
divider:Dock(FILL)
divider:SetLeftWidth(frame:GetCookieNumber("div", 140))
divider:SetLeftMin(0)
divider:SetRightMin(0)
divider:SetLeft(left_panel)
divider:SetRight(frame.PropPanel)
local sound_name_list = vgui.Create("DListView", frame.PropPanel)
sound_name_list:AddColumn(L"name")
sound_name_list:Dock(FILL)
sound_name_list:SetMultiSelect(false)
sound_name_list:SetVisible(false)
local sound_list = vgui.Create("DListView", frame.PropPanel)
sound_list:AddColumn(L"path")
sound_list:AddColumn(L"byte size")
sound_list:Dock(FILL)
sound_list:SetMultiSelect(false)
sound_list:SetVisible(false)
local function AddGeneric(self, sound, ...)
local line = self:AddLine(sound, ...)
local play = vgui.Create("DImageButton", line)
play:SetImage("icon16/control_play.png")
play:SizeToContents()
play:Dock(LEFT)
function play.Start()
for _, v in pairs(self:GetLines()) do
v.play:Stop()
end
play:SetImage("icon16/control_stop.png")
local snd = CreateSound(pac.LocalPlayer, sound)
snd:Play()
pace.asset_browser_snd = snd
timer.Create("pac_asset_browser_play", SoundDuration(sound), 1, function()
if play:IsValid() then
play:Stop()
end
end)
end
function play.Stop()
play:SetImage("icon16/control_play.png")
if pace.asset_browser_snd then
pace.asset_browser_snd:Stop()
timer.Remove("pac_asset_browser_play")
end
end
line.OnMousePressed = function(_, code)
self:ClearSelection()
self:SelectItem(line)
if code == MOUSE_RIGHT then
play:Start()
else
pace.model_browser_callback(sound, "GAME")
end
end
local label = line.Columns[1]
label:SetTextInset(play:GetWide() + 5, 0)
play.DoClick = function()
if timer.Exists("pac_asset_browser_play") and self:GetLines()[self:GetSelectedLine()] == line then
play:Stop()
return
end
self:ClearSelection()
self:SelectItem(line)
play:Start()
end
line.play = play
end
function sound_name_list:AddSound(name)
AddGeneric(self, name)
end
function sound_list:AddSound(path, pathid)
local sound_path = path:match("sound/(.+)")
AddGeneric(self, sound_path, file.Size(path, pathid))
end
local select_me
if texture_view or material_view then
local node = root_node:AddNode("materials", "icon16/folder_database.png")
node.dir = "materials"
local viewPanel = vgui.Create("pac_AssetBrowser_ContentContainer", frame.PropPanel)
viewPanel:DockMargin(5, 0, 0, 0)
viewPanel:SetVisible(false)
viewPanel:SetZoomControls(zoom_controls)
node.propPanel = viewPanel
for list_name, materials in pairs(pace.Materials) do
local list = node:AddNode(list_name)
list.dir = "materials"
list.propPanel = viewPanel
list.OnNodeSelected = function()
if viewPanel and viewPanel.currentNode and viewPanel.currentNode == list then return end
viewPanel:Clear(true)
viewPanel.currentNode = list
if material_view then
for _, material_name in ipairs(materials) do
local path = "materials/" .. material_name .. ".vmt"
if file_Exists(path, "GAME") then
create_material_icon(path, viewPanel)
end
end
end
if texture_view then
local done = {}
local textures = {}
for _, material_name in ipairs(materials) do
for k, v in pairs(get_material_keyvalues(material_name)) do
if type(v) == "ITexture" then
local name = v:GetName()
if not done[name] then
done[name] = true
table.insert(textures, "materials/" .. name .. ".vtf")
end
end
end
end
for _, path in ipairs(textures) do
viewPanel:Add(create_texture_icon(path))
end
end
tree:OnNodeSelected(list)
viewPanel.currentNode = list
end
if #browse_types == 1 and list_name == "materials" and (texture_view or material_view) then
select_me = list
end
end
end
if table.HasValue(browse_types, "models") then
local spawnlists = root_node:AddNode("spawnlists")
spawnlists.info = {}
spawnlists.info.id = 0
spawnlists.dir = "models"
local has_game = {}
has_game[""] = true
for k, v in pairs(engine.GetGames()) do
if v.mounted then
has_game[v.folder] = true
end
end
local function fillNavBar(propTable, parentNode)
for k, v in table_sortedpairs(propTable, function(a, b) return a.key < b.key end) do
if v.parentid == parentNode.info.id and has_game[v.needsapp] then
local node = parentNode:AddNode(v.name, v.icon)
node:SetExpanded(true)
node.info = v
node.dir = "models"
node.propPanel = vgui.Create(vgui.GetControlTable("ContentContainer") and "ContentContainer" or "pac_AssetBrowser_ContentContainer", frame.PropPanel)
node.propPanel:DockMargin(5, 0, 0, 0)
node.propPanel:SetVisible(false)
parentNode.propPanel = node.propPanel
node.OnNodeSelected = function()
if not node.setup then
node.setup = true
for i, object in table_sortedpairs(v.contents, function(a, b) return a.key < b.key end) do
if object.type == "model" then
node.propPanel:Add(create_model_icon(object.model))
elseif object.type == "header" then
if not object.text or not isstring(object.text) then return end
local label = vgui.Create("ContentHeader", node.propPanel)
label:SetText(object.text)
node.propPanel:Add(label)
end
end
end
tree:OnNodeSelected(node)
end
if #browse_types == 1 and v.name == "Construction Props" then
select_me = node
end
fillNavBar(propTable, node)
end
end
end
fillNavBar(spawnmenu.GetPropTable(), spawnlists)
end
if sound_view then
local node = root_node:AddNode("game sounds", "icon16/sound.png")
node.dir = "sound names"
node.propPanel = sound_name_list
node.OnNodeSelected = function()
local categories = {}
for _, sound_name in ipairs(sound.GetTable()) do
local category = sound_name:match("^(.-)%.") or sound_name:match("^(.-)_") or sound_name:match("^(.-)%u")
if not category or category == nil then category = "misc" end
categories[category] = categories[category] or {}
table.insert(categories[category], sound_name)
end
local sorted = {}
for name, sounds in pairs(categories) do
table.sort(sounds, function(a, b) return a < b end)
table.insert(sorted, {name = name, sounds = sounds})
end
table.sort(sorted, function(a, b) return a.name < b.name end)
for _, data in ipairs(sorted) do
local category_name, sounds = data.name, data.sounds
local node = node:AddNode(category_name, "icon16/sound.png")
node.dir = "sound names"
node.propPanel = sound_name_list
node.OnNodeSelected = function()
sound_name_list:Clear()
for _, sound_name in ipairs(sounds) do
sound_name_list:AddSound(sound_name)
end
tree:OnNodeSelected(node)
end
end
end
end
do -- mounted
local function addBrowseContent(viewPanel, node, name, icon, path, pathid)
local function on_select(self, node)
if viewPanel and viewPanel.currentNode and viewPanel.currentNode == node then return end
node.dir = self.dir
sound_list:Clear()
viewPanel:Clear(true)
viewPanel.currentNode = node
local searchString = node:GetFolder()
if searchString == "" and #browse_types == 1 then
local count = 0
local function find_recursive(path, pathid)
if count >= 500 then return end
local files_, folders_ = file.Find(path .. "/*", pathid)
if files_ then
for i,v in ipairs(files_) do
count = count + 1
local path = path .. "/" .. v
path = path:gsub("^.-(" .. browse_types[1] .. "/.+)$", "%1")
if browse_types[1] == "models" then
if not IsUselessModel(path) then
viewPanel:Add(create_model_icon(path, pathid))
end
elseif browse_types[1] == "materials" then
if path:find("%.vmt$") then
if material_view then
create_material_icon(path, viewPanel)
end
elseif texture_view then
viewPanel:Add(create_texture_icon(path, pathid))
end
elseif browse_types[1] == "sound" then
sound_list:AddSound(path, pathid)
end
end
for i,v in ipairs(folders_) do
find_recursive(path .. "/" .. v, pathid)
end
end
end
find_recursive(path .. browse_types[1], node:GetPathID())
else
local files, folders = file.Find(searchString .. "/*", node:GetPathID())
if files then
--[[
for _, dir in pairs(folders) do
local SPAWNICON = vgui.GetControlTable("SpawnIcon")
local icon = vgui.Create("DButton", viewPanel)
icon:SetSize(64,64)
icon:SetText(dir)
icon:SetWrap(true)
icon.Paint = SPAWNICON.Paint
icon.PaintOver = SPAWNICON.PaintOver
icon.DoClick = function()
for _, child in pairs(node.ChildNodes:GetChildren()) do
if child:GetFolder() == ((node:GetFolder() == "" and dir) or (node:GetFolder() .. "/" .. dir)) then
prev_node = tree.Tree:GetSelectedItem()
tree.Tree:SetSelectedItem(child)
node:SetExpanded(true)
break
end
end
end
viewPanel:Add(icon)
end
]]
if self.dir == "models" then
for k, v in pairs(files) do
local path = node:GetFolder() .. "/" .. v
if not path:StartWith("models/pac3_cache/") then
if not IsUselessModel(path) then
viewPanel:Add(create_model_icon(path, pathid))
end
end
end
elseif self.dir == "materials" then
for k, v in pairs(files) do
local path = node:GetFolder() .. "/" .. v
if v:find("%.vmt$") then
if material_view then
create_material_icon(path, viewPanel)
end
elseif texture_view then
viewPanel:Add(create_texture_icon(path, pathid))
end
end
elseif self.dir == "sound" then
for k, v in pairs(files) do
local path = node:GetFolder() .. "/" .. v
sound_list:AddSound(path, pathid)
end
end
end
end
if self.dir == "sound" then
node.propPanel = sound_list
else
node.propPanel = viewPanel
end
tree:OnNodeSelected(node)
viewPanel.currentNode = node
end
node = node:AddNode(name, icon)
node:SetFolder("")
node:SetPathID(pathid)
node.viewPanel = viewPanel
for _, dir in ipairs(browse_types) do
local files, folders = file.Find(path .. dir .. "/*", pathid)
if files and (files[1] or folders[1]) then
local parent = node
local node = node:AddFolder(dir, path .. dir, pathid, false)
node.dir = dir
node.OnNodeSelected = on_select
if not select_me and #browse_types == 1 and name == "all" and browse_types[1] == dir and dir ~= "models" then
select_me = node
end
if not select_me and #browse_types == 3 and name == "all" and dir == "materials" then
select_me = node
end
end
end
node.OnNodeSelected = on_select
end
local viewPanel = vgui.Create("pac_AssetBrowser_ContentContainer", frame.PropPanel)
viewPanel:DockMargin(5, 0, 0, 0)
viewPanel:SetVisible(false)
viewPanel:SetZoomControls(zoom_controls)
do
local special = {
{
title = "all",
folder = "GAME",
icon = "games/16/all.png",
},
{
title = "downloaded",
folder = "DOWNLOAD",
icon = "materials/icon16/server_go.png",
},
{
title = "workshop",
folder = "WORKSHOP",
icon = "materials/icon16/plugin.png",
},
{
title = "thirdparty",
folder = "THIRDPARTY",
icon = "materials/icon16/folder_brick.png",
},
{
title = "mod",
folder = "MOD",
icon = "materials/icon16/folder_brick.png",
},
}
for _, info in ipairs(special) do
addBrowseContent(viewPanel, root_node, info.title, info.icon, "", info.folder)
end
end
do
local games = engine.GetGames()
table.insert(games, {
title = "Garry's Mod",
folder = "garrysmod",
mounted = true
})
for _, game in table_sortedpairs(games, function(a, b) return a.val.title < b.val.title end) do
if game.mounted then
addBrowseContent(viewPanel, root_node, game.title, "games/16/" .. (game.icon or game.folder) .. ".png", "", game.folder)
end
end
end
local node = root_node:AddNode("addons")
for _, addon in table_sortedpairs(engine.GetAddons(), function(a, b) return a.val.title < b.val.title end) do
if addon.file:StartWith("addons/") then
local _, dirs = file.Find("*", addon.title)
if
table.HasValue(dirs, "materials") or
table.HasValue(dirs, "models") or
table.HasValue(dirs, "sound")
then
addBrowseContent(viewPanel, node, addon.title, "icon16/bricks.png", "", addon.title)
end
end
end
local _, folders = file.Find("addons/*", "MOD")
for _, path in ipairs(folders) do
if
file.IsDir("addons/" .. path .. "/materials", "MOD") or
file.IsDir("addons/" .. path .. "/sound", "MOD") or
file.IsDir("addons/" .. path .. "/models", "MOD")
then
addBrowseContent(viewPanel, node, path, "icon16/folder.png", "addons/" .. path .. "/", "MOD")
end
end
end
local model_view = vgui.Create("pac_AssetBrowser_ContentContainer", frame.PropPanel)
model_view:DockMargin(5, 0, 0, 0)
model_view:SetVisible(false)
model_view:SetZoomControls(zoom_controls)
local search = vgui.Create("DTextEntry", left_panel)
search:Dock(TOP)
search:SetTooltip("Press enter to search")
search.propPanel = model_view
search.model_view = model_view
search.delay_functions = {}
file_menu:AddOption(L"build search cache", function()
search:StartSearch("", "models/", {}, "GAME", function(path, pathid) end)
search:StartSearch("", "sound/", {}, "GAME", function(path, pathid) end)
search:StartSearch("", "materials/", {}, "GAME", function(path, pathid) end)
end)
local cancel = vgui.Create("DImageButton", search)
cancel:SetImage(pace.MiscIcons.clear)
cancel:SetSize(16, 16)
cancel.DoClick = function() search:Cancel() end
cancel:SetVisible(false)
do
local old = search.OnGetFocus
function search:OnGetFocus ()
if self:GetValue() == self.default_text then
self:SetValue("")
end
old(self)
end
end
do
local old = search.OnLoseFocus
function search:OnLoseFocus ()
if self:GetValue() == "" then
self:SetValue(self.default_text)
end
old(self)
end
end
local function find(path, pathid)
local key = path .. pathid
if pac.asset_browser_cache[key] then
return unpack(pac.asset_browser_cache[key])
end
local files, folders = file.Find(path, pathid)
pac.asset_browser_cache[key] = {files, folders}
return files, folders
end
function search:PerformLayout()
cancel:SetPos(self:GetWide() - 16 - 2, 2)
end
function search:StartSearch(search_text, folder, extensions, pathid, cb)
cancel:SetVisible(true)
local files, folders = find(folder .. "*", pathid)
self.searched = true
if files then
update_title(table.Count(self.delay_functions) .. " directories left - " .. folder .. "*")
for k, v in ipairs(files) do
local file = folder .. v
for _, ext in ipairs(extensions) do
if v:EndsWith(ext) and file:find(search_text, nil, true) then
local func = function() return cb(file, pathid) end
self.delay_functions[func] = func
break
end
end
end
for k, v in ipairs(folders) do
if v ~= "pac3_cache" then
local func = function()
self:StartSearch(search_text, folder .. v .. "/", extensions, pathid, cb)
end
self.delay_functions[func] = func
end
end
end
end
function search:Stop()
cancel:SetVisible(false)
self.delay_functions = {}
self.searched = false
end
function search:Cancel(why)
self:Stop()
if why then
update_title("search canceled: " .. why)
else
update_title("search canceled")
end
end
function search:Think()
if input.IsKeyDown(KEY_ESCAPE) then
self:Cancel()
return
end
if input.IsControlDown() and input.IsKeyDown(KEY_F) then
self:RequestFocus()
end
local i = 0
for key, func in pairs(self.delay_functions) do
i = i + 1
local ok, reason = func()
if ok == false then
self:Cancel(reason)
return
end
self.delay_functions[func] = nil
if i > 50 then break end
end
if i == 0 and self.searched then
self:Stop()
update_title()
file.Write("pac3_cache/pac_asset_browser_index.txt", util.TableToJSON(pac.asset_browser_cache))
end
if frame.dir then
if not self:IsEnabled() then
self:SetEnabled(true)
end
local change = false
if self:GetValue() == "" or self:GetValue() == self.default_text then
change = true
end
local pathid = frame.pathid or "GAME"
if pathid == "GAME" then pathid = "all" end
self.default_text = L("search " .. pathid .. "/" .. frame.dir .. "/*")
if change then
self:SetValue(self.default_text)
end
else
self:SetValue("")
if self:IsEnabled() then
self:SetEnabled(false)
end
end
end
function search:OnEnter()
if self:GetValue() == "" then return end
local count = 0
local pathid = frame.pathid or "GAME"
local dir = frame.dir
if dir == "models" then
self.propPanel = self.model_view
self.propPanel:Clear()
self:StartSearch(self:GetValue(), "models/", {".mdl"}, pathid, function(path, pathid)
if count >= 500 then return false, "too many results (" .. count .. ")" end
count = count + 1
if not IsUselessModel(path) then
self.propPanel:Add(create_model_icon(path, pathid))
end
end)
elseif dir == "sound" then
self.propPanel = sound_list
self.propPanel:Clear()
self:StartSearch(self:GetValue(), "sound/", {".wav", ".mp3", ".ogg"}, pathid, function(path, pathid)
if count >= 1500 then return false, "too many results (" .. count .. ")" end
count = count + 1
sound_list:AddSound(path, pathid)
end)
elseif dir == "materials" then
self.propPanel = self.model_view
self.propPanel:Clear()
self:StartSearch(self:GetValue(), "materials/", {".vmt", ".vtf", ".png"}, pathid, function(path, pathid)
if count >= 750 then return false, "too many results (" .. count .. ")" end
if path:EndsWith(".vmt") then
if material_view then
count = count + 1
create_material_icon(path, self.propPanel)
end
elseif texture_view then
self.propPanel:Add(create_texture_icon(path, pathid))
count = count + 1
end
end)
elseif dir == "sound names" then
self.propPanel = sound_name_list
self.propPanel:Clear()
local search_text = self:GetValue()
for _, name in ipairs(sound.GetTable()) do
if count >= 1500 then update_title("too many results (" .. count .. ")") return end
if name:find(search_text, nil, true) then
count = count + 1
sound_name_list:AddSound(name)
end
end
end
self.dir = dir
self.pathid = pathid
tree:OnNodeSelected(self)
end
file_menu:AddSpacer()
file_menu:AddOption(L"exit", function() frame:Remove() end):SetImage(pace.MiscIcons.exit)
if select_me then
select_me:GetParentNode():SetExpanded(true)
select_me:SetExpanded(true)
tree:SetSelectedItem(select_me)
end
frame:MakePopup()
end
if pace.model_browser and pace.model_browser:IsValid() then
local visible = pace.model_browser:IsVisible()
pace.model_browser:Remove()
if visible then
pace.AssetBrowser(function(...) print(...) return false end)
end
end
concommand.Add("pac_asset_browser", function(_, _, args)
pace.AssetBrowser(function(path) SetClipboardText(path) update_title("copied " .. path .. " to clipboard!") return false end, args[1] and table.concat(args, ";"))
pace.model_browser:SetSize(ScrW()/1.25, ScrH()/1.25)
pace.model_browser:Center()
end)
list.Set(
"DesktopWindows",
"PACAssetBrowser",
{
title = "Asset Browser",
icon = "icon16/images.png",
width = 960,
height = 700,
onewindow = true,
init = function(icn, pnl)
pnl:Remove()
RunConsoleCommand("pac_asset_browser")
end
}
)