diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index dcfefa961..4eba363eb 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -16,7 +16,7 @@ jobs: uses: nebularg/actions-luacheck@v1 with: args: --no-color -q - files: $(git ls-files '*.lua' ':!:totalRP3/Libs/Ellyb/Libraries/*' ':!:totalRP3/Libs/MSA-DropDownMenu-1.0/*' ':!:totalRP3/Libs/TaintLess/*' ':!:totalRP3/Locales/????.lua') + files: $(git ls-files '*.lua' ':!:totalRP3/Libs/Ellyb/Libraries/*' ':!:totalRP3/Libs/MSA-DropDownMenu-1.0/*' ':!:totalRP3/Libs/TaintLess/*' ':!:totalRP3/Locales/????.lua', ':!:Types/*') annotate: warning - name: Run editorconfig-checker diff --git a/.luacheckrc b/.luacheckrc index 186e2f648..b1257310c 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -3,6 +3,7 @@ max_line_length = false exclude_files = { "Scripts/mature_dictionary_template.lua", "totalRP3/Libs", + "Types", }; ignore = { @@ -397,6 +398,7 @@ stds.wow = { "CreateFromMixins", "CreateIndexRangeDataProvider", "CreateScrollBoxListGridView", + "CreateTextureMarkup", "CreateVector2D", "DisableAddOn", "EventRegistry", @@ -472,7 +474,6 @@ stds.wow = { "IsVeteranTrialAccount", "JoinChannelByName", "Lerp", - "LIGHTBLUE_FONT_COLOR", "Mixin", "MouseIsOver", "NeutralPlayerSelectFaction", @@ -606,6 +607,7 @@ stds.wow = { "FUEL", "FURY", "GENERIC_FRACTION_STRING", + "GREEN_FONT_COLOR", "HEALTH", "HIGHLIGHT_FONT_COLOR", "HOLY_POWER", @@ -644,6 +646,7 @@ stds.wow = { "LE_PET_JOURNAL_FILTER_COLLECTED", "LE_PET_JOURNAL_FILTER_NOT_COLLECTED", "LE_SORT_BY_LEVEL", + "LIGHTBLUE_FONT_COLOR", "LINK_FONT_COLOR", "LIST_DELIMITER", "LOCALE_enGB", diff --git a/Types/CallbackRegistry.lua b/Types/CallbackRegistry.lua new file mode 100644 index 000000000..dc1462e39 --- /dev/null +++ b/Types/CallbackRegistry.lua @@ -0,0 +1,48 @@ +---@meta + +--- An object that acts as the owning handle of a callback registration. +---@alias TRP3.CallbackOwner string|table|thread + +--- Interface for objects that allow untyped callback event registrations, +--- mirroring the interface exposed by CallbackHandler-1.0. +--- +---@class TRP3.CallbackRegistry +local CallbackRegistry = {}; + +---@generic T: string +---@param owner TRP3.CallbackOwner +---@param event T +---@param callback fun(event: T, ...)|string? +function CallbackRegistry.RegisterCallback(owner, event, callback) end + +---@generic T: string, V +---@param owner TRP3.CallbackOwner +---@param event T +---@param callback fun(arg1: V, event: T, ...)|string? +---@param arg1 V +function CallbackRegistry.RegisterCallback(owner, event, callback, arg1) end + +---@param owner TRP3.CallbackOwner +---@param event string +function CallbackRegistry.UnregisterCallback(owner, event) end + +---@param owner TRP3.CallbackOwner +function CallbackRegistry.UnregisterAllCallbacks(owner) end + +--- Interface for objects that allow dispatching of untyped callback events, +--- mirroring the interface exposed by CallbackHandler-1.0. +---@class TRP3.CallbackDispatcher +local CallbackDispatcher = {}; + +---@param event string +---@param ... any +function CallbackDispatcher:Fire(event, ...) end + +---@param object table +---@return TRP3.CallbackDispatcher callbacks +function TRP3_API.InitCallbackRegistry(object) end + +---@param object table +---@param events string[] | { [string]: string } +---@return TRP3.CallbackDispatcher callbacks +function TRP3_API.InitCallbackRegistryWithEvents(object, events) end diff --git a/Types/Core.lua b/Types/Core.lua new file mode 100644 index 000000000..225c5decb --- /dev/null +++ b/Types/Core.lua @@ -0,0 +1,4 @@ +---@meta + +---@class TRP3_API +TRP3_API = {}; diff --git a/Types/Game.lua b/Types/Game.lua new file mode 100644 index 000000000..015faaf06 --- /dev/null +++ b/Types/Game.lua @@ -0,0 +1,5 @@ +---@meta + +---@alias TRP3.ClassToken "DEATHKNIGHT" | "DEMONHUNTER" | "DRUID" | "EVOKER" | "HUNTER" | "MAGE" | "MONK" | "PALADIN" | "PRIEST" | "ROGUE" | "SHAMAN" | "WARLOCK" | "WARRIOR" +---@alias TRP3.FileID integer +---@alias TRP3.AtlasElementID integer diff --git a/Types/IconBrowser.lua b/Types/IconBrowser.lua new file mode 100644 index 000000000..d31b1c23f --- /dev/null +++ b/Types/IconBrowser.lua @@ -0,0 +1,63 @@ +---@meta + +---@class TRP3.IconBrowserModel +local IconBrowserModel = {}; + +---@param owner TRP3.CallbackOwner +---@param event "OnModelUpdated" +---@param callback fun(event: "OnModelUpdated")|string? +function IconBrowserModel.RegisterCallback(owner, event, callback) end + +---@param owner TRP3.CallbackOwner +---@param event "OnModelUpdated" +function IconBrowserModel.UnregisterCallback(owner, event) end + +---@param owner TRP3.CallbackOwner +function IconBrowserModel.UnregisterAllCallbacks(owner) end + +---@class TRP3.IconBrowserFilterModel : TRP3.IconBrowserModel +local IconBrowserFilterModel = {}; + +---@param owner TRP3.CallbackOwner +---@param event "OnModelUpdated" +---@param callback fun(event: "OnModelUpdated")|string? +function IconBrowserFilterModel.RegisterCallback(owner, event, callback) end + +---@param owner TRP3.CallbackOwner +---@param event "OnSearchProgressChanged" +---@param callback fun(event: "OnSearchProgressChanged", progress: TRP3.IconBrowserSearchProgress)|string? +function IconBrowserFilterModel.RegisterCallback(owner, event, callback) end + +---@param owner TRP3.CallbackOwner +---@param event "OnSearchStateChanged" +---@param callback fun(event: "OnSearchStateChanged", state: "running" | "finished")|string? +function IconBrowserFilterModel.RegisterCallback(owner, event, callback) end + +---@param owner TRP3.CallbackOwner +---@param event "OnSearchProgressChanged" +function IconBrowserFilterModel.UnregisterCallback(owner, event) end + +---@class TRP3.IconBrowserFilterTask +local IconBrowserSearchTask = {}; + +---@param owner TRP3.CallbackOwner +---@param event "OnProgressChanged" +---@param callback fun(event: "OnProgressChanged", progress: TRP3.IconBrowserSearchProgress)|string? +function IconBrowserSearchTask.RegisterCallback(owner, event, callback) end + +---@param owner TRP3.CallbackOwner +---@param event "OnResultsChanged" +---@param callback fun(event: "OnResultsChanged", results: integer[])|string? +function IconBrowserSearchTask.RegisterCallback(owner, event, callback) end + +---@param owner TRP3.CallbackOwner +---@param event "OnStateChanged" +---@param callback fun(event: "OnStateChanged", state: "running" | "finished")|string? +function IconBrowserSearchTask.RegisterCallback(owner, event, callback) end + +---@param owner TRP3.CallbackOwner +---@param event "OnStateChanged" | "OnProgressChanged" | "OnResultsChanged" +function IconBrowserSearchTask.UnregisterCallback(owner, event) end + +---@param owner TRP3.CallbackOwner +function IconBrowserSearchTask.UnregisterAllCallbacks(owner) end diff --git a/totalRP3/Core/Core.xml b/totalRP3/Core/Core.xml index 78cc3937b..8fe15d2ea 100644 --- a/totalRP3/Core/Core.xml +++ b/totalRP3/Core/Core.xml @@ -19,6 +19,7 @@ https://raw.githubusercontent.com/Meorawr/wow-ui-schema/main/UI.xsd"> + diff --git a/totalRP3/Core/FunctionUtil.lua b/totalRP3/Core/FunctionUtil.lua new file mode 100644 index 000000000..21b3cfadc --- /dev/null +++ b/totalRP3/Core/FunctionUtil.lua @@ -0,0 +1,52 @@ +-- Copyright The Total RP 3 Authors +-- SPDX-License-Identifier: Apache-2.0 + +TRP3_FunctionUtil = {}; + +--- Returns a closure that when first invoked will start a timer of duration +--- `timeout`. When this timer has elapsed, the supplied callback will be +--- invoked. +--- +--- Repeated calls to the closure will reset the timeout back to zero, in +--- effect delaying execution of the callback. +--- +--- @param timeout number +--- @param callback function +function TRP3_FunctionUtil.Debounce(timeout, callback) + local calls = 0; + + local function Decrement() + calls = calls - 1; + + if calls == 0 then + callback(); + end + end + + return function() + C_Timer.After(timeout, Decrement); + calls = calls + 1; + end +end + +--- Returns a closure that when first invoked will immediately execute the +--- supplied callback, and starts a timer of duration `timeout`. Until the +--- timer has elapsed, future invocations will do nothing. +--- +--- @param timeout number +--- @param callback function +function TRP3_FunctionUtil.Throttle(timeout, callback) + local callable = true; + + local function Reset() + callable = true; + end + + return function() + if callable then + C_Timer.After(timeout, Reset); + callable = false; + callback(); + end + end +end diff --git a/totalRP3/Core/Popup.lua b/totalRP3/Core/Popup.lua index 1586ffbc3..66a8b2d44 100644 --- a/totalRP3/Core/Popup.lua +++ b/totalRP3/Core/Popup.lua @@ -394,35 +394,33 @@ end -- Icon browser --*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* -local IconBrowserCallbackOwner = {}; -- Dummy owner for callback registrations. - -local function OnIconBrowserSelection(onSelectCallback, _, iconInfo) - TRP3_IconBrowserUtil.UnregisterAllCallbacks(IconBrowserCallbackOwner); - hidePopups(); +local function showIconBrowser(onSelectCallback, onCancelCallback, scale, selectedIcon) + local function OnAccept(iconInfo) + hidePopups(); - if onSelectCallback then - onSelectCallback(iconInfo.name, iconInfo); + if onSelectCallback then + onSelectCallback(iconInfo.name, iconInfo); + end end -end -local function OnIconBrowserClosed(onCancelCallback) - TRP3_IconBrowserUtil.UnregisterAllCallbacks(IconBrowserCallbackOwner); - hidePopups(); + local function OnCancel() + hidePopups(); - if onCancelCallback then - onCancelCallback(); + if onCancelCallback then + onCancelCallback(); + end end -end -local function showIconBrowser(onSelectCallback, onCancelCallback, scale) - TRP3_IconBrowserUtil.RegisterCallback(IconBrowserCallbackOwner, "OnBrowserIconSelected", OnIconBrowserSelection, onSelectCallback); - TRP3_IconBrowserUtil.RegisterCallback(IconBrowserCallbackOwner, "OnBrowserClosed", OnIconBrowserClosed, onCancelCallback); - TRP3_IconBrowserUtil.OpenBrowser(); - TRP3_IconBrowser:SetScale(scale or 1); + TRP3_IconBrowser.Open({ + onAcceptCallback = OnAccept, + onCancelCallback = OnCancel, + scale = scale, + selectedIcon = selectedIcon, + }); end function TRP3_API.popup.hideIconBrowser() - TRP3_IconBrowserUtil.CloseBrowser(); + TRP3_IconBrowser.Close(); end --*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* @@ -1145,7 +1143,7 @@ local POPUP_STRUCTURE = { showMethod = showColorBrowser, }, [TRP3_API.popup.ICONS] = { - frame = TRP3_IconBrowser, + frame = TRP3_IconBrowserFrame, showMethod = showIconBrowser, }, [TRP3_API.popup.MUSICS] = { diff --git a/totalRP3/Core/Prototype.lua b/totalRP3/Core/Prototype.lua index 18abe29b5..e262c9a63 100644 --- a/totalRP3/Core/Prototype.lua +++ b/totalRP3/Core/Prototype.lua @@ -1,6 +1,7 @@ -- Copyright The Total RP 3 Authors -- SPDX-License-Identifier: Apache-2.0 +---@class TRP3_API local TRP3_API = select(2, ...); -- Prototype factory @@ -53,14 +54,22 @@ function PrototypeMetatableFactory:GetOrCreate(prototype) return metatable; end +---@generic T : table +---@param prototype T +---@return T object function TRP3_API.CreateFromPrototype(prototype) local metatable = PrototypeMetatableFactory:GetOrCreate(prototype); return setmetatable({}, metatable); end +---@generic T : table & { __init: fun(object: table, ...: any)? } +---@param prototype T +---@param ... any +---@return T object function TRP3_API.CreateAndInitFromPrototype(prototype, ...) local object = TRP3_API.CreateFromPrototype(prototype); + ---@cast prototype table if prototype.__init then prototype.__init(object, ...); end @@ -68,6 +77,10 @@ function TRP3_API.CreateAndInitFromPrototype(prototype, ...) return object; end +---@generic T : table +---@param object table +---@param prototype T +---@return T object function TRP3_API.ApplyPrototypeToObject(object, prototype) local metatable = PrototypeMetatableFactory:GetOrCreate(prototype); return setmetatable(object, metatable); diff --git a/totalRP3/Locales/enUS.lua b/totalRP3/Locales/enUS.lua index cb4427242..ea6e11195 100644 --- a/totalRP3/Locales/enUS.lua +++ b/totalRP3/Locales/enUS.lua @@ -849,6 +849,8 @@ This will works:|cff00ff00 UI_IMAGE_BROWSER = "Image browser", UI_IMAGE_SELECT = "Select image", UI_FILTER = "Filter", + UI_FILTER_NO_RESULTS_FOUND_TITLE = "No results found.", + UI_FILTER_NO_RESULTS_FOUND_TEXT = "Try adjusting your search criteria.", UI_LINK_URL = "http://your.url.here", UI_LINK_TEXT = "Your text here", UI_LINK_SAFE = [[Here's the link URL.]], diff --git a/totalRP3/Modules/Register/Characters/RegisterAbout.lua b/totalRP3/Modules/Register/Characters/RegisterAbout.lua index 871347de4..e8f4cb344 100644 --- a/totalRP3/Modules/Register/Characters/RegisterAbout.lua +++ b/totalRP3/Modules/Register/Characters/RegisterAbout.lua @@ -37,8 +37,8 @@ local CONFIG_REGISTER_ABOUT_H3_SIZE = "config_register_about_h3_size"; local defaultFontParameters; local refreshTemplate2EditDisplay, saveInDraft, template2SaveToDraft; -- Function reference -local showIconBrowser = function(callback) - TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, nil, {callback}); +local showIconBrowser = function(callback, selectedIcon) + TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, nil, {callback, nil, nil, selectedIcon}); end; local function updateAboutTemplateFonts(frame) @@ -337,7 +337,7 @@ function refreshTemplate2EditDisplay() showIconBrowser(function(icon) frame.frameData.IC = icon; setupIconButton(_G[frame:GetName().."Icon"], icon); - end); + end, frameData.IC); end); -- Buttons if frameIndex == 1 then @@ -894,9 +894,9 @@ function TRP3_API.register.inits.aboutInit() setupListBox(TRP3_RegisterAbout_Edit_Template3_PhysBkg, bkgTab, setTemplate3PhysBkg, nil, 150, true); setupListBox(TRP3_RegisterAbout_Edit_Template3_PsyBkg, bkgTab, setTemplate3PsyBkg, nil, 150, true); setupListBox(TRP3_RegisterAbout_Edit_Template3_HistBkg, bkgTab, setTemplate3HistBkg, nil, 150, true); - TRP3_RegisterAbout_Edit_Template3_PhysIcon:SetScript("OnClick", function() showIconBrowser(onPhisIconSelected) end ); - TRP3_RegisterAbout_Edit_Template3_PsyIcon:SetScript("OnClick", function() showIconBrowser(onPsychoIconSelected) end ); - TRP3_RegisterAbout_Edit_Template3_HistIcon:SetScript("OnClick", function() showIconBrowser(onHistoIconSelected) end ); + TRP3_RegisterAbout_Edit_Template3_PhysIcon:SetScript("OnClick", function() showIconBrowser(onPhisIconSelected, draftData.T3.PH.IC) end ); + TRP3_RegisterAbout_Edit_Template3_PsyIcon:SetScript("OnClick", function() showIconBrowser(onPsychoIconSelected, draftData.T3.PS.IC) end ); + TRP3_RegisterAbout_Edit_Template3_HistIcon:SetScript("OnClick", function() showIconBrowser(onHistoIconSelected, draftData.T3.HI.IC) end ); TRP3_RegisterAbout_Edit_Music_Action:SetScript("OnClick", onMusicEditClicked); TRP3_RegisterAbout_Edit_Template2_Add:SetScript("OnClick", template2AddFrame); TRP3_RegisterAbout_AboutPanel_EditButton:SetScript("OnClick", onEdit); diff --git a/totalRP3/Modules/Register/Characters/RegisterCharacteristics.lua b/totalRP3/Modules/Register/Characters/RegisterCharacteristics.lua index fa2c86568..ebe7a929e 100644 --- a/totalRP3/Modules/Register/Characters/RegisterCharacteristics.lua +++ b/totalRP3/Modules/Register/Characters/RegisterCharacteristics.lua @@ -31,8 +31,8 @@ local buildZoneText = Utils.str.buildZoneText; local setupEditBoxesNavigation = TRP3_API.ui.frame.setupEditBoxesNavigation; local setupListBox = TRP3_API.ui.listbox.setupListBox; -local showIconBrowser = function(callback) - TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, nil, {callback}); +local showIconBrowser = function(callback, selectedIcon) + TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, nil, {callback, nil, nil, selectedIcon}); end; local PSYCHO_PRESETS_UNKOWN; @@ -1119,7 +1119,7 @@ function setEditDisplay() showIconBrowser(function(icon) miscStructure.IC = icon; setupIconButton(_G[frame:GetName() .. "Icon"], icon or TRP3_InterfaceIcons.Default); - end); + end, miscStructure.IC); end); frame.miscIndex = frameIndex; @@ -1192,14 +1192,14 @@ function setEditDisplay() showIconBrowser(function(icon) psychoStructure.LI = icon; setupIconButton(self, icon or TRP3_InterfaceIcons.Default); - end); + end, psychoStructure.LI); end); frame.CustomRightIcon:SetScript("OnClick", function(self) showIconBrowser(function(icon) psychoStructure.RI = icon; setupIconButton(self, icon or TRP3_InterfaceIcons.Default); - end); + end, psychoStructure.RI); end); -- Run through all the child elements. If they've got a hide set flag @@ -1513,7 +1513,7 @@ function TRP3_API.register.inits.characteristicsInit() -- UI TRP3_RegisterCharact_Edit_MiscAdd:SetScript("OnClick", miscAddDropDown); - TRP3_RegisterCharact_Edit_NamePanel_Icon:SetScript("OnClick", function() showIconBrowser(onPlayerIconSelected) end); + TRP3_RegisterCharact_Edit_NamePanel_Icon:SetScript("OnClick", function() showIconBrowser(onPlayerIconSelected, draftData.IC) end); TRP3_RegisterCharact_NamePanel_Edit_CancelButton:SetScript("OnClick", showCharacteristicsTab); TRP3_RegisterCharact_NamePanel_Edit_SaveButton:SetScript("OnClick", onSave); TRP3_RegisterCharact_NamePanel_EditButton:SetScript("OnClick", onEdit); diff --git a/totalRP3/Modules/Register/Companions/RegisterCompanionsPage.lua b/totalRP3/Modules/Register/Companions/RegisterCompanionsPage.lua index e582144a1..dbc20f2ec 100644 --- a/totalRP3/Modules/Register/Companions/RegisterCompanionsPage.lua +++ b/totalRP3/Modules/Register/Companions/RegisterCompanionsPage.lua @@ -356,8 +356,8 @@ end -- Init --*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* -local showIconBrowser = function(callback) - TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, nil, {callback}); +local showIconBrowser = function(callback, selectedIcon) + TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, nil, {callback, nil, nil, selectedIcon}); end; -- Tutorial @@ -416,7 +416,7 @@ TRP3_API.RegisterCallback(TRP3_Addon, TRP3_Addon.Events.WORKFLOW_ON_LOAD, functi TRP3_CompanionsPageInformationConsult_NamePanel_EditButton:SetScript("OnClick", toEditMode); TRP3_CompanionsPageInformationEdit_NamePanel_CancelButton:SetScript("OnClick", showInformationTab); TRP3_CompanionsPageInformationEdit_NamePanel_SaveButton:SetScript("OnClick", onSave); - TRP3_CompanionsPageInformationEdit_NamePanel_Icon:SetScript("OnClick", function() showIconBrowser(onPlayerIconSelected) end ); + TRP3_CompanionsPageInformationEdit_NamePanel_Icon:SetScript("OnClick", function() showIconBrowser(onPlayerIconSelected, draftData.IC) end ); TRP3_CompanionsPageInformationEdit_NamePanel_NameColor.onSelection = onNameColorSelected; setupFieldSet(TRP3_CompanionsPageInformationConsult_NamePanel, loc.REG_PLAYER_NAMESTITLES, 150); diff --git a/totalRP3/Modules/Register/Main/RegisterGlance.lua b/totalRP3/Modules/Register/Main/RegisterGlance.lua index 7f950ffef..5caae2c0a 100644 --- a/totalRP3/Modules/Register/Main/RegisterGlance.lua +++ b/totalRP3/Modules/Register/Main/RegisterGlance.lua @@ -426,9 +426,9 @@ local function openGlanceEditor(slot, slotData, callback, external, arg1, arg2) TRP3_AtFirstGlanceEditorIcon:SetScript("OnClick", function(self) TRP3_API.popup.hideIconBrowser(); if self.isExternal then - TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, {parent = TRP3_AtFirstGlanceEditor, point = "RIGHT", parentPoint = "LEFT"}, {onIconSelected, nil, 0.75}); + TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, {parent = TRP3_AtFirstGlanceEditor, point = "RIGHT", parentPoint = "LEFT"}, {onIconSelected, nil, 0.75, TRP3_AtFirstGlanceEditorIcon.icon}); else - TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, nil, {onIconSelected}); + TRP3_API.popup.showPopup(TRP3_API.popup.ICONS, nil, {onIconSelected, nil, nil, TRP3_AtFirstGlanceEditorIcon.icon}); end end); onIconSelected(slotData.IC); diff --git a/totalRP3/Resources/UI/common-iconmask.tga b/totalRP3/Resources/UI/common-iconmask.tga new file mode 100644 index 000000000..a4a882ef4 Binary files /dev/null and b/totalRP3/Resources/UI/common-iconmask.tga differ diff --git a/totalRP3/UI/Browsers/IconBrowser.lua b/totalRP3/UI/Browsers/IconBrowser.lua index 7f3c5a827..196864cd4 100644 --- a/totalRP3/UI/Browsers/IconBrowser.lua +++ b/totalRP3/UI/Browsers/IconBrowser.lua @@ -1,216 +1,441 @@ -- Copyright The Total RP 3 Authors -- SPDX-License-Identifier: Apache-2.0 -local _, TRP3_API = ...; local L = TRP3_API.loc; local LibRPMedia = LibStub:GetLibrary("LibRPMedia-1.0"); --- Callback registry ------------------------------------------------------------------------------- - -local IconBrowserCallbacks = TRP3_API.CreateCallbackRegistryWithEvents({ - "OnBrowserClosed", - "OnBrowserOpened", - "OnBrowserIconSelected", - "OnSearchUpdated", -}); - --- Search controller ------------------------------------------------------------------------------- - -local IconSearchState = { - Searching = "searching", - Finished = "finished", - Cancelled = "cancelled", -}; - -local IconSearchController = {}; - -function IconSearchController:OnLoad() - self.query = ""; - self.results = {}; +local function GenerateSearchableString(str) + return string.utf8lower(string.trim((string.gsub(str, "%p+", " ")))); +end + +--- Table structure providing data about a single icon sourced from a model. +--- +---@class TRP3.IconBrowserModelItem +---@field index integer +---@field key integer +---@field name string +---@field type "atlas" | "file" +---@field file TRP3.FileID? +---@field atlas TRP3.AtlasElementID? +---@field selected boolean? + +--- IconBrowserModel is a basic model that sources icons from LibRPMedia. +--- +---@class TRP3.IconBrowserModel +---@field private callbacks TRP3.CallbackDispatcher +local IconBrowserModel = {}; + +---@protected +function IconBrowserModel:__init() + self.callbacks = TRP3_API.InitCallbackRegistryWithEvents(self, { "OnModelUpdated" }); +end + +---@return integer count +function IconBrowserModel:GetIconCount() + return LibRPMedia:GetNumIcons(); +end + +---@param index integer +---@return TRP3.IconBrowserModelItem? data +function IconBrowserModel:GetIconInfo(index) + return LibRPMedia:GetIconDataByIndex(index); +end + +---@param index integer +---@return string? name +function IconBrowserModel:GetIconName(index) + return LibRPMedia:GetIconDataByIndex(index, "name"); +end + +local function CreateIconBrowserModel() + return TRP3_API.CreateAndInitFromPrototype(IconBrowserModel); +end + +--- Table structure providing information about the progress of an +--- asynchronous search task against an icon model. +--- +---@class TRP3.IconBrowserSearchProgress +---@field found integer +---@field searched integer +---@field total integer + +--- IconBrowserSearchTask is a single-shot object that performs an +--- asynchronous name-based search against a model to provide a filtered +--- list of icon indices. +--- +---@class TRP3.IconBrowserFilterTask +---@field private callbacks TRP3.CallbackDispatcher +---@field private state "pending" | "running" | "finished" +---@field private ticker unknown +---@field private model TRP3.IconBrowserModel +---@field private query string +---@field private found integer +---@field private searched integer +---@field private total integer +---@field private step integer +---@field private results integer[] +local IconBrowserSearchTask = {}; + +---@param query string +---@param model TRP3.IconBrowserModel +---@protected +function IconBrowserSearchTask:__init(query, model) + self.callbacks = TRP3_API.InitCallbackRegistry(self); + self.state = "pending"; self.ticker = nil; - self.progress = LibRPMedia:GetNumIcons(); - self.total = self.progress; - self.lastResumeTime = -math.huge; + self.model = model; + self.query = query; + self.found = 0; + self.searched = 0; + self.total = model:GetIconCount(); + self.step = 500; + self.results = {}; end -function IconSearchController:BeginSearch(query) - query = string.utf8lower(string.trim(query)); +function IconBrowserSearchTask:Start() + assert(self.state == "pending", "attempted to restart an already started search task"); + self.ticker = C_Timer.NewTicker(0, function() self:OnUpdate(); end); + self.state = "running"; + self.callbacks:Fire("OnStateChanged", self.state); +end - if self.query == query then +function IconBrowserSearchTask:Finish() + if self.state == "finished" then return; end - -- Cancel any existing ticker. - if self.ticker ~= nil then - self.ticker:Cancel(); - self.ticker = nil; - end + self.ticker:Cancel(); + self.ticker = nil; + self.state = "finished"; + self.callbacks:Fire("OnStateChanged", self.state); +end - if query == "" then - self.query = ""; - self.results = {}; - self.ticker = nil; - self.progress = LibRPMedia:GetNumIcons(); - self.total = self.progress; - self.lastResumeTime = -math.huge; - else - self.query = query; - self.results = {}; - self.ticker = C_Timer.NewTicker(0, function() self:OnUpdate(); end); - self.progress = 0; - self.total = LibRPMedia:GetNumIcons(); - self.lastResumeTime = -math.huge; - end +function IconBrowserSearchTask:GetQuery() + return self.query; +end - IconBrowserCallbacks:TriggerEvent("OnSearchUpdated"); +---@return TRP3.IconBrowserSearchProgress +function IconBrowserSearchTask:GetProgress() + return { found = self.found, searched = self.searched, total = self.total }; end -function IconSearchController:ResumeSearch() - if not self:HasSearchState(IconSearchState.Searching) then - return; - end +function IconBrowserSearchTask:GetState() + return self.state; +end - local MAX_ICONS_PER_TICK = 500; - local i = self.progress + 1; - local j = math.min(self.progress + MAX_ICONS_PER_TICK, self.total); +function IconBrowserSearchTask:GetResults() + return self.results; +end + +---@private +function IconBrowserSearchTask:OnUpdate() + local query = self.query; + local model = self.model; + local found = self.found; + local results = self.results; + + local i = self.searched + 1; + local j = math.min(self.searched + self.step, self.total); for iconIndex = i, j do - local iconName = LibRPMedia:GetIconDataByIndex(iconIndex, "name"); - local pattern = self.query; + local iconName = GenerateSearchableString(model:GetIconName(iconIndex)); local offset = 1; local plain = true; - if iconName and string.find(iconName, pattern, offset, plain) then - table.insert(self.results, iconIndex); + if iconName and string.find(iconName, query, offset, plain) then + found = found + 1; + results[found] = iconIndex; end end - -- Update search progress; if we've reached the total number of icons - -- then cancel and release the ticker. + if i == 1 or self.found ~= found then + self.found = found; + self.callbacks:Fire("OnResultsChanged", self.results); + end - self.progress = j; - self.lastResumeTime = GetTime(); + self.searched = j; + self.callbacks:Fire("OnProgressChanged", self:GetProgress()); - if self.progress == self.total and self.ticker ~= nil then - self.ticker:Cancel(); - self.ticker = nil; + if self.searched >= self.total then + self:Finish(); end +end - IconBrowserCallbacks:TriggerEvent("OnSearchUpdated"); +---@param query string +---@param model TRP3.IconBrowserModel +local function CreateIconBrowserSearchTask(query, model) + return TRP3_API.CreateAndInitFromPrototype(IconBrowserSearchTask, query, model); end +--- IconBrowserFilterModel is a proxy model that implements asynchronous +--- filtering via name-based search queries. +--- +---@class TRP3.IconBrowserFilterModel : TRP3.IconBrowserModel +---@field private callbacks TRP3.CallbackDispatcher +---@field private source TRP3.IconBrowserModel +---@field private sourceIndices integer[] +---@field private searchQuery string +---@field private searchTask TRP3.IconBrowserFilterTask? +---@field private searchProgress TRP3.IconBrowserSearchProgress +local IconBrowserFilterModel = {}; -function IconSearchController:CancelSearch() - if not self:HasSearchState(IconSearchState.Searching) then - return; - end +---@param source TRP3.IconBrowserModel +---@protected +function IconBrowserFilterModel:__init(source) + self.callbacks = TRP3_API.InitCallbackRegistry(self); + self.source = source; + self.sourceIndices = {}; + self.searchQuery = ""; + self.searchTask = nil; - self.ticker:Cancel(); - self.ticker = nil; - IconBrowserCallbacks:TriggerEvent("OnSearchUpdated"); + self.source.RegisterCallback(self, "OnModelUpdated", "OnSourceModelUpdated"); end -function IconSearchController:OnUpdate() - local MAX_ELAPSED_TIME = 0.1; - local MAX_SKIPPED_TIME = 0.5; +function IconBrowserFilterModel:GetIconCount() + local count; - local elapsedTime = GetTickTime(); - local skippedTime = GetTime() - self.lastResumeTime; + if self:HasSearchQuery() then + count = #self.sourceIndices; + else + count = self.source:GetIconCount(); + end - -- If the last frame was too slow then we won't resume the search on this - -- frame to allow the client to catch up a little bit. - -- - -- We won't duck forever though - if for whatever reason the client is - -- persistently slow we'll eventually force resumption of the search. + return count; +end - if (elapsedTime < MAX_ELAPSED_TIME) or (skippedTime >= MAX_SKIPPED_TIME) then - self:ResumeSearch(); - end +function IconBrowserFilterModel:GetIconInfo(filterIndex) + return self.source:GetIconInfo(self:GetSourceIndex(filterIndex)); +end + +function IconBrowserFilterModel:GetIconName(filterIndex) + return self.source:GetIconName(self:GetSourceIndex(filterIndex)); +end + +function IconBrowserFilterModel:GetSourceModel() + return self.source; end -function IconSearchController:GetSearchState() - if self.progress == self.total then - return IconSearchState.Finished; - elseif self.ticker ~= nil then - return IconSearchState.Searching; +function IconBrowserFilterModel:GetSourceIndex(filterIndex) + local sourceIndex; + + if self:HasSearchQuery() then + sourceIndex = self.sourceIndices[filterIndex]; else - return IconSearchState.Cancelled; + sourceIndex = filterIndex; end + + return sourceIndex; +end + +function IconBrowserFilterModel:ClearSearchQuery() + self:SetSearchQuery(""); end -function IconSearchController:GetSearchInfo() - return { - query = self.query, - progress = self.progress, - total = self.total, - state = self:GetSearchState(), - }; +function IconBrowserFilterModel:GetSearchQuery() + return self.searchQuery; end -function IconSearchController:GetNumSearchResults() - if self.query == "" then - return self.total; +---@return TRP3.IconBrowserSearchProgress +function IconBrowserFilterModel:GetSearchProgress() + local progress; + + if self.searchTask ~= nil then + progress = self.searchTask:GetProgress(); else - return #self.results; + local count = self.source:GetIconCount(); + progress = { found = count, searched = count, total = count }; end + + return progress; end -function IconSearchController:GetSearchResultInfo(index) - if self.query == "" then - return LibRPMedia:GetIconDataByIndex(index); +---@return "running" | "finished" +function IconBrowserFilterModel:GetSearchState() + local state; + + if self.searchTask ~= nil then + state = "running"; else - return LibRPMedia:GetIconDataByIndex(self.results[index]); + state = "finished"; end + + return state; end -function IconSearchController:HasSearchState(state) - return self:GetSearchState() == state; +function IconBrowserFilterModel:HasSearchQuery() + return self.searchQuery ~= ""; end -IconSearchController:OnLoad(); +---@param query string +function IconBrowserFilterModel:SetSearchQuery(query) + query = GenerateSearchableString(query); ---@cast query string --- Browser utilities ------------------------------------------------------------------------------- + if self.searchQuery ~= query then + self.searchQuery = query; + self:RebuildModel(); + end +end + +---@private +function IconBrowserFilterModel:OnSourceModelUpdated() + self:RebuildModel(); +end + +---@private +function IconBrowserFilterModel:RebuildModel() + if self.searchTask then + self.searchTask:Finish(); + self.searchTask = nil; + end + + local query = self:GetSearchQuery(); + + if query == "" then + self.callbacks:Fire("OnModelUpdated"); + return; + end + + local function OnStateChanged(_, state) + if state == "finished" then + self.searchTask.UnregisterAllCallbacks(self); + self.searchTask = nil; + end + + self.callbacks:Fire("OnSearchStateChanged", state); + end + + local function OnProgressChanged(_, progress) + self.callbacks:Fire("OnSearchProgressChanged", progress); + end + + local function OnResultsChanged(_, results) + self.sourceIndices = results; + self.callbacks:Fire("OnModelUpdated"); + end -TRP3_IconBrowserUtil = {}; + self.searchTask = CreateIconBrowserSearchTask(query, self.source); + self.searchTask.RegisterCallback(self, "OnStateChanged", OnStateChanged); + self.searchTask.RegisterCallback(self, "OnProgressChanged", OnProgressChanged); + self.searchTask.RegisterCallback(self, "OnResultsChanged", OnResultsChanged); + self.searchTask:Start(); +end -function TRP3_IconBrowserUtil.GetNumSearchResults() - return IconSearchController:GetNumSearchResults(); +---@param source TRP3.IconBrowserModel +local function CreateIconBrowserFilterModel(source) + return TRP3_API.CreateAndInitFromPrototype(IconBrowserFilterModel, source); end -function TRP3_IconBrowserUtil.GetSearchInfo() - return IconSearchController:GetSearchInfo(); +--- IconBrowserSelectionModel is a proxy model that relocates the currently +--- selected icon to the start of the model. +---@class TRP3.IconBrowserSelectionModel : TRP3.IconBrowserModel +---@field private callbacks TRP3.CallbackDispatcher +---@field private source TRP3.IconBrowserModel +---@field private selectedIndex integer? +local IconBrowserSelectionModel = {}; + +---@protected +function IconBrowserSelectionModel:__init(source) + self.callbacks = TRP3_API.InitCallbackRegistryWithEvents(self, { "OnModelUpdated" }); + self.source = source; + self.selectedIndex = nil; end -function TRP3_IconBrowserUtil.GetSearchResultInfo(index) - return IconSearchController:GetSearchResultInfo(index); +---@param index integer +---@return TRP3.IconBrowserModelItem? data +function IconBrowserSelectionModel:GetIconInfo(index) + local iconInfo = self.source:GetIconInfo(self:GetSourceIndex(index)); + + if iconInfo and self.selectedIndex then + iconInfo.selected = (index == 1); + end + + return iconInfo; end -function TRP3_IconBrowserUtil.BeginSearch(query) - IconSearchController:BeginSearch(query); +function IconBrowserSelectionModel:GetIconCount() + return self.source:GetIconCount(); end -function TRP3_IconBrowserUtil.RegisterCallback(owner, event, callback, ...) - IconBrowserCallbacks.RegisterCallback(owner, event, callback, ...); +---@param index integer +---@return string? name +function IconBrowserSelectionModel:GetIconName(index) + return self.source:GetIconName(self:GetSourceIndex(index)); end -function TRP3_IconBrowserUtil.UnregisterCallback(owner, event) - IconBrowserCallbacks.UnregisterCallback(owner, event); +function IconBrowserSelectionModel:GetSourceModel() + return self.source; end -function TRP3_IconBrowserUtil.UnregisterAllCallbacks(owner) - IconBrowserCallbacks.UnregisterAllCallbacks(owner); +function IconBrowserSelectionModel:GetSourceIndex(filterIndex) + local sourceIndex = filterIndex; + + if self.selectedIndex then + if filterIndex == 1 then + sourceIndex = self.selectedIndex; + elseif filterIndex <= self.selectedIndex then + sourceIndex = filterIndex - 1; + end + end + + return sourceIndex; +end + +function IconBrowserSelectionModel:GetSelectedIndex() + return self.selectedIndex; end -function TRP3_IconBrowserUtil.CloseBrowser() - TRP3_IconBrowser:Hide(); +function IconBrowserSelectionModel:SetSelectedIndex(sourceIndex) + if self.selectedIndex ~= sourceIndex then + self.selectedIndex = sourceIndex; + self.callbacks:Fire("OnModelUpdated"); + end end -function TRP3_IconBrowserUtil.OpenBrowser() - TRP3_IconBrowser:Show(); +---@param source TRP3.IconBrowserModel +local function CreateIconBrowserSelectionModel(source) + return TRP3_API.CreateAndInitFromPrototype(IconBrowserSelectionModel, source); +end + +--- Creates a data provider that displays the contents of an icon data model +--- within a scrollbox list view. +--- +---@param model TRP3.IconBrowserModel +---@return table provider +local function CreateIconDataProvider(model) + local provider = CreateFromMixins(CallbackRegistryMixin); + + function provider:Enumerate(i, j) + i = i and (i - 1) or 0; + j = j or model:GetIconCount(); + + local function Next(_, k) + k = k + 1; + + if k <= j then + return k, model:GetIconInfo(k); + end + end + + return Next, nil, i; + end + + function provider:Find(i) + return model:GetIconInfo(i); + end + + function provider:GetSize() + return model:GetIconCount(); + end + + provider:GenerateCallbackEvents({ "OnSizeChanged" }); + provider:OnLoad(); + + model.RegisterCallback(provider, "OnModelUpdated", function() + provider:TriggerEvent("OnSizeChanged"); + end); + + return provider; end -- Browser mixin @@ -219,60 +444,93 @@ end TRP3_IconBrowserMixin = {}; function TRP3_IconBrowserMixin:OnLoad() - local GRID_STRIDE = 8; - local GRID_PADDING = 8; - local GRID_SPACING_X = 6; - local GRID_SPACING_Y = 6; + self.callbacks = TRP3_API.InitCallbackRegistryWithEvents(self, { "OnOpened", "OnClosed", "OnIconSelected" }); + self.baseModel = CreateIconBrowserModel(); + self.selectionModel = CreateIconBrowserSelectionModel(self.baseModel); + self.model = CreateIconBrowserFilterModel(self.selectionModel); + + local GRID_STRIDE = 9; + local GRID_PADDING = 4; - TRP3_IconBrowserUtil.RegisterCallback(self, "OnSearchUpdated"); + self.ScrollContent.ScrollView = CreateScrollBoxListGridView(GRID_STRIDE, GRID_PADDING, GRID_PADDING, GRID_PADDING, GRID_PADDING); + self.ScrollContent.ScrollView:SetElementInitializer("TRP3_IconBrowserButton", function(button, iconInfo) self:OnIconButtonInitialized(button, iconInfo); end); + ScrollUtil.InitScrollBoxListWithScrollBar(self.ScrollContent.ScrollBox, self.ScrollContent.ScrollBar, self.ScrollContent.ScrollView); + ScrollUtil.AddManagedScrollBarVisibilityBehavior(self.ScrollContent.ScrollBox, self.ScrollContent.ScrollBar); + self.ScrollContent.ScrollBox:SetDataProvider(CreateIconDataProvider(self.model)); - self.CloseButton:SetScript("OnClick", function(_, ...) self:OnCloseButtonClicked(...); end); - self.SearchFilterEditBox = self.Filter.EditBox; - self.SearchFilterEditBox:SetScript("OnTextChanged", function(_, ...) self:OnFilterTextChanged(...); end); - self.SearchFilterEditBoxTitle = self.Filter.EditBox.title; - self.SearchFilterTotalText = self.Filter.TotalText; + self.CloseButton:SetScript("OnClick", function() self:OnCloseButtonClicked(); end); + self.SearchBox:HookScript("OnTextChanged", TRP3_FunctionUtil.Debounce(0.25, function() self:OnFilterTextChanged(); end)); - self.ScrollView = CreateScrollBoxListGridView(GRID_STRIDE, GRID_PADDING, GRID_PADDING, GRID_PADDING, GRID_PADDING, GRID_SPACING_X, GRID_SPACING_Y); - self.ScrollView:SetElementInitializer("TRP3_IconBrowserButton", function(button) button:Refresh(); end) - ScrollUtil.InitScrollBoxListWithScrollBar(self.ScrollBox, self.ScrollBar, self.ScrollView); - ScrollUtil.AddManagedScrollBarVisibilityBehavior(self.ScrollBox, self.ScrollBar); - self.DataProvider = CreateIndexRangeDataProvider(0); - self.ScrollBox:SetDataProvider(self.DataProvider); + self.model.RegisterCallback(self, "OnModelUpdated"); + self.model.RegisterCallback(self, "OnSearchStateChanged"); + self.model.RegisterCallback(self, "OnSearchProgressChanged"); end function TRP3_IconBrowserMixin:OnShow() - self.SearchFilterEditBox:SetText(""); - self.SearchFilterEditBox:SetFocus(true); + self.model:ClearSearchQuery(); + self.Title:SetText(L.UI_ICON_BROWSER); + self.SearchBox.Instructions:SetTextColor(0.6, 0.6, 0.6); + self.SearchBox:SetText(""); + self.SearchBox:SetFocus(true); + self.ScrollContent.EmptyText.Title:SetText(L.UI_FILTER_NO_RESULTS_FOUND_TITLE); + self.ScrollContent.EmptyText.Text:SetText(L.UI_FILTER_NO_RESULTS_FOUND_TEXT); + self.ScrollContent.ScrollBox:ScrollToBegin(); self:Refresh(); - IconBrowserCallbacks:TriggerEvent("OnBrowserOpened"); + self.callbacks:Fire("OnOpened"); end function TRP3_IconBrowserMixin:OnHide() - IconBrowserCallbacks:TriggerEvent("OnBrowserClosed"); + self.callbacks:Fire("OnClosed"); +end + +function TRP3_IconBrowserMixin:OnSearchStateChanged() + self:Refresh(); +end + +function TRP3_IconBrowserMixin:OnSearchProgressChanged() + self:Refresh(); +end + +function TRP3_IconBrowserMixin:OnModelUpdated() + self:Refresh(); end function TRP3_IconBrowserMixin:OnCloseButtonClicked() - TRP3_IconBrowserUtil.CloseBrowser(); + self:Hide(); end function TRP3_IconBrowserMixin:OnFilterTextChanged() - local query = self.SearchFilterEditBox:GetText(); - TRP3_IconBrowserUtil.BeginSearch(query); + self.model:SetSearchQuery(self.SearchBox:GetText()); end -function TRP3_IconBrowserMixin:OnSearchUpdated() - self:Refresh(); +function TRP3_IconBrowserMixin:OnIconButtonInitialized(button, iconInfo) + button:SetScript("OnClick", function() self:OnIconButtonClicked(button); end); + button:Init(iconInfo); +end + +function TRP3_IconBrowserMixin:OnIconButtonClicked(button) + local iconInfo = button:GetElementData(); + self.callbacks:Fire("OnIconSelected", iconInfo); + self:Hide(); end function TRP3_IconBrowserMixin:Refresh() - local searchInfo = TRP3_IconBrowserUtil.GetSearchInfo(); - local searchResultCount = TRP3_IconBrowserUtil.GetNumSearchResults(); + local filterCount = self.model:GetIconCount(); + local progress = self.model:GetSearchProgress(); + local state = self.model:GetSearchState(); - self.Title:SetText(L.UI_ICON_BROWSER); - self.SearchFilterEditBoxTitle:SetText(L.UI_FILTER); - self.SearchFilterTotalText:SetFormattedText(GENERIC_FRACTION_STRING, searchResultCount, searchInfo.total); - self.DataProvider:SetSize(searchResultCount); - self.ProgressBar:Refresh(); + self.ScrollContent.ProgressBar.Text:SetFormattedText(L.UI_ICON_BROWSER_SEARCHING, progress.searched / progress.total * 100); + + -- FIXME: Hacky hack hack + if self.x ~= state then + local reverse = (state == "running"); + self.ScrollContent.ProgressBar.AnimInOut:Play(reverse); + self.ScrollContent.Thing.AnimIn:Play(not reverse); + end + self.x=state + + self.ScrollContent.ProgressBar:SetValue(progress.searched / progress.total); + self.ScrollContent.EmptyText:SetShown(state == "finished" and filterCount == 0); end TRP3_IconBrowserButtonMixin = {}; @@ -282,41 +540,69 @@ function TRP3_IconBrowserButtonMixin:OnLoad() end function TRP3_IconBrowserButtonMixin:OnEnter() - TRP3_RefreshTooltipForFrame(self); + local iconInfo = self:GetElementData(); + + if not iconInfo then + return; + end + + local iconSizeSource = 64; + local iconSizeScaled = 32; + local titleLineIcon = CreateTextureMarkup(iconInfo.file, iconSizeSource, iconSizeSource, iconSizeScaled, iconSizeScaled, 0, 1, 0, 1); + local titleLineText = string.join(" ", titleLineIcon, iconInfo.name); + + TRP3_MainTooltip:SetOwner(self, "ANCHOR_RIGHT"); + GameTooltip_SetTitle(TRP3_MainTooltip, titleLineText, GREEN_FONT_COLOR, false); + TRP3_MainTooltip:Show(); end function TRP3_IconBrowserButtonMixin:OnLeave() TRP3_MainTooltip:Hide(); end -function TRP3_IconBrowserButtonMixin:OnClick() - local resultIndex = self:GetElementData(); - local resultInfo = TRP3_IconBrowserUtil.GetSearchResultInfo(resultIndex); - - IconBrowserCallbacks:TriggerEvent("OnBrowserIconSelected", resultInfo); - TRP3_IconBrowserUtil.CloseBrowser(); +---@param iconInfo TRP3.IconBrowserModelItem +function TRP3_IconBrowserButtonMixin:Init(iconInfo) + self.SelectedTexture:SetShown(iconInfo and iconInfo.selected); + self.Icon:SetTexture(iconInfo and iconInfo.file or [[INTERFACE\ICONS\INV_MISC_QUESTIONMARK]]); end -function TRP3_IconBrowserButtonMixin:Refresh() - local resultIndex = self:GetElementData(); - local resultInfo = TRP3_IconBrowserUtil.GetSearchResultInfo(resultIndex); +-- Browser API +------------------------------------------------------------------------------ + +TRP3_IconBrowser = {}; - self:SetNormalTexture(resultInfo.file); - self:SetPushedTexture(resultInfo.file); - TRP3_API.ui.tooltip.setTooltipForFrame(self, self, "RIGHT", 0, -100, TRP3_API.utils.str.icon(resultInfo.name, 75), resultInfo.name); +function TRP3_IconBrowser.Close() + TRP3_IconBrowserFrame:Hide(); end -TRP3_IconBrowserProgressBarMixin = {}; +function TRP3_IconBrowser.Open(options) + local owner = {}; -function TRP3_IconBrowserProgressBarMixin:OnShow() - self:Refresh(); -end + local function OnClosed() + TRP3_IconBrowserFrame.UnregisterAllCallbacks(owner); + + if options.onCancelCallback then + options.onCancelCallback(); + end + end + + local function OnIconSelected(_, iconInfo) + TRP3_IconBrowserFrame.UnregisterAllCallbacks(owner); + + if options.onAcceptCallback then + options.onAcceptCallback(iconInfo); + end + end -function TRP3_IconBrowserProgressBarMixin:Refresh() - local searchInfo = TRP3_IconBrowserUtil.GetSearchInfo(); + --- FIXME: Hacky hack hack + if type(options.selectedIcon) == "string" then + TRP3_IconBrowserFrame.selectionModel:SetSelectedIndex(LibRPMedia:GetIconIndexByName(options.selectedIcon)); + else + TRP3_IconBrowserFrame.selectionModel:SetSelectedIndex(nil); + end - self.Text:SetFormattedText(L.UI_ICON_BROWSER_SEARCHING, searchInfo.progress / searchInfo.total * 100) - self:SetShown(searchInfo.progress < searchInfo.total); - self:SetMinMaxValues(0, searchInfo.total); - self:SetValue(searchInfo.progress); + TRP3_IconBrowserFrame.RegisterCallback(owner, "OnClosed", OnClosed); + TRP3_IconBrowserFrame.RegisterCallback(owner, "OnIconSelected", OnIconSelected); + TRP3_IconBrowserFrame:SetScale(tonumber(options.scale) or 1); + TRP3_IconBrowserFrame:Show(); end diff --git a/totalRP3/UI/Browsers/IconBrowser.xml b/totalRP3/UI/Browsers/IconBrowser.xml index 4aa07f87a..ca4f93633 100644 --- a/totalRP3/UI/Browsers/IconBrowser.xml +++ b/totalRP3/UI/Browsers/IconBrowser.xml @@ -6,20 +6,78 @@ https://raw.githubusercontent.com/Meorawr/wow-ui-schema/main/UI.xsd"> - + - + + + + + + + + + + + + + + + + + + + + + + @@ -66,12 +124,15 @@ https://raw.githubusercontent.com/Meorawr/wow-ui-schema/main/UI.xsd"> - - - + + + + + + - + @@ -81,79 +142,98 @@ https://raw.githubusercontent.com/Meorawr/wow-ui-schema/main/UI.xsd"> - - + + - + + + + + - + + - - + + - + - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + - + - + - +