diff --git a/Bagshui.toc b/Bagshui.toc index 1d2b879..370a8b7 100644 --- a/Bagshui.toc +++ b/Bagshui.toc @@ -2,7 +2,7 @@ ## Title: Bagshui ## Notes: |cffdb4e97Feng shui for your bags|r|nAll-in-one autosorting inventory replacement|nWoW 1.18.1 Clients fork - requires ClassicAPI ## Author: veechs | txtsd | WoW 1.18.1 Clients fork -## Version: 1.6.4 +## Version: 1.6.5 ## OptionalDeps: AtlasLoot, ItemRack, Outfitter, pfUI, !!!ClassicAPI ## RequiredMods: ClassicAPI.dll (https://github.com/brues-code/ClassicAPI) ## SavedVariables: BagshuiData diff --git a/Changelog.md b/Changelog.md index c1275ad..049b2df 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,10 +1,15 @@ # Bagshui Changelog -## Unreleased +## 1.6.5 - 2026-10-31 ### Fixed - A window anchored to the Right or Top jumped toward the left/bottom edge whenever it refreshed if the game's UI scale wasn't 1. The saved position is now calculated the same way it is restored. +- Possible hitch while dragging a window: a full redraw (for example from bag changes while looting or in combat) could run while the window was moving. Redraws are now held until the window is dropped, then run once. If the drop is missed, updates resume after 10 seconds. + +### Performance + +- Queuing an inventory update (on every bag change) no longer builds a new string each time, which cuts garbage collection work. ## 1.6.4 - 2026-10-27 diff --git a/Components/Bagshui.Events.lua b/Components/Bagshui.Events.lua index 58bfef3..3fa02f3 100644 --- a/Components/Bagshui.Events.lua +++ b/Components/Bagshui.Events.lua @@ -91,9 +91,22 @@ Bagshui:LoadComponent( ---@param arg2 any? Parameter for `Bagshui:QueueEvent()`. ---@param arg3 any? Parameter for `Bagshui:QueueEvent()`. ---@param arg4 any? Parameter for `Bagshui:QueueEvent()`. + -- Cache of event ids for class callbacks: classCallbackIds[classInstance][classFunction] = eventId. + -- QueueUpdate() runs on every BAG_UPDATE, so building the id string each time made garbage. + local classCallbackIds = setmetatable({}, { __mode = "k" }) + function Bagshui:QueueClassCallback(classInstance, classFunction, delaySeconds, noReset, arg1, arg2, arg3, arg4) - -- Create a unique identifier for this event since it's not a predefined event string. - local eventId = tostring(classInstance) .. ":" .. tostring(classFunction) + -- Create (once) a unique identifier for this event since it's not a predefined event string. + local idsForInstance = classCallbackIds[classInstance] + if not idsForInstance then + idsForInstance = {} + classCallbackIds[classInstance] = idsForInstance + end + local eventId = idsForInstance[classFunction] + if not eventId then + eventId = tostring(classInstance) .. ":" .. tostring(classFunction) + idsForInstance[classFunction] = eventId + end if self:QueueEvent(eventId, delaySeconds, noReset, arg1, arg2, arg3, arg4) then -- When the event is successfully queued, store callback information so RaiseEvent() -- knows to handle this as a class callback, not a standard event. diff --git a/Components/Inventory.Layout.lua b/Components/Inventory.Layout.lua index 7b061cd..efc114a 100644 --- a/Components/Inventory.Layout.lua +++ b/Components/Inventory.Layout.lua @@ -27,6 +27,15 @@ Bagshui:AddComponent(function() return end + -- Don't redraw while the window is being dragged. A full redraw (for example from a BAG_UPDATE + -- while looting in combat) resizes and re-lays out every button of a frame that is moving, + -- which shows up as a hitch. The update runs as soon as the window is dropped. + if self:IsDragInProgress() then + self.updateDeferredByDrag = true + self.updateDeferredByDragCascade = self.updateDeferredByDragCascade or cascade + return + end + -- Update is only needed if the window is visible. if not self:Visible() and not self.forceCacheUpdate then self.windowUpdateBlocked = false diff --git a/Components/Inventory.Ui.lua b/Components/Inventory.Ui.lua index 1dfcf6c..0ea89ac 100644 --- a/Components/Inventory.Ui.lua +++ b/Components/Inventory.Ui.lua @@ -1144,6 +1144,7 @@ Bagshui:AddComponent(function() -- If the mouse button was released while the game hitched, OnDragStop may never have fired. -- A stuck flag would make FixWindowPosition() (and window rescue) skip this window forever. self.dragInProgress = false + self:RunUpdateDeferredByDrag() self:ClearSearch() self:CloseMenusAndClearFocuses() self:ClearEditModeCursor() @@ -1164,6 +1165,7 @@ Bagshui:AddComponent(function() elseif not self.settings.windowLocked then -- Has to be self instead of "this" because it can be called from docked windows. self.dragInProgress = true + self.dragStartedAt = _G.GetTime() self.uiFrame:StartMoving() end end @@ -1182,6 +1184,34 @@ Bagshui:AddComponent(function() -- instead of saving an unreachable position. self:RescueWindow() self:NotifySkinOfPositionChange() + -- Catch up on anything that changed while the window was moving. + self:RunUpdateDeferredByDrag() + if self.dockedInventory then + self.dockedInventory:RunUpdateDeferredByDrag() + end + end + end + + --- Is this window (or the window it's docked to) being dragged right now? + --- Gives up after 10 seconds in case OnDragStop never fired (game hitch while releasing the mouse), + --- so updates can't stay paused. + ---@return boolean + function Inventory:IsDragInProgress() + local owner = self.dockTo and Bagshui.components[self.dockTo] or self + return ( + owner.dragInProgress + and owner.dragStartedAt + and (_G.GetTime() - owner.dragStartedAt) < 10 + ) and true or false + end + + --- Run an `Update()` that was skipped because the window was being dragged. + function Inventory:RunUpdateDeferredByDrag() + if self.updateDeferredByDrag then + local cascade = self.updateDeferredByDragCascade + self.updateDeferredByDrag = false + self.updateDeferredByDragCascade = nil + self:QueueUpdate(nil, cascade) end end