From 6d7c5d4f4ca369dd52a7d7ec51ef1dd8093d4c34 Mon Sep 17 00:00:00 2001 From: Vati Date: Thu, 16 Apr 2026 02:33:53 +0400 Subject: [PATCH] feat: search togglable and search by tooltip text ~t:{string} --- Core/Database.lua | 8 ++- Core/Utils.lua | 59 ++++++++++++++++++++ Guda.toc | 2 +- UI/BagFrame.lua | 96 ++++++++++++++++++++++++++++++--- UI/BagFrame.xml | 66 ++++++++++++++++++++++- UI/FrameHelpers.lua | 125 ++++++++++++++++++++++++++++++------------- UI/SettingsPopup.lua | 72 ++++++++++++++++++++++--- UI/SettingsPopup.xml | 19 +++++-- 8 files changed, 391 insertions(+), 56 deletions(-) diff --git a/Core/Database.lua b/Core/Database.lua index b5de537..526feb6 100644 --- a/Core/Database.lua +++ b/Core/Database.lua @@ -46,7 +46,8 @@ function DB:Initialize() iconFontSize = 12, showQualityBorderEquipment = true, showQualityBorderOther = true, - showSearchBar = true, + showSearchBar = true, -- legacy boolean; preserved for migration. New code reads searchBarMode. + searchBarMode = "shown", -- "shown" | "hidden" | "toggle" hideBagline = true, hideFooter = false, bgTransparency = 0.15, @@ -120,6 +121,11 @@ function DB:Initialize() if Guda_CharDB.settings.showSearchBar == nil then Guda_CharDB.settings.showSearchBar = true end + -- Migrate legacy boolean to three-state searchBarMode. + if Guda_CharDB.settings.searchBarMode == nil then + Guda_CharDB.settings.searchBarMode = + (Guda_CharDB.settings.showSearchBar == false) and "hidden" or "shown" + end if Guda_CharDB.settings.questBarPinnedItems == nil then Guda_CharDB.settings.questBarPinnedItems = {} end diff --git a/Core/Utils.lua b/Core/Utils.lua index 32fdddf..0d304fd 100644 --- a/Core/Utils.lua +++ b/Core/Utils.lua @@ -48,6 +48,7 @@ local tooltipCache = { bindOnEquip = {}, -- IsBindOnEquip results uniqueItem = {}, -- IsUniqueItem results restoreTag = {}, -- GetConsumableRestoreTag results + fullText = {}, -- GetTooltipText results (full tooltip lowercased, for ~t: search) } local tooltipCacheStats = { hits = 0, @@ -61,6 +62,7 @@ function Utils:ClearTooltipCache() tooltipCache.bindOnEquip = {} tooltipCache.uniqueItem = {} tooltipCache.restoreTag = {} + tooltipCache.fullText = {} tooltipCacheStats.hits = 0 tooltipCacheStats.misses = 0 addon:Debug("Tooltip cache cleared") @@ -1240,6 +1242,63 @@ function Utils:GetConsumableRestoreTag(bagID, slotID, itemLink) return tag end +-- Return the full tooltip text of an item as a single lowercase string, with +-- newlines between lines. Used by the ~t: keyword search filter — one scan +-- per item per session, cached by item ID so repeated searches are free. +-- Accepts (bagID, slotID) for live items or (nil, nil, itemLink) as a +-- link-only fallback (e.g. auction/mail items). +function Utils:GetTooltipText(bagID, slotID, itemLink) + local cacheLink = itemLink or (bagID and slotID and GetContainerItemLink(bagID, slotID)) + local cacheKey = GetTooltipCacheKey(cacheLink) + if cacheKey and tooltipCache.fullText[cacheKey] ~= nil then + tooltipCacheStats.hits = tooltipCacheStats.hits + 1 + local cached = tooltipCache.fullText[cacheKey] + return (cached ~= false) and cached or nil + end + tooltipCacheStats.misses = tooltipCacheStats.misses + 1 + + local tooltip = GetScanTooltip() + tooltip:ClearLines() + local ok = false + if bagID and slotID then + pcall(function() tooltip:SetBagItem(bagID, slotID); ok = true end) + end + if not ok and cacheLink then + local _, _, itemString = string.find(cacheLink, "|H(item:[^|]+)|h") + if itemString then + pcall(function() tooltip:SetHyperlink(itemString); ok = true end) + end + end + if not ok then + if cacheKey then tooltipCache.fullText[cacheKey] = false end + return nil + end + + local parts = {} + local n = tooltip:NumLines() or 0 + for i = 1, n do + local left = getglobal("GudaScanTooltipTextLeft" .. i) + local right = getglobal("GudaScanTooltipTextRight" .. i) + local lt = left and left:GetText() or nil + local rt = right and right:GetText() or nil + if lt and lt ~= "" then table.insert(parts, lt) end + if rt and rt ~= "" then table.insert(parts, rt) end + end + + -- Don't cache partial data: vanilla sometimes returns a 1-line tooltip + -- before the client finishes loading the item record. 2+ lines means + -- at least name + one stat/description line was present. + if n < 2 then + return nil + end + + local joined = string.lower(table.concat(parts, "\n")) + if cacheKey then + tooltipCache.fullText[cacheKey] = joined + end + return joined +end + -- Check if item is Arrow or Bullet (for Quiver routing). -- Locale-independent: also matches against the local client's translated -- subtype strings, resolved from reference vanilla items at runtime. diff --git a/Guda.toc b/Guda.toc index 18d4b7f..f270475 100644 --- a/Guda.toc +++ b/Guda.toc @@ -2,7 +2,7 @@ ## Title: Guda ## Notes: All-in-one bag and bank addon for World of Warcraft 1.12.1 (Turtle WoW) ## Author: Vati -## Version: 2.2.9 +## Version: 2.3.0 ## SavedVariables: Guda_DB ## SavedVariablesPerCharacter: Guda_CharDB ## OptionalDeps: pfUI diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index 3b74922..fb108ec 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -237,7 +237,9 @@ function Guda_BagFrame_OnShow(self) BagFrame:UpdateBorderVisibility() end - -- Apply search bar visibility setting + -- Apply search bar visibility setting. Always start collapsed in toggle + -- mode so opening the bag doesn't leave stale filter state visible. + BagFrame.searchBarExpanded = false if BagFrame.UpdateSearchBarVisibility then BagFrame:UpdateSearchBarVisibility() end @@ -3057,6 +3059,32 @@ function Guda_BagFrame_ClearSearch() BagFrame:Update() end +-- Queue a one-shot warmup of Utils:GetTooltipText for every occupied bag +-- slot currently visible. Runs once per session the first time the user +-- types a ~t: query — subsequent keystrokes hit warm cache and filter +-- instantly. Frame-budgeted via Utils:QueueWork so typing stays responsive. +local tooltipTextWarmed = false +local function WarmTooltipTextCache() + if tooltipTextWarmed then return end + tooltipTextWarmed = true + local Utils = addon.Modules.Utils + if not Utils or not Utils.QueueWork or not Utils.GetTooltipText then return end + for bagID = 0, 4 do + local numSlots = GetContainerNumSlots(bagID) + if numSlots and numSlots > 0 then + for slotID = 1, numSlots do + local link = GetContainerItemLink(bagID, slotID) + if link then + local b, s, l = bagID, slotID, link + Utils:QueueWork(function() + Utils:GetTooltipText(b, s, l) + end, "tooltipTextSearchWarmup") + end + end + end + end +end + -- Search changed handler function Guda_BagFrame_OnSearchChanged(self) local text = self:GetText() @@ -3069,6 +3097,12 @@ function Guda_BagFrame_OnSearchChanged(self) BagFrame.foundFirstMatch = false -- Reset debug flags BagFrame.warnedAboutParsing = false BagFrame.warnedAboutNoName = false + -- Kick off a one-time tooltip-text warmup if the query uses ~t:. + -- string.find returns nil when pattern is absent; plain=true to match + -- "~t:" literally (no magic chars in the pattern). + if string.find(text, "~t:", 1, true) then + WarmTooltipTextCache() + end BagFrame:Update() end end @@ -3677,29 +3711,75 @@ function BagFrame:UpdateBorderVisibility() end -- Update search bar visibility based on setting +-- Reads the three-state searchBarMode setting. +-- Returns one of "shown", "hidden", "toggle". Falls back to the legacy +-- boolean showSearchBar if the new setting isn't set yet. +local function GetSearchBarMode() + if not addon or not addon.Modules or not addon.Modules.DB then return "shown" end + local mode = addon.Modules.DB:GetSetting("searchBarMode") + if mode == "shown" or mode == "hidden" or mode == "toggle" then + return mode + end + local legacy = addon.Modules.DB:GetSetting("showSearchBar") + if legacy == false then return "hidden" end + return "shown" +end + function BagFrame:UpdateSearchBarVisibility() if not addon or not addon.Modules or not addon.Modules.DB then return end - local searchBar = getglobal("Guda_BagFrame_SearchBar") + local searchBar = getglobal("Guda_BagFrame_SearchBar") local itemContainer = getglobal("Guda_BagFrame_ItemContainer") + local toggleBtn = getglobal("Guda_BagFrame_SearchToggleButton") if not searchBar or not itemContainer then return end - local showSearchBar = addon.Modules.DB:GetSetting("showSearchBar") - if showSearchBar == nil then - showSearchBar = true + local mode = GetSearchBarMode() + local effectiveShown + if mode == "shown" then + effectiveShown = true + elseif mode == "hidden" then + effectiveShown = false + else -- "toggle" + effectiveShown = self.searchBarExpanded and true or false end - if showSearchBar then + if effectiveShown then searchBar:Show() - -- Anchor ItemContainer to SearchBar's bottom itemContainer:ClearAllPoints() itemContainer:SetPoint("TOP", searchBar, "BOTTOM", 0, -5) else searchBar:Hide() - -- Anchor ItemContainer directly to frame top (skip search bar space) itemContainer:ClearAllPoints() itemContainer:SetPoint("TOP", "Guda_BagFrame", "TOP", 0, -40) end + + if toggleBtn then + if mode == "toggle" then toggleBtn:Show() else toggleBtn:Hide() end + end +end + +-- Toggle the search bar's expanded state (only meaningful in "toggle" mode). +function BagFrame:ToggleSearchBar() + local mode = GetSearchBarMode() + if mode ~= "toggle" then return end -- no-op in shown/hidden modes + + self.searchBarExpanded = not (self.searchBarExpanded and true or false) + self:UpdateSearchBarVisibility() + + local searchBox = getglobal("Guda_BagFrame_SearchBar_SearchBox") + if self.searchBarExpanded then + if searchBox then searchBox:SetFocus() end + else + if searchBox then + searchBox:SetText("") + searchBox:ClearFocus() + if Guda_BagFrame_OnSearchChanged then + Guda_BagFrame_OnSearchChanged(searchBox) + end + end + end + + if BagFrame.Update then BagFrame:Update() end end -- Update footer visibility based on settings diff --git a/UI/BagFrame.xml b/UI/BagFrame.xml index 324a2f0..5606c0e 100644 --- a/UI/BagFrame.xml +++ b/UI/BagFrame.xml @@ -147,6 +147,56 @@ + + +