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">
-
-
+
+
-
+
+
+
+
+
-
+
+
-
-
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
-
+