--[[ | 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 } )