From cadf154d0fb22b49d50c4666029e4924cfd8ac44 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Sun, 6 Sep 2026 08:54:46 +0200 Subject: [PATCH] refactor: consolidate small compatibility layers --- Core/InventoryIndex.lua | 90 +++++++++ Core/InventoryIndexSafety.lua | 86 --------- Core/ItemChargesClassicAPI.lua | 77 -------- Core/ItemDetectionCacheSafety.lua | 25 --- Core/ItemDetectionClassicAPI.lua | 110 +++++++++++ Data/BagScanner.lua | 49 +++++ Data/BankScanner.lua | 79 ++++++++ Data/CacheLifecycleSafety.lua | 75 -------- Data/ItemCategoryOverrides.lua | 45 ----- Guda.toc | 10 - UI/ItemButtonPerformanceClassicAPI.lua | 247 +++++++++++++++++++++++++ UI/QuestItemBar.lua | 109 +++++++++++ UI/QuestItemBarClassicAPI.lua | 37 ---- UI/QuestItemBarMoveSafety.lua | 64 ------- UI/TooltipPositionFix.lua | 110 ----------- UI/TrackedItemBarClassicAPI.lua | 73 ++++++++ UI/TrackedItemBarMoveSafety.lua | 69 ------- UI/UsabilityTintClassicAPI.lua | 129 ------------- 18 files changed, 757 insertions(+), 727 deletions(-) delete mode 100644 Core/InventoryIndexSafety.lua delete mode 100644 Core/ItemChargesClassicAPI.lua delete mode 100644 Core/ItemDetectionCacheSafety.lua delete mode 100644 Data/CacheLifecycleSafety.lua delete mode 100644 Data/ItemCategoryOverrides.lua delete mode 100644 UI/QuestItemBarClassicAPI.lua delete mode 100644 UI/QuestItemBarMoveSafety.lua delete mode 100644 UI/TooltipPositionFix.lua delete mode 100644 UI/TrackedItemBarMoveSafety.lua delete mode 100644 UI/UsabilityTintClassicAPI.lua diff --git a/Core/InventoryIndex.lua b/Core/InventoryIndex.lua index d283037..4febf48 100644 --- a/Core/InventoryIndex.lua +++ b/Core/InventoryIndex.lua @@ -445,3 +445,93 @@ function InventoryIndex:Initialize() addon:Debug("ClassicAPI inventory count index initialized") end + +--===================================================== +-- Consolidated from Core/InventoryIndexSafety.lua +--===================================================== +-- Guda ClassicAPI InventoryIndex mutation hooks +-- Ensures the O(1) tooltip index never stays stale after SavedVariables or +-- cross-character data changes that do not necessarily emit a fresh bag event. +-- Lua 5.0 compatible. + +local addon = Guda +local Index = addon.Modules.InventoryIndex +local DB = addon.Modules.DB +if not Index or not DB then return end + +local oldSaveBags = DB.SaveBags +local oldSaveBank = DB.SaveBank +local oldSaveEquipment = DB.SaveEquipment +local oldSaveMailbox = DB.SaveMailbox +local oldAddMailToCharacter = DB.AddMailToCharacter +local oldToggleGoldBlacklist = DB.ToggleGoldBlacklist +local oldRemoveCharacter = DB.RemoveCharacter +local oldCleanupOldCharacters = DB.CleanupOldCharacters + +if oldSaveBags then + function DB:SaveBags(bagData) + oldSaveBags(self, bagData) + Index:InvalidateCurrent() + end +end + +if oldSaveBank then + function DB:SaveBank(bankData) + oldSaveBank(self, bankData) + Index:InvalidateCurrent() + end +end + +if oldSaveEquipment then + function DB:SaveEquipment(equipmentData) + oldSaveEquipment(self, equipmentData) + Index:InvalidateCurrent() + end +end + +if oldSaveMailbox then + function DB:SaveMailbox(mailboxData) + oldSaveMailbox(self, mailboxData) + -- MAIL_INBOX_UPDATE can fire before MailboxScanner's debounced save. + -- Re-invalidating here guarantees the next tooltip sees the persisted + -- post-update mailbox snapshot rather than the pre-save one. + Index:InvalidateCurrent() + end +end + +if oldAddMailToCharacter then + function DB:AddMailToCharacter(name, realm, mailRow) + local added = oldAddMailToCharacter(self, name, realm, mailRow) + if added then + -- This can mutate a different local character's mailbox. + Index:InvalidateAll() + end + return added + end +end + +if oldToggleGoldBlacklist then + function DB:ToggleGoldBlacklist(fullName) + oldToggleGoldBlacklist(self, fullName) + Index:InvalidateAll() + end +end + +if oldRemoveCharacter then + function DB:RemoveCharacter(fullName) + local removed = oldRemoveCharacter(self, fullName) + if removed then + Index:InvalidateAll() + end + return removed + end +end + +if oldCleanupOldCharacters then + function DB:CleanupOldCharacters() + oldCleanupOldCharacters(self) + Index:InvalidateAll() + end +end + +addon:Debug("ClassicAPI inventory index mutation safety enabled") diff --git a/Core/InventoryIndexSafety.lua b/Core/InventoryIndexSafety.lua deleted file mode 100644 index e8efe2d..0000000 --- a/Core/InventoryIndexSafety.lua +++ /dev/null @@ -1,86 +0,0 @@ --- Guda ClassicAPI InventoryIndex mutation hooks --- Ensures the O(1) tooltip index never stays stale after SavedVariables or --- cross-character data changes that do not necessarily emit a fresh bag event. --- Lua 5.0 compatible. - -local addon = Guda -local Index = addon.Modules.InventoryIndex -local DB = addon.Modules.DB -if not Index or not DB then return end - -local oldSaveBags = DB.SaveBags -local oldSaveBank = DB.SaveBank -local oldSaveEquipment = DB.SaveEquipment -local oldSaveMailbox = DB.SaveMailbox -local oldAddMailToCharacter = DB.AddMailToCharacter -local oldToggleGoldBlacklist = DB.ToggleGoldBlacklist -local oldRemoveCharacter = DB.RemoveCharacter -local oldCleanupOldCharacters = DB.CleanupOldCharacters - -if oldSaveBags then - function DB:SaveBags(bagData) - oldSaveBags(self, bagData) - Index:InvalidateCurrent() - end -end - -if oldSaveBank then - function DB:SaveBank(bankData) - oldSaveBank(self, bankData) - Index:InvalidateCurrent() - end -end - -if oldSaveEquipment then - function DB:SaveEquipment(equipmentData) - oldSaveEquipment(self, equipmentData) - Index:InvalidateCurrent() - end -end - -if oldSaveMailbox then - function DB:SaveMailbox(mailboxData) - oldSaveMailbox(self, mailboxData) - -- MAIL_INBOX_UPDATE can fire before MailboxScanner's debounced save. - -- Re-invalidating here guarantees the next tooltip sees the persisted - -- post-update mailbox snapshot rather than the pre-save one. - Index:InvalidateCurrent() - end -end - -if oldAddMailToCharacter then - function DB:AddMailToCharacter(name, realm, mailRow) - local added = oldAddMailToCharacter(self, name, realm, mailRow) - if added then - -- This can mutate a different local character's mailbox. - Index:InvalidateAll() - end - return added - end -end - -if oldToggleGoldBlacklist then - function DB:ToggleGoldBlacklist(fullName) - oldToggleGoldBlacklist(self, fullName) - Index:InvalidateAll() - end -end - -if oldRemoveCharacter then - function DB:RemoveCharacter(fullName) - local removed = oldRemoveCharacter(self, fullName) - if removed then - Index:InvalidateAll() - end - return removed - end -end - -if oldCleanupOldCharacters then - function DB:CleanupOldCharacters() - oldCleanupOldCharacters(self) - Index:InvalidateAll() - end -end - -addon:Debug("ClassicAPI inventory index mutation safety enabled") diff --git a/Core/ItemChargesClassicAPI.lua b/Core/ItemChargesClassicAPI.lua deleted file mode 100644 index efd0863..0000000 --- a/Core/ItemChargesClassicAPI.lua +++ /dev/null @@ -1,77 +0,0 @@ --- Preserve Guda's original charge overlay behavior. --- ClassicAPI's container charge value is not reliable for distinguishing --- ordinary stack counts from explicit item charges on the target client, so --- only show the yellow "xN" overlay when the item tooltip actually contains a --- Charges line (matching upstream Guda behavior). - -local addon = Guda -local ItemDetection = addon.Modules.ItemDetection -if not ItemDetection then return end - -local chargesCache = {} - -local function SafeSetHyperlink(tooltip, link) - if not link then return false end - local _, _, bare = string.find(link, "|H(item:[^|]+)|h") - if not bare and string.find(link, "^item:") then bare = link end - if not bare then return false end - return pcall(tooltip.SetHyperlink, tooltip, bare) -end - -local function GetExplicitTooltipCharges(itemData, bagID, slotID) - local tooltip, tooltipName = addon.Modules.Utils:GetScanTooltip() - tooltip:SetOwner(WorldFrame, "ANCHOR_NONE") - tooltip:ClearLines() - - local ok = false - if bagID and slotID and bagID ~= -1 then - ok = pcall(tooltip.SetBagItem, tooltip, bagID, slotID) - end - if not ok then - ok = SafeSetHyperlink(tooltip, itemData and itemData.link) - end - if not ok then return nil, false end - - local numLines = tooltip:NumLines() or 0 - for i = 1, numLines do - local line = getglobal(tooltipName .. "TextLeft" .. i) - local text = line and line:GetText() - if text then - local _, _, num = string.find(string.lower(text), "^(%d+) charges?$") - if num then return tonumber(num), numLines >= 2 end - end - end - return nil, numLines >= 2 -end - -function ItemDetection:GetCharges(itemData, bagID, slotID) - if not bagID or not slotID then return nil end - - local slotKey = bagID .. ":" .. slotID - local cached = chargesCache[slotKey] - if cached ~= nil then - if cached == false then return nil end - return cached - end - - local charges, complete = GetExplicitTooltipCharges(itemData, bagID, slotID) - if complete then - chargesCache[slotKey] = charges or false - end - return charges -end - -function ItemDetection:InvalidateCharges(bagID) - if bagID then - local prefix = bagID .. ":" - for key in pairs(chargesCache) do - if string.find(key, "^" .. prefix) then - chargesCache[key] = nil - end - end - else - chargesCache = {} - end -end - -addon:Debug("Upstream-compatible charge display enabled") diff --git a/Core/ItemDetectionCacheSafety.lua b/Core/ItemDetectionCacheSafety.lua deleted file mode 100644 index ff7833e..0000000 --- a/Core/ItemDetectionCacheSafety.lua +++ /dev/null @@ -1,25 +0,0 @@ --- Guda ClassicAPI ItemDetection cache safety --- Keep the optimized detection cache and the legacy per-slot charge cache in --- sync when ClassicAPI charge access is unavailable. --- Lua 5.0 compatible. - -local addon = Guda -local ItemDetection = addon.Modules.ItemDetection -if not ItemDetection then return end - -local FastClearCache = ItemDetection.ClearCache - -function ItemDetection:ClearCache() - if FastClearCache then - FastClearCache(self) - end - - -- ItemDetectionClassicAPI keeps the original Vanilla charge-cache - -- invalidator behind this public method. With ClassicAPI charges present - -- it is a no-op; without them it clears the legacy bagID:slotID cache. - if self.InvalidateCharges then - self:InvalidateCharges(nil) - end -end - -addon:Debug("ClassicAPI ItemDetection cache safety enabled") diff --git a/Core/ItemDetectionClassicAPI.lua b/Core/ItemDetectionClassicAPI.lua index 31528a5..e63ab05 100644 --- a/Core/ItemDetectionClassicAPI.lua +++ b/Core/ItemDetectionClassicAPI.lua @@ -364,3 +364,113 @@ function ItemDetection:InvalidateCharges(bagID) end addon:Debug("ClassicAPI single-pass ItemDetection enabled") + +--===================================================== +-- Consolidated from Core/ItemChargesClassicAPI.lua +--===================================================== +-- Preserve Guda's original charge overlay behavior. +-- ClassicAPI's container charge value is not reliable for distinguishing +-- ordinary stack counts from explicit item charges on the target client, so +-- only show the yellow "xN" overlay when the item tooltip actually contains a +-- Charges line (matching upstream Guda behavior). + +local addon = Guda +local ItemDetection = addon.Modules.ItemDetection +if not ItemDetection then return end + +local chargesCache = {} + +local function SafeSetHyperlink(tooltip, link) + if not link then return false end + local _, _, bare = string.find(link, "|H(item:[^|]+)|h") + if not bare and string.find(link, "^item:") then bare = link end + if not bare then return false end + return pcall(tooltip.SetHyperlink, tooltip, bare) +end + +local function GetExplicitTooltipCharges(itemData, bagID, slotID) + local tooltip, tooltipName = addon.Modules.Utils:GetScanTooltip() + tooltip:SetOwner(WorldFrame, "ANCHOR_NONE") + tooltip:ClearLines() + + local ok = false + if bagID and slotID and bagID ~= -1 then + ok = pcall(tooltip.SetBagItem, tooltip, bagID, slotID) + end + if not ok then + ok = SafeSetHyperlink(tooltip, itemData and itemData.link) + end + if not ok then return nil, false end + + local numLines = tooltip:NumLines() or 0 + for i = 1, numLines do + local line = getglobal(tooltipName .. "TextLeft" .. i) + local text = line and line:GetText() + if text then + local _, _, num = string.find(string.lower(text), "^(%d+) charges?$") + if num then return tonumber(num), numLines >= 2 end + end + end + return nil, numLines >= 2 +end + +function ItemDetection:GetCharges(itemData, bagID, slotID) + if not bagID or not slotID then return nil end + + local slotKey = bagID .. ":" .. slotID + local cached = chargesCache[slotKey] + if cached ~= nil then + if cached == false then return nil end + return cached + end + + local charges, complete = GetExplicitTooltipCharges(itemData, bagID, slotID) + if complete then + chargesCache[slotKey] = charges or false + end + return charges +end + +function ItemDetection:InvalidateCharges(bagID) + if bagID then + local prefix = bagID .. ":" + for key in pairs(chargesCache) do + if string.find(key, "^" .. prefix) then + chargesCache[key] = nil + end + end + else + chargesCache = {} + end +end + +addon:Debug("Upstream-compatible charge display enabled") + +--===================================================== +-- Consolidated from Core/ItemDetectionCacheSafety.lua +--===================================================== +-- Guda ClassicAPI ItemDetection cache safety +-- Keep the optimized detection cache and the legacy per-slot charge cache in +-- sync when ClassicAPI charge access is unavailable. +-- Lua 5.0 compatible. + +local addon = Guda +local ItemDetection = addon.Modules.ItemDetection +if not ItemDetection then return end + +local FastClearCache = ItemDetection.ClearCache + +function ItemDetection:ClearCache() + if FastClearCache then + FastClearCache(self) + end + + -- ItemDetectionClassicAPI keeps the original Vanilla charge-cache + -- invalidator behind this public method. With ClassicAPI charges present + -- it is a no-op; without them it clears the legacy bagID:slotID cache. + if self.InvalidateCharges then + self:InvalidateCharges(nil) + end +end + +addon:Debug("ClassicAPI ItemDetection cache safety enabled") diff --git a/Data/BagScanner.lua b/Data/BagScanner.lua index b571fce..b1a8ab3 100644 --- a/Data/BagScanner.lua +++ b/Data/BagScanner.lua @@ -480,3 +480,52 @@ function BagScanner:Initialize() addon:Debug("Bag scanner initialized with ClassicAPI incremental cache") end + +--===================================================== +-- Consolidated from Data/ItemCategoryOverrides.lua +--===================================================== +-- Turtle WoW item category corrections for stale server-side item classifications. +-- Keep this list intentionally small and explicit to avoid affecting real quest items. +-- Lua 5.0 compatible. + +local addon = Guda +if not addon or not addon.Modules or not addon.Modules.BagScanner then return end + +local CATEGORY_OVERRIDES = { + [730] = "Trade Goods", -- Murloc Eye: no longer a quest item on Turtle WoW +} + +-- ItemDetection checks this table at runtime before treating API/tooltip data as +-- authoritative quest metadata. Extend the existing exclusions rather than +-- replacing them so the Juju fixes remain intact. +if addon.Constants then + addon.Constants.QUEST_CATEGORY_EXCLUSIONS = addon.Constants.QUEST_CATEGORY_EXCLUSIONS or {} + for itemID in pairs(CATEGORY_OVERRIDES) do + addon.Constants.QUEST_CATEGORY_EXCLUSIONS[itemID] = true + end +end + +local BagScanner = addon.Modules.BagScanner +local OriginalScanSlot = BagScanner.ScanSlot + +if OriginalScanSlot then + function BagScanner:ScanSlot(bagID, slotID) + local itemData = OriginalScanSlot(self, bagID, slotID) + if not itemData then return nil end + + local itemID = tonumber(itemData.itemID) + if not itemID and itemData.link and addon.Modules.Utils then + itemID = addon.Modules.Utils:ExtractItemID(itemData.link) + end + + local category = itemID and CATEGORY_OVERRIDES[itemID] or nil + if category then + itemData.class = category + itemData.type = category + end + + return itemData + end +end + +addon:Debug("Turtle WoW item category overrides enabled") diff --git a/Data/BankScanner.lua b/Data/BankScanner.lua index f1ef4a3..224e1e3 100644 --- a/Data/BankScanner.lua +++ b/Data/BankScanner.lua @@ -363,3 +363,82 @@ end function BankScanner:IsBankOpen() return bankOpen end + +--===================================================== +-- Consolidated from Data/CacheLifecycleSafety.lua +--===================================================== +-- Guda ClassicAPI cache lifecycle safety +-- Whole-cache invalidation is deferred to in-place dirty refreshes while the +-- corresponding UI is visible. This prevents pooled item tables still held by +-- visible buttons from being cleared/reused before their redraw runs. +-- Lua 5.0 compatible. + +local addon = Guda +local BagScanner = addon.Modules.BagScanner +local BankScanner = addon.Modules.BankScanner +if not BagScanner or not BankScanner then return end + +local oldBagClearCache = BagScanner.ClearCache +local oldBagInvalidateCache = BagScanner.InvalidateCache +local oldBankClearCache = BankScanner.ClearCache +local oldBankInvalidateCache = BankScanner.InvalidateCache + +local function IsShown(name) + local frame = getglobal(name) + return frame and frame.IsShown and frame:IsShown() +end + +local function MarkAllBagsDirty() + if addon.Constants and addon.Constants.BAGS then + for _, bagID in ipairs(addon.Constants.BAGS) do + BagScanner:InvalidateBag(bagID) + end + else + for bagID = 0, 4 do + BagScanner:InvalidateBag(bagID) + end + end + BagScanner:InvalidateBag(-2) +end + +local function MarkAllBankBagsDirty() + if addon.Constants and addon.Constants.BANK_BAGS then + for _, bagID in ipairs(addon.Constants.BANK_BAGS) do + BankScanner:InvalidateBag(bagID) + end + end +end + +function BagScanner:ClearCache() + if IsShown("Guda_BagFrame") then + MarkAllBagsDirty() + return + end + return oldBagClearCache(self) +end + +function BagScanner:InvalidateCache() + if IsShown("Guda_BagFrame") then + MarkAllBagsDirty() + return + end + return oldBagInvalidateCache(self) +end + +function BankScanner:ClearCache() + if BankScanner:IsBankOpen() and IsShown("Guda_BankFrame") then + MarkAllBankBagsDirty() + return + end + return oldBankClearCache(self) +end + +function BankScanner:InvalidateCache() + if BankScanner:IsBankOpen() and IsShown("Guda_BankFrame") then + MarkAllBankBagsDirty() + return + end + return oldBankInvalidateCache(self) +end + +addon:Debug("ClassicAPI cache lifecycle safety enabled") diff --git a/Data/CacheLifecycleSafety.lua b/Data/CacheLifecycleSafety.lua deleted file mode 100644 index 34d625a..0000000 --- a/Data/CacheLifecycleSafety.lua +++ /dev/null @@ -1,75 +0,0 @@ --- Guda ClassicAPI cache lifecycle safety --- Whole-cache invalidation is deferred to in-place dirty refreshes while the --- corresponding UI is visible. This prevents pooled item tables still held by --- visible buttons from being cleared/reused before their redraw runs. --- Lua 5.0 compatible. - -local addon = Guda -local BagScanner = addon.Modules.BagScanner -local BankScanner = addon.Modules.BankScanner -if not BagScanner or not BankScanner then return end - -local oldBagClearCache = BagScanner.ClearCache -local oldBagInvalidateCache = BagScanner.InvalidateCache -local oldBankClearCache = BankScanner.ClearCache -local oldBankInvalidateCache = BankScanner.InvalidateCache - -local function IsShown(name) - local frame = getglobal(name) - return frame and frame.IsShown and frame:IsShown() -end - -local function MarkAllBagsDirty() - if addon.Constants and addon.Constants.BAGS then - for _, bagID in ipairs(addon.Constants.BAGS) do - BagScanner:InvalidateBag(bagID) - end - else - for bagID = 0, 4 do - BagScanner:InvalidateBag(bagID) - end - end - BagScanner:InvalidateBag(-2) -end - -local function MarkAllBankBagsDirty() - if addon.Constants and addon.Constants.BANK_BAGS then - for _, bagID in ipairs(addon.Constants.BANK_BAGS) do - BankScanner:InvalidateBag(bagID) - end - end -end - -function BagScanner:ClearCache() - if IsShown("Guda_BagFrame") then - MarkAllBagsDirty() - return - end - return oldBagClearCache(self) -end - -function BagScanner:InvalidateCache() - if IsShown("Guda_BagFrame") then - MarkAllBagsDirty() - return - end - return oldBagInvalidateCache(self) -end - -function BankScanner:ClearCache() - if BankScanner:IsBankOpen() and IsShown("Guda_BankFrame") then - MarkAllBankBagsDirty() - return - end - return oldBankClearCache(self) -end - -function BankScanner:InvalidateCache() - if BankScanner:IsBankOpen() and IsShown("Guda_BankFrame") then - MarkAllBankBagsDirty() - return - end - return oldBankInvalidateCache(self) -end - -addon:Debug("ClassicAPI cache lifecycle safety enabled") diff --git a/Data/ItemCategoryOverrides.lua b/Data/ItemCategoryOverrides.lua deleted file mode 100644 index 533c9db..0000000 --- a/Data/ItemCategoryOverrides.lua +++ /dev/null @@ -1,45 +0,0 @@ --- Turtle WoW item category corrections for stale server-side item classifications. --- Keep this list intentionally small and explicit to avoid affecting real quest items. --- Lua 5.0 compatible. - -local addon = Guda -if not addon or not addon.Modules or not addon.Modules.BagScanner then return end - -local CATEGORY_OVERRIDES = { - [730] = "Trade Goods", -- Murloc Eye: no longer a quest item on Turtle WoW -} - --- ItemDetection checks this table at runtime before treating API/tooltip data as --- authoritative quest metadata. Extend the existing exclusions rather than --- replacing them so the Juju fixes remain intact. -if addon.Constants then - addon.Constants.QUEST_CATEGORY_EXCLUSIONS = addon.Constants.QUEST_CATEGORY_EXCLUSIONS or {} - for itemID in pairs(CATEGORY_OVERRIDES) do - addon.Constants.QUEST_CATEGORY_EXCLUSIONS[itemID] = true - end -end - -local BagScanner = addon.Modules.BagScanner -local OriginalScanSlot = BagScanner.ScanSlot - -if OriginalScanSlot then - function BagScanner:ScanSlot(bagID, slotID) - local itemData = OriginalScanSlot(self, bagID, slotID) - if not itemData then return nil end - - local itemID = tonumber(itemData.itemID) - if not itemID and itemData.link and addon.Modules.Utils then - itemID = addon.Modules.Utils:ExtractItemID(itemData.link) - end - - local category = itemID and CATEGORY_OVERRIDES[itemID] or nil - if category then - itemData.class = category - itemData.type = category - end - - return itemData - end -end - -addon:Debug("Turtle WoW item category overrides enabled") diff --git a/Guda.toc b/Guda.toc index 5db58b1..a04aeda 100644 --- a/Guda.toc +++ b/Guda.toc @@ -22,21 +22,16 @@ Core\ClassicAPI.lua Core\Performance.lua Core\ItemDetection.lua Core\ItemDetectionClassicAPI.lua -Core\ItemChargesClassicAPI.lua -Core\ItemDetectionCacheSafety.lua Core\CategoryManager.lua Core\ManualJunkAutoSell.lua Core\Tooltip.lua Core\InventoryIndex.lua -Core\InventoryIndexSafety.lua Core\ClamOpener.lua Core\AutoLoot.lua Core\CacheWarmer.lua Data\BagScanner.lua -Data\ItemCategoryOverrides.lua Data\BankScanner.lua -Data\CacheLifecycleSafety.lua Data\MailboxScanner.lua Data\MoneyTracker.lua Data\EquipmentScanner.lua @@ -48,18 +43,13 @@ Sorting\BagReplacer.lua # UI Lua must load before XML so OnLoad handlers exist UI\ItemButton.lua UI\ItemButtonPerformanceClassicAPI.lua -UI\UsabilityTintClassicAPI.lua -UI\TooltipPositionFix.lua UI\FrameHelpers.lua UI\BagFrame.lua UI\BankFrame.lua UI\MailboxFrame.lua UI\QuestItemBar.lua -UI\QuestItemBarClassicAPI.lua -UI\QuestItemBarMoveSafety.lua UI\TrackedItemBar.lua UI\TrackedItemBarClassicAPI.lua -UI\TrackedItemBarMoveSafety.lua UI\SettingsPopup.lua UI\ItemButton.xml UI\BagFrame.xml diff --git a/UI/ItemButtonPerformanceClassicAPI.lua b/UI/ItemButtonPerformanceClassicAPI.lua index 7ad10f7..d9c2d2f 100644 --- a/UI/ItemButtonPerformanceClassicAPI.lua +++ b/UI/ItemButtonPerformanceClassicAPI.lua @@ -254,3 +254,250 @@ initFrame:SetScript("OnEvent", function() this:UnregisterEvent("PLAYER_ENTERING_WORLD") end end) + +--===================================================== +-- Consolidated from UI/UsabilityTintClassicAPI.lua +--===================================================== +-- Guda ClassicAPI usability tint safety +-- Re-applies unusable-item tint after sorting/moves without synchronous mass scans. +-- Lua 5.0 compatible. + +local addon = Guda +if not addon or not Guda_ItemButton_SetItem then return end + +local OriginalSetItem = Guda_ItemButton_SetItem + +local function EnsureUnusableOverlay(button) + if button.unusableOverlay then return button.unusableOverlay end + + local overlay = button:CreateTexture(nil, "OVERLAY") + overlay:SetAllPoints(button) + overlay:SetTexture("Interface\\ChatFrame\\ChatFrameBackground") + overlay:Hide() + button.unusableOverlay = overlay + return overlay +end + +-- Mirror ItemButton.lua's unusable color selection exactly. The original helper +-- is local to ItemButton.lua, so this safety layer cannot call it directly. +local function GetUnusableColor() + if pfUI and C and C.appearance and C.appearance.bags and C.appearance.bags.unusable_color then + local cr, cg, cb, ca = strsplit(",", C.appearance.bags.unusable_color) + local r = tonumber(cr) or 0.9 + local g = tonumber(cg) or 0.2 + local b = tonumber(cb) or 0.2 + local a = tonumber(ca) or 1.0 + return r, g, b, a + end + + if RED_FONT_COLOR then + return RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, 1.0 + end + + return 0.9, 0.2, 0.2, 1.0 +end + +local function ApplyUnusableTint(button, unusable) + if not button then return end + + local markUnusable = true + if addon.Modules and addon.Modules.DB then + local setting = addon.Modules.DB:GetSetting("markUnusableItems") + if setting ~= nil then markUnusable = setting and true or false end + end + + local overlay = button.unusableOverlay + if not markUnusable or not unusable then + if overlay then overlay:Hide() end + return + end + + overlay = EnsureUnusableOverlay(button) + + local r, g, b, a = GetUnusableColor() + -- Match ItemButton.lua exactly: configured alpha is reduced once by 45%. + local alpha = (a or 1.0) * 0.45 + overlay:SetVertexColor(r or 0.9, g or 0.2, b or 0.2, alpha) + overlay:Show() +end + +local function RefreshUsabilityAsync(button, itemData, bagID, slotID, isReadOnly, otherCharName) + if not button or not itemData or not itemData.link then return end + if isReadOnly or otherCharName then return end + + local detection = addon.Modules and addon.Modules.ItemDetection + if not detection then return end + + -- Never scan tooltip state while the sort engine is actively moving/locking items. + -- The final post-sort redraw will call SetItem again and schedule a safe refresh. + if addon.Modules.SortEngine and addon.Modules.SortEngine.sortingInProgress then + return + end + + local link = itemData.link + local cached = detection.IsUnusableCached and detection:IsUnusableCached(itemData) or nil + if cached ~= nil then + ApplyUnusableTint(button, cached) + return + end + + -- Avoid duplicate jobs when BAG_UPDATE causes several redraws for the same item. + if button._gudaUsabilityPendingLink == link then return end + button._gudaUsabilityPendingLink = link + + local function DoRefresh() + if button._gudaUsabilityPendingLink ~= link then return end + button._gudaUsabilityPendingLink = nil + + if not button.IsShown or not button:IsShown() or not button.hasItem then return end + if button.otherChar or button.isReadOnly then return end + if button.bagID ~= bagID or button.slotID ~= slotID then return end + if not button.itemData or button.itemData.link ~= link then return end + + local props = detection:GetItemProperties(button.itemData, button.bagID, button.slotID) + ApplyUnusableTint(button, props and props.isUnusable) + end + + local Utils = addon.Modules and addon.Modules.Utils + if Utils and Utils.QueueWork then + Utils:QueueWork(DoRefresh) + else + DoRefresh() + end +end + +function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly) + OriginalSetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly) + + if not self then return end + + local currentData = self.itemData or itemData + local currentLink = currentData and currentData.link or nil + + -- Cancel a stale pending job if this pooled button now represents another item. + if self._gudaUsabilityPendingLink and self._gudaUsabilityPendingLink ~= currentLink then + self._gudaUsabilityPendingLink = nil + end + + if self.hasItem and currentData then + RefreshUsabilityAsync(self, currentData, tonumber(bagID), tonumber(slotID), isReadOnly, otherCharName) + elseif self.unusableOverlay then + self.unusableOverlay:Hide() + end +end + +addon:Debug("ClassicAPI usability tint safety enabled") + +--===================================================== +-- Consolidated from UI/TooltipPositionFix.lua +--===================================================== +-- Guda item/comparison tooltip positioning fix. +-- Keeps Guda item tooltips toward the inside of the screen so comparison +-- tooltips have room to render with pfUI/DFUI and the ClassicAPI tooltip code. + +local addon = Guda +if not addon or not Guda_ItemButton_OnEnter then return end + +local originalOnEnter = Guda_ItemButton_OnEnter + +local function IsCursorAnchored() + if GameTooltip and GameTooltip.GetAnchorType then + return GameTooltip:GetAnchorType() == "ANCHOR_CURSOR" + end + + -- Compatibility with pfUI versions that expose cursor positioning only + -- through their configuration table. + if pfUI and pfUI.env and pfUI.env.C and pfUI.env.C.tooltip then + return pfUI.env.C.tooltip.position == "cursor" + end + + return false +end + +local function PositionMainTooltip(button) + if not button or not GameTooltip or not GameTooltip:IsShown() then return end + if IsCursorAnchored() then return end + + local centerX = button.GetCenter and button:GetCenter() + local screenWidth = UIParent and UIParent.GetWidth and UIParent:GetWidth() + if not centerX or not screenWidth or screenWidth <= 0 then return end + + GameTooltip:ClearAllPoints() + + if centerX < (screenWidth / 2) then + -- Item is on the left: grow the tooltip group toward the right. + GameTooltip:SetPoint("BOTTOMLEFT", button, "TOPRIGHT", -10, 0) + else + -- Preserve Guda's historical position for items on the right. + GameTooltip:SetPoint("BOTTOMRIGHT", button, "TOPLEFT", 10, 0) + end +end + +local function GetComparisonGap(tooltip) + local separation = 6 + local edgeSize = 0 + + if tooltip and tooltip.GetBackdrop then + local backdrop = tooltip:GetBackdrop() + if type(backdrop) == "table" and backdrop.edgeSize then + edgeSize = backdrop.edgeSize + end + end + + return separation + edgeSize +end + +local function PositionComparisonTooltips() + if not GameTooltip or not GameTooltip:IsShown() then return end + + local first = ShoppingTooltip1 + local second = ShoppingTooltip2 + if not first or not first:IsShown() then return end + + local screenWidth = UIParent and UIParent.GetWidth and UIParent:GetWidth() + local left = GameTooltip.GetLeft and GameTooltip:GetLeft() + local right = GameTooltip.GetRight and GameTooltip:GetRight() + if not screenWidth or not left or not right then return end + + local leftSpace = left + local rightSpace = screenWidth - right + local useLeft = leftSpace > rightSpace + local gap = GetComparisonGap(first) + + first:SetOwner(GameTooltip, "ANCHOR_NONE") + first:ClearAllPoints() + if useLeft then + first:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", -gap, -10) + else + first:SetPoint("TOPLEFT", GameTooltip, "TOPRIGHT", gap, -10) + end + + if second and second:IsShown() then + second:SetOwner(first, "ANCHOR_NONE") + second:ClearAllPoints() + if useLeft then + second:SetPoint("TOPRIGHT", first, "TOPLEFT", -gap, 0) + else + second:SetPoint("TOPLEFT", first, "TOPRIGHT", gap, 0) + end + end +end + +function Guda_ItemButton_OnEnter(self) + originalOnEnter(self) + + -- Empty/drop-target buttons either have no item tooltip or use a small + -- custom tooltip that does not participate in equipment comparison. + if not self or self.isDropTarget or not self.hasItem then return end + if not GameTooltip or not GameTooltip:IsShown() then return end + if IsCursorAnchored() then return end + + PositionMainTooltip(self) + + -- Comparison tooltips may already have been shown synchronously by a + -- tooltip hook. Re-anchor them now; if they are shown later, ClassicAPI's + -- own side selection will use the corrected GameTooltip position. + PositionComparisonTooltips() +end + +addon:Debug("Guda comparison tooltip positioning fix enabled") diff --git a/UI/QuestItemBar.lua b/UI/QuestItemBar.lua index 20b36d5..abab690 100644 --- a/UI/QuestItemBar.lua +++ b/UI/QuestItemBar.lua @@ -833,3 +833,112 @@ function QuestItemBar:Debug() end QuestItemBar.isLoaded = true + +--===================================================== +-- Consolidated from UI/QuestItemBarClassicAPI.lua +--===================================================== +-- Guda ClassicAPI Quest Item Bar fast path +-- Keeps the upstream UI/flyout logic but reuses BagScanner item data so the +-- quest detector does not perform a second container scan for each item. + +local addon = Guda +local QuestItemBar = addon.Modules.QuestItemBar +if not QuestItemBar then return end + +function QuestItemBar:CheckQuestItemUsable(bagID, slotID) + if not bagID or not slotID then return false, false, false end + + local itemData = nil + if addon.Modules.BagScanner and addon.Modules.BagScanner.GetBagData then + local bagData = addon.Modules.BagScanner:GetBagData() + local bag = bagData and bagData[bagID] + itemData = bag and bag.slots and bag.slots[slotID] + end + + if addon.Modules.ItemDetection and itemData then + local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID) + if props then + return props.isQuestItem, props.isQuestStarter, props.isQuestUsable + end + end + + -- Preserve the upstream fallback if the slot is not represented in the + -- shared snapshot for any reason. + if addon.Modules.Utils and addon.Modules.Utils.IsQuestItem then + local isQuestItem, isQuestStarter = + addon.Modules.Utils:IsQuestItem(bagID, slotID, itemData and itemData.link, false, false) + return isQuestItem, isQuestStarter, isQuestItem + end + + return false, false, false +end + +addon:Debug("QuestItemBar ClassicAPI snapshot detector loaded") + +--===================================================== +-- Consolidated from UI/QuestItemBarMoveSafety.lua +--===================================================== +-- Guda Quest Item Bar movement safety +-- Prevents structural Quest Bar refreshes while the frame is being dragged. +-- Lua 5.0 compatible. + +local addon = Guda +if not addon or not addon.Modules or not addon.Modules.QuestItemBar then return end + +local QuestItemBar = addon.Modules.QuestItemBar +if not QuestItemBar.Update then return end + +local OriginalUpdate = QuestItemBar.Update + +local function FlushDeferredUpdate(barFrame) + if not barFrame or barFrame.isMoving then return end + if not barFrame._gudaQuestBarDeferredUpdate then return end + + barFrame._gudaQuestBarDeferredUpdate = nil + QuestItemBar:Update() +end + +local function WrapMouseUp(frame, barFrame) + if not frame or frame._gudaQuestBarMoveSafetyWrapped then return end + + local originalMouseUp = frame:GetScript("OnMouseUp") + frame._gudaQuestBarMoveSafetyWrapped = true + + frame:SetScript("OnMouseUp", function() + if originalMouseUp then + originalMouseUp() + end + + FlushDeferredUpdate(barFrame or Guda_QuestItemBar) + end) +end + +local function EnsureMoveSafetyHooks() + local barFrame = Guda_QuestItemBar + if not barFrame then return end + + -- The bar itself can be dragged from its empty area. + WrapMouseUp(barFrame, barFrame) + + -- The two visible quest item buttons also start/stop movement on behalf + -- of their parent frame. Wrap them after they are created by Update(). + WrapMouseUp(getglobal("Guda_QuestItemBarButton1"), barFrame) + WrapMouseUp(getglobal("Guda_QuestItemBarButton2"), barFrame) +end + +function QuestItemBar:Update() + local barFrame = Guda_QuestItemBar + + -- BAG_UPDATE and other events can arrive while StartMoving() is active. + -- Rebuilding/hiding/resizing the frame in that state is unsafe on 1.12. + -- Coalesce any number of refresh requests into one update after mouse-up. + if barFrame and barFrame.isMoving then + barFrame._gudaQuestBarDeferredUpdate = true + return + end + + OriginalUpdate(self) + EnsureMoveSafetyHooks() +end + +addon:Debug("QuestItemBar movement safety enabled") diff --git a/UI/QuestItemBarClassicAPI.lua b/UI/QuestItemBarClassicAPI.lua deleted file mode 100644 index 8648be5..0000000 --- a/UI/QuestItemBarClassicAPI.lua +++ /dev/null @@ -1,37 +0,0 @@ --- Guda ClassicAPI Quest Item Bar fast path --- Keeps the upstream UI/flyout logic but reuses BagScanner item data so the --- quest detector does not perform a second container scan for each item. - -local addon = Guda -local QuestItemBar = addon.Modules.QuestItemBar -if not QuestItemBar then return end - -function QuestItemBar:CheckQuestItemUsable(bagID, slotID) - if not bagID or not slotID then return false, false, false end - - local itemData = nil - if addon.Modules.BagScanner and addon.Modules.BagScanner.GetBagData then - local bagData = addon.Modules.BagScanner:GetBagData() - local bag = bagData and bagData[bagID] - itemData = bag and bag.slots and bag.slots[slotID] - end - - if addon.Modules.ItemDetection and itemData then - local props = addon.Modules.ItemDetection:GetItemProperties(itemData, bagID, slotID) - if props then - return props.isQuestItem, props.isQuestStarter, props.isQuestUsable - end - end - - -- Preserve the upstream fallback if the slot is not represented in the - -- shared snapshot for any reason. - if addon.Modules.Utils and addon.Modules.Utils.IsQuestItem then - local isQuestItem, isQuestStarter = - addon.Modules.Utils:IsQuestItem(bagID, slotID, itemData and itemData.link, false, false) - return isQuestItem, isQuestStarter, isQuestItem - end - - return false, false, false -end - -addon:Debug("QuestItemBar ClassicAPI snapshot detector loaded") diff --git a/UI/QuestItemBarMoveSafety.lua b/UI/QuestItemBarMoveSafety.lua deleted file mode 100644 index 02cdd25..0000000 --- a/UI/QuestItemBarMoveSafety.lua +++ /dev/null @@ -1,64 +0,0 @@ --- Guda Quest Item Bar movement safety --- Prevents structural Quest Bar refreshes while the frame is being dragged. --- Lua 5.0 compatible. - -local addon = Guda -if not addon or not addon.Modules or not addon.Modules.QuestItemBar then return end - -local QuestItemBar = addon.Modules.QuestItemBar -if not QuestItemBar.Update then return end - -local OriginalUpdate = QuestItemBar.Update - -local function FlushDeferredUpdate(barFrame) - if not barFrame or barFrame.isMoving then return end - if not barFrame._gudaQuestBarDeferredUpdate then return end - - barFrame._gudaQuestBarDeferredUpdate = nil - QuestItemBar:Update() -end - -local function WrapMouseUp(frame, barFrame) - if not frame or frame._gudaQuestBarMoveSafetyWrapped then return end - - local originalMouseUp = frame:GetScript("OnMouseUp") - frame._gudaQuestBarMoveSafetyWrapped = true - - frame:SetScript("OnMouseUp", function() - if originalMouseUp then - originalMouseUp() - end - - FlushDeferredUpdate(barFrame or Guda_QuestItemBar) - end) -end - -local function EnsureMoveSafetyHooks() - local barFrame = Guda_QuestItemBar - if not barFrame then return end - - -- The bar itself can be dragged from its empty area. - WrapMouseUp(barFrame, barFrame) - - -- The two visible quest item buttons also start/stop movement on behalf - -- of their parent frame. Wrap them after they are created by Update(). - WrapMouseUp(getglobal("Guda_QuestItemBarButton1"), barFrame) - WrapMouseUp(getglobal("Guda_QuestItemBarButton2"), barFrame) -end - -function QuestItemBar:Update() - local barFrame = Guda_QuestItemBar - - -- BAG_UPDATE and other events can arrive while StartMoving() is active. - -- Rebuilding/hiding/resizing the frame in that state is unsafe on 1.12. - -- Coalesce any number of refresh requests into one update after mouse-up. - if barFrame and barFrame.isMoving then - barFrame._gudaQuestBarDeferredUpdate = true - return - end - - OriginalUpdate(self) - EnsureMoveSafetyHooks() -end - -addon:Debug("QuestItemBar movement safety enabled") diff --git a/UI/TooltipPositionFix.lua b/UI/TooltipPositionFix.lua deleted file mode 100644 index cadf657..0000000 --- a/UI/TooltipPositionFix.lua +++ /dev/null @@ -1,110 +0,0 @@ --- Guda item/comparison tooltip positioning fix. --- Keeps Guda item tooltips toward the inside of the screen so comparison --- tooltips have room to render with pfUI/DFUI and the ClassicAPI tooltip code. - -local addon = Guda -if not addon or not Guda_ItemButton_OnEnter then return end - -local originalOnEnter = Guda_ItemButton_OnEnter - -local function IsCursorAnchored() - if GameTooltip and GameTooltip.GetAnchorType then - return GameTooltip:GetAnchorType() == "ANCHOR_CURSOR" - end - - -- Compatibility with pfUI versions that expose cursor positioning only - -- through their configuration table. - if pfUI and pfUI.env and pfUI.env.C and pfUI.env.C.tooltip then - return pfUI.env.C.tooltip.position == "cursor" - end - - return false -end - -local function PositionMainTooltip(button) - if not button or not GameTooltip or not GameTooltip:IsShown() then return end - if IsCursorAnchored() then return end - - local centerX = button.GetCenter and button:GetCenter() - local screenWidth = UIParent and UIParent.GetWidth and UIParent:GetWidth() - if not centerX or not screenWidth or screenWidth <= 0 then return end - - GameTooltip:ClearAllPoints() - - if centerX < (screenWidth / 2) then - -- Item is on the left: grow the tooltip group toward the right. - GameTooltip:SetPoint("BOTTOMLEFT", button, "TOPRIGHT", -10, 0) - else - -- Preserve Guda's historical position for items on the right. - GameTooltip:SetPoint("BOTTOMRIGHT", button, "TOPLEFT", 10, 0) - end -end - -local function GetComparisonGap(tooltip) - local separation = 6 - local edgeSize = 0 - - if tooltip and tooltip.GetBackdrop then - local backdrop = tooltip:GetBackdrop() - if type(backdrop) == "table" and backdrop.edgeSize then - edgeSize = backdrop.edgeSize - end - end - - return separation + edgeSize -end - -local function PositionComparisonTooltips() - if not GameTooltip or not GameTooltip:IsShown() then return end - - local first = ShoppingTooltip1 - local second = ShoppingTooltip2 - if not first or not first:IsShown() then return end - - local screenWidth = UIParent and UIParent.GetWidth and UIParent:GetWidth() - local left = GameTooltip.GetLeft and GameTooltip:GetLeft() - local right = GameTooltip.GetRight and GameTooltip:GetRight() - if not screenWidth or not left or not right then return end - - local leftSpace = left - local rightSpace = screenWidth - right - local useLeft = leftSpace > rightSpace - local gap = GetComparisonGap(first) - - first:SetOwner(GameTooltip, "ANCHOR_NONE") - first:ClearAllPoints() - if useLeft then - first:SetPoint("TOPRIGHT", GameTooltip, "TOPLEFT", -gap, -10) - else - first:SetPoint("TOPLEFT", GameTooltip, "TOPRIGHT", gap, -10) - end - - if second and second:IsShown() then - second:SetOwner(first, "ANCHOR_NONE") - second:ClearAllPoints() - if useLeft then - second:SetPoint("TOPRIGHT", first, "TOPLEFT", -gap, 0) - else - second:SetPoint("TOPLEFT", first, "TOPRIGHT", gap, 0) - end - end -end - -function Guda_ItemButton_OnEnter(self) - originalOnEnter(self) - - -- Empty/drop-target buttons either have no item tooltip or use a small - -- custom tooltip that does not participate in equipment comparison. - if not self or self.isDropTarget or not self.hasItem then return end - if not GameTooltip or not GameTooltip:IsShown() then return end - if IsCursorAnchored() then return end - - PositionMainTooltip(self) - - -- Comparison tooltips may already have been shown synchronously by a - -- tooltip hook. Re-anchor them now; if they are shown later, ClassicAPI's - -- own side selection will use the corrected GameTooltip position. - PositionComparisonTooltips() -end - -addon:Debug("Guda comparison tooltip positioning fix enabled") diff --git a/UI/TrackedItemBarClassicAPI.lua b/UI/TrackedItemBarClassicAPI.lua index 6cbbf49..9cec1a7 100644 --- a/UI/TrackedItemBarClassicAPI.lua +++ b/UI/TrackedItemBarClassicAPI.lua @@ -305,3 +305,76 @@ function TrackedItemBar:Update() end addon:Debug("TrackedItemBar ClassicAPI snapshot consumer loaded") + +--===================================================== +-- Consolidated from UI/TrackedItemBarMoveSafety.lua +--===================================================== +-- Guda Tracked Item Bar movement safety +-- Prevents structural Tracked Item Bar refreshes while the frame is being dragged. +-- Lua 5.0 compatible. + +local addon = Guda +if not addon or not addon.Modules or not addon.Modules.TrackedItemBar then return end + +local TrackedItemBar = addon.Modules.TrackedItemBar +if not TrackedItemBar.Update then return end + +local OriginalUpdate = TrackedItemBar.Update + +local function FlushDeferredUpdate(barFrame) + if not barFrame or barFrame.isMoving then return end + if not barFrame._gudaTrackedBarDeferredUpdate then return end + + barFrame._gudaTrackedBarDeferredUpdate = nil + TrackedItemBar:Update() +end + +local function WrapMouseUp(frame, barFrame) + if not frame or frame._gudaTrackedBarMoveSafetyWrapped then return end + + local originalMouseUp = frame:GetScript("OnMouseUp") + frame._gudaTrackedBarMoveSafetyWrapped = true + + frame:SetScript("OnMouseUp", function() + if originalMouseUp then + originalMouseUp() + end + + FlushDeferredUpdate(barFrame or Guda_TrackedItemBar) + end) +end + +local function EnsureMoveSafetyHooks() + local barFrame = Guda_TrackedItemBar + if not barFrame then return end + + -- The bar itself can be dragged from its empty area. + WrapMouseUp(barFrame, barFrame) + + -- Tracked-item buttons can also start/stop movement on behalf of the bar. + -- Buttons are created dynamically, so wrap every currently existing one. + local i = 1 + while true do + local button = getglobal("Guda_TrackedItemBarButton" .. i) + if not button then break end + WrapMouseUp(button, barFrame) + i = i + 1 + end +end + +function TrackedItemBar:Update() + local barFrame = Guda_TrackedItemBar + + -- BAG_UPDATE, PLAYER_LEVEL_UP and other refreshes can arrive while + -- StartMoving() is active. Rebuilding/hiding/resizing the frame in that + -- state is unsafe on 1.12, so coalesce them into one post-drag refresh. + if barFrame and barFrame.isMoving then + barFrame._gudaTrackedBarDeferredUpdate = true + return + end + + OriginalUpdate(self) + EnsureMoveSafetyHooks() +end + +addon:Debug("TrackedItemBar movement safety enabled") diff --git a/UI/TrackedItemBarMoveSafety.lua b/UI/TrackedItemBarMoveSafety.lua deleted file mode 100644 index 5dd2cc3..0000000 --- a/UI/TrackedItemBarMoveSafety.lua +++ /dev/null @@ -1,69 +0,0 @@ --- Guda Tracked Item Bar movement safety --- Prevents structural Tracked Item Bar refreshes while the frame is being dragged. --- Lua 5.0 compatible. - -local addon = Guda -if not addon or not addon.Modules or not addon.Modules.TrackedItemBar then return end - -local TrackedItemBar = addon.Modules.TrackedItemBar -if not TrackedItemBar.Update then return end - -local OriginalUpdate = TrackedItemBar.Update - -local function FlushDeferredUpdate(barFrame) - if not barFrame or barFrame.isMoving then return end - if not barFrame._gudaTrackedBarDeferredUpdate then return end - - barFrame._gudaTrackedBarDeferredUpdate = nil - TrackedItemBar:Update() -end - -local function WrapMouseUp(frame, barFrame) - if not frame or frame._gudaTrackedBarMoveSafetyWrapped then return end - - local originalMouseUp = frame:GetScript("OnMouseUp") - frame._gudaTrackedBarMoveSafetyWrapped = true - - frame:SetScript("OnMouseUp", function() - if originalMouseUp then - originalMouseUp() - end - - FlushDeferredUpdate(barFrame or Guda_TrackedItemBar) - end) -end - -local function EnsureMoveSafetyHooks() - local barFrame = Guda_TrackedItemBar - if not barFrame then return end - - -- The bar itself can be dragged from its empty area. - WrapMouseUp(barFrame, barFrame) - - -- Tracked-item buttons can also start/stop movement on behalf of the bar. - -- Buttons are created dynamically, so wrap every currently existing one. - local i = 1 - while true do - local button = getglobal("Guda_TrackedItemBarButton" .. i) - if not button then break end - WrapMouseUp(button, barFrame) - i = i + 1 - end -end - -function TrackedItemBar:Update() - local barFrame = Guda_TrackedItemBar - - -- BAG_UPDATE, PLAYER_LEVEL_UP and other refreshes can arrive while - -- StartMoving() is active. Rebuilding/hiding/resizing the frame in that - -- state is unsafe on 1.12, so coalesce them into one post-drag refresh. - if barFrame and barFrame.isMoving then - barFrame._gudaTrackedBarDeferredUpdate = true - return - end - - OriginalUpdate(self) - EnsureMoveSafetyHooks() -end - -addon:Debug("TrackedItemBar movement safety enabled") diff --git a/UI/UsabilityTintClassicAPI.lua b/UI/UsabilityTintClassicAPI.lua deleted file mode 100644 index d64007d..0000000 --- a/UI/UsabilityTintClassicAPI.lua +++ /dev/null @@ -1,129 +0,0 @@ --- Guda ClassicAPI usability tint safety --- Re-applies unusable-item tint after sorting/moves without synchronous mass scans. --- Lua 5.0 compatible. - -local addon = Guda -if not addon or not Guda_ItemButton_SetItem then return end - -local OriginalSetItem = Guda_ItemButton_SetItem - -local function EnsureUnusableOverlay(button) - if button.unusableOverlay then return button.unusableOverlay end - - local overlay = button:CreateTexture(nil, "OVERLAY") - overlay:SetAllPoints(button) - overlay:SetTexture("Interface\\ChatFrame\\ChatFrameBackground") - overlay:Hide() - button.unusableOverlay = overlay - return overlay -end - --- Mirror ItemButton.lua's unusable color selection exactly. The original helper --- is local to ItemButton.lua, so this safety layer cannot call it directly. -local function GetUnusableColor() - if pfUI and C and C.appearance and C.appearance.bags and C.appearance.bags.unusable_color then - local cr, cg, cb, ca = strsplit(",", C.appearance.bags.unusable_color) - local r = tonumber(cr) or 0.9 - local g = tonumber(cg) or 0.2 - local b = tonumber(cb) or 0.2 - local a = tonumber(ca) or 1.0 - return r, g, b, a - end - - if RED_FONT_COLOR then - return RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, 1.0 - end - - return 0.9, 0.2, 0.2, 1.0 -end - -local function ApplyUnusableTint(button, unusable) - if not button then return end - - local markUnusable = true - if addon.Modules and addon.Modules.DB then - local setting = addon.Modules.DB:GetSetting("markUnusableItems") - if setting ~= nil then markUnusable = setting and true or false end - end - - local overlay = button.unusableOverlay - if not markUnusable or not unusable then - if overlay then overlay:Hide() end - return - end - - overlay = EnsureUnusableOverlay(button) - - local r, g, b, a = GetUnusableColor() - -- Match ItemButton.lua exactly: configured alpha is reduced once by 45%. - local alpha = (a or 1.0) * 0.45 - overlay:SetVertexColor(r or 0.9, g or 0.2, b or 0.2, alpha) - overlay:Show() -end - -local function RefreshUsabilityAsync(button, itemData, bagID, slotID, isReadOnly, otherCharName) - if not button or not itemData or not itemData.link then return end - if isReadOnly or otherCharName then return end - - local detection = addon.Modules and addon.Modules.ItemDetection - if not detection then return end - - -- Never scan tooltip state while the sort engine is actively moving/locking items. - -- The final post-sort redraw will call SetItem again and schedule a safe refresh. - if addon.Modules.SortEngine and addon.Modules.SortEngine.sortingInProgress then - return - end - - local link = itemData.link - local cached = detection.IsUnusableCached and detection:IsUnusableCached(itemData) or nil - if cached ~= nil then - ApplyUnusableTint(button, cached) - return - end - - -- Avoid duplicate jobs when BAG_UPDATE causes several redraws for the same item. - if button._gudaUsabilityPendingLink == link then return end - button._gudaUsabilityPendingLink = link - - local function DoRefresh() - if button._gudaUsabilityPendingLink ~= link then return end - button._gudaUsabilityPendingLink = nil - - if not button.IsShown or not button:IsShown() or not button.hasItem then return end - if button.otherChar or button.isReadOnly then return end - if button.bagID ~= bagID or button.slotID ~= slotID then return end - if not button.itemData or button.itemData.link ~= link then return end - - local props = detection:GetItemProperties(button.itemData, button.bagID, button.slotID) - ApplyUnusableTint(button, props and props.isUnusable) - end - - local Utils = addon.Modules and addon.Modules.Utils - if Utils and Utils.QueueWork then - Utils:QueueWork(DoRefresh) - else - DoRefresh() - end -end - -function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly) - OriginalSetItem(self, bagID, slotID, itemData, isBank, otherCharName, matchesFilter, isReadOnly) - - if not self then return end - - local currentData = self.itemData or itemData - local currentLink = currentData and currentData.link or nil - - -- Cancel a stale pending job if this pooled button now represents another item. - if self._gudaUsabilityPendingLink and self._gudaUsabilityPendingLink ~= currentLink then - self._gudaUsabilityPendingLink = nil - end - - if self.hasItem and currentData then - RefreshUsabilityAsync(self, currentData, tonumber(bagID), tonumber(slotID), isReadOnly, otherCharName) - elseif self.unusableOverlay then - self.unusableOverlay:Hide() - end -end - -addon:Debug("ClassicAPI usability tint safety enabled")