refactor: consolidate small compatibility layers

This commit is contained in:
Dusk-92
2026-09-06 08:54:46 +02:00
parent e096ba2d90
commit cadf154d0f
18 changed files with 757 additions and 727 deletions
+90
View File
@@ -445,3 +445,93 @@ function InventoryIndex:Initialize()
addon:Debug("ClassicAPI inventory count index initialized") addon:Debug("ClassicAPI inventory count index initialized")
end 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")
-86
View File
@@ -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")
-77
View File
@@ -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")
-25
View File
@@ -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")
+110
View File
@@ -364,3 +364,113 @@ function ItemDetection:InvalidateCharges(bagID)
end end
addon:Debug("ClassicAPI single-pass ItemDetection enabled") 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")
+49
View File
@@ -480,3 +480,52 @@ function BagScanner:Initialize()
addon:Debug("Bag scanner initialized with ClassicAPI incremental cache") addon:Debug("Bag scanner initialized with ClassicAPI incremental cache")
end 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")
+79
View File
@@ -363,3 +363,82 @@ end
function BankScanner:IsBankOpen() function BankScanner:IsBankOpen()
return bankOpen return bankOpen
end 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")
-75
View File
@@ -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")
-45
View File
@@ -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")
-10
View File
@@ -22,21 +22,16 @@ Core\ClassicAPI.lua
Core\Performance.lua Core\Performance.lua
Core\ItemDetection.lua Core\ItemDetection.lua
Core\ItemDetectionClassicAPI.lua Core\ItemDetectionClassicAPI.lua
Core\ItemChargesClassicAPI.lua
Core\ItemDetectionCacheSafety.lua
Core\CategoryManager.lua Core\CategoryManager.lua
Core\ManualJunkAutoSell.lua Core\ManualJunkAutoSell.lua
Core\Tooltip.lua Core\Tooltip.lua
Core\InventoryIndex.lua Core\InventoryIndex.lua
Core\InventoryIndexSafety.lua
Core\ClamOpener.lua Core\ClamOpener.lua
Core\AutoLoot.lua Core\AutoLoot.lua
Core\CacheWarmer.lua Core\CacheWarmer.lua
Data\BagScanner.lua Data\BagScanner.lua
Data\ItemCategoryOverrides.lua
Data\BankScanner.lua Data\BankScanner.lua
Data\CacheLifecycleSafety.lua
Data\MailboxScanner.lua Data\MailboxScanner.lua
Data\MoneyTracker.lua Data\MoneyTracker.lua
Data\EquipmentScanner.lua Data\EquipmentScanner.lua
@@ -48,18 +43,13 @@ Sorting\BagReplacer.lua
# UI Lua must load before XML so OnLoad handlers exist # UI Lua must load before XML so OnLoad handlers exist
UI\ItemButton.lua UI\ItemButton.lua
UI\ItemButtonPerformanceClassicAPI.lua UI\ItemButtonPerformanceClassicAPI.lua
UI\UsabilityTintClassicAPI.lua
UI\TooltipPositionFix.lua
UI\FrameHelpers.lua UI\FrameHelpers.lua
UI\BagFrame.lua UI\BagFrame.lua
UI\BankFrame.lua UI\BankFrame.lua
UI\MailboxFrame.lua UI\MailboxFrame.lua
UI\QuestItemBar.lua UI\QuestItemBar.lua
UI\QuestItemBarClassicAPI.lua
UI\QuestItemBarMoveSafety.lua
UI\TrackedItemBar.lua UI\TrackedItemBar.lua
UI\TrackedItemBarClassicAPI.lua UI\TrackedItemBarClassicAPI.lua
UI\TrackedItemBarMoveSafety.lua
UI\SettingsPopup.lua UI\SettingsPopup.lua
UI\ItemButton.xml UI\ItemButton.xml
UI\BagFrame.xml UI\BagFrame.xml
+247
View File
@@ -254,3 +254,250 @@ initFrame:SetScript("OnEvent", function()
this:UnregisterEvent("PLAYER_ENTERING_WORLD") this:UnregisterEvent("PLAYER_ENTERING_WORLD")
end end
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")
+109
View File
@@ -833,3 +833,112 @@ function QuestItemBar:Debug()
end end
QuestItemBar.isLoaded = true 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")
-37
View File
@@ -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")
-64
View File
@@ -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")
-110
View File
@@ -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")
+73
View File
@@ -305,3 +305,76 @@ function TrackedItemBar:Update()
end end
addon:Debug("TrackedItemBar ClassicAPI snapshot consumer loaded") 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")
-69
View File
@@ -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")
-129
View File
@@ -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")