From 808999f6823ed8ce2fc093cce43643e76aa32ac0 Mon Sep 17 00:00:00 2001 From: Vati Date: Wed, 18 Mar 2026 02:23:53 +0400 Subject: [PATCH 1/4] marker position --- Assets/pin.blp | Bin 0 -> 6676 bytes Core/Database.lua | 36 +++++++++++++ Sorting/SortEngine.lua | 46 +++++++++++------ UI/ItemButton.lua | 115 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 176 insertions(+), 21 deletions(-) create mode 100644 Assets/pin.blp diff --git a/Assets/pin.blp b/Assets/pin.blp new file mode 100644 index 0000000000000000000000000000000000000000..e6ee043620c8cc28a4c310da213963014cf2a586 GIT binary patch literal 6676 zcmeHL4{Q_X6@Pb$;hej~7>CqKutOa!$aU$2)W0){%awl1|QDsYTes{sv( ze{QKlL{oT`AbzK4ZV~4YA-1a5TRVLHV-;ds)^D5x)GwWEzZYFq!a|0!~>&3SJLdy1`z)R(d?X+Oyo$0U&4GJ z;^(014Z0!OKg3U`xLplJ7ME_ieMLQoeC#S*95Z$;o%{Ur$VZE~mR+=16}S?M)jH95 z#=3uETi#CtJ6=y0RN%{`_w}SAJ_>?eHAPkqoajY-owObrqsqm8N9C`F@teAg9h;i9 zN8{>JWHEwpz5Y_S5ySCg8g$^K6vX6a45H`A9VM z2wdn@Nzzst7GB1B0|P)A3hZcjM!Qb+3+s1Czw{F7m$sh85=Q?)%P`Dve7;m=l|&;o z)c;?Z_lRF$y*8I@Y9W>7NwlHZQO`=Qk>-A~0_)o;{#gx0qn}^<7H{Y8(^RE3biZFE zWdJgy%rmtac~Fe4aIYpcoVPpTUSZco{#v$QB%;}+-v*3f} zmk97Xqq`FpnK}fn$eQ{;SHC0qA)w(|ZT&WbYD{IdaXy@4nG@r$M5>#kLwbev9FDpp ze>$=AEsB4?c{kejp!I%l6WX1kd6(GOt(^JlL{k&ydxC=bQWb!2_@5tvT7T~<*u`hrQ z#Q&0{FHmpPh;OPl!`B6#0YxErwr?uZ6~IZE&ytIZQLl_lp{qOGEpU)m@l!#t7+dE|*@&mFgJ4QbLN!u@~o^|PYKkr6wL9fv;#0a2( zF5h$p(V$>!-fevO%5hx)P``?$l9G}BYMlIPIv;sg0AymnzYeV9kC-Bg6MrxHr3=7c zEkGMS5P0ij(DRYcAFG{}gU({USGCf9NAum_fW)lBRTqpHZ?EG9xR3vEOP?4zCUC_i z3o@{|3yvXSBaBR<#P;cQxB3jrTkHDKoM!+s_D`)gnonq@$Fd0PF@p_)olF zJ#IeeJWuh(=lTCbe53ItWDMoMFW!$Nf5Ap-pPd>yWTg6E&FiVH3&{EL`ml|*-&n8r zLHm2Dwe@R!cz({!$;rJlu<;%}A+)REsI;0mwnz2qp>PLD=AS_m=m=`MjGrFB(n>Tocw~8`OQBi4=r`0=Mcv$k(&bUcVpj zxA!2wE}XA*xIK2$)?qbA7T8^(7-WM<+f&e%CFx+oN(8uyZb*qBi->jqqLhIKC%1G60*|%(d0C z8nM4xY_>7y-?QSk`HkHe{dcMG+#8LW(~GL``L(SZY-Lc{N9T7%0HFH52R^Bb+php5 z;}4h}(^Ba9LpB50&>oIAjZ*wG%KMww@O-?D^8Xg)-!CzrPFr{xB{lR5?4SRIe!{h&pPtxmkF;9-q94$e_vM}Gand?M1%{RYGVp`umf29WX2j_=nxc@Tv zB7WXdFg}|f5s&199{XlbS+40YtvA1pxl!Hlm;8F|wGQlG4%tTFv$|sQM+-|MMqcxW zUrJY@lFi(T^>ot66=Q%;K>o8WUp`al+b8p7?RII-kGXj?&TexkYu~}<=FIG&uvg)n Jcc}Sc`=2XW46^_L literal 0 HcmV?d00001 diff --git a/Core/Database.lua b/Core/Database.lua index bedc744..834eb4f 100644 --- a/Core/Database.lua +++ b/Core/Database.lua @@ -162,6 +162,11 @@ function DB:Initialize() Guda_CharDB.setProtectionExceptions = {} end + -- Initialize pinned slots storage + if not Guda_CharDB.pinnedSlots then + Guda_CharDB.pinnedSlots = {} + end + -- Initialize CategoryManager for custom categories if addon.Modules.CategoryManager then addon.Modules.CategoryManager:Initialize() @@ -540,6 +545,37 @@ function DB:IsItemProtected(itemID) return false end +------------------------------------------------- +-- Pinned Slots (per-character, slot-based) +-- Slots pinned by the user are skipped during sorting. +-- Key format: bagID * 1000 + slot +------------------------------------------------- + +function DB:IsPinnedSlot(bagID, slot) + if not bagID or not slot or not Guda_CharDB or not Guda_CharDB.pinnedSlots then return false end + return Guda_CharDB.pinnedSlots[bagID * 1000 + slot] and true or false +end + +function DB:TogglePinnedSlot(bagID, slot) + if not bagID or not slot or not Guda_CharDB then return false end + if not Guda_CharDB.pinnedSlots then + Guda_CharDB.pinnedSlots = {} + end + local key = bagID * 1000 + slot + if Guda_CharDB.pinnedSlots[key] then + Guda_CharDB.pinnedSlots[key] = nil + return false -- unpinned + else + Guda_CharDB.pinnedSlots[key] = true + return true -- pinned + end +end + +function DB:GetPinnedSlotSet() + if not Guda_CharDB or not Guda_CharDB.pinnedSlots then return {} end + return Guda_CharDB.pinnedSlots +end + -- Cleanup old characters (not updated in 90 days) function DB:CleanupOldCharacters() local cutoff = time() - (90 * 24 * 60 * 60) -- 90 days diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index 2837ada..9c00dd3 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -1147,7 +1147,7 @@ end -- PHASE 5: Empty Slot Management & Apply Sort --=========================================================================== -local function CollectItems(bagIDs) +local function CollectItems(bagIDs, pinnedSlots) local items = {} local sequence = 0 -- Add sequence number for stable sort @@ -1156,6 +1156,10 @@ local function CollectItems(bagIDs) if addon.Modules.Utils:IsBagValid(bagID) then for slot = 1, numSlots do + -- Skip pinned slots — they are not touched by sorting + if pinnedSlots and pinnedSlots[bagID * 1000 + slot] then + -- do nothing, slot is pinned + else -- Scan directly from game API instead of cached data local texture, itemCount, locked = GetContainerItemInfo(bagID, slot) local itemLink = GetContainerItemLink(bagID, slot) @@ -1194,6 +1198,7 @@ local function CollectItems(bagIDs) class = category or "", }) end + end -- if pinnedSlots end end end @@ -1201,7 +1206,7 @@ local function CollectItems(bagIDs) return items end -local function BuildTargetPositions(bagIDs, itemCount) +local function BuildTargetPositions(bagIDs, itemCount, pinnedSlots) local positions = {} local index = 1 @@ -1234,8 +1239,11 @@ local function BuildTargetPositions(bagIDs, itemCount) if addon.Modules.Utils:IsBagValid(bagID) then for slot = 1, numSlots do if index <= itemCount then - positions[index] = {bag = bagID, slot = slot} - index = index + 1 + -- Skip pinned slots + if not (pinnedSlots and pinnedSlots[bagID * 1000 + slot]) then + positions[index] = {bag = bagID, slot = slot} + index = index + 1 + end else break end @@ -1402,7 +1410,7 @@ end -- Build tail positions (end-to-start) for a given count across the provided bags, -- starting from the "last" regular bag (lowest priority, then highest bagID), -- and spilling into previous bags when needed. -local function BuildGreyTailPositions(bagIDs, greyCount) +local function BuildGreyTailPositions(bagIDs, greyCount, pinnedSlots) local positions = {} if greyCount <= 0 then return positions end @@ -1439,7 +1447,7 @@ local function BuildGreyTailPositions(bagIDs, greyCount) local tailSlots = {} for _, info in ipairs(ordered) do for slot = info.numSlots, 1, -1 do - if table.getn(tailSlots) < greyCount then + if table.getn(tailSlots) < greyCount and not (pinnedSlots and pinnedSlots[info.bagID * 1000 + slot]) then table.insert(tailSlots, { bag = info.bagID, slot = slot }) else break @@ -1611,6 +1619,9 @@ end function SortEngine:SortBagsPass() local bagIDs = addon.Constants.BAGS + -- Cache pinned slots for this pass + local pinnedSlots = addon.Modules.DB and addon.Modules.DB:GetPinnedSlotSet() or {} + -- Phase 1: Detect specialized bags local containers = DetectSpecializedBags(bagIDs) @@ -1625,10 +1636,10 @@ function SortEngine:SortBagsPass() for _, bagType in ipairs({"enchant", "herb", "soul", "quiver", "ammo"}) do local specialBags = containers[bagType] for _, bagID in ipairs(specialBags) do - local items = CollectItems({bagID}) + local items = CollectItems({bagID}, pinnedSlots) if table.getn(items) > 0 then items = SortItems(items) - local targetPositions = BuildTargetPositions({bagID}, table.getn(items)) + local targetPositions = BuildTargetPositions({bagID}, table.getn(items), pinnedSlots) local moveCount = ApplySort({bagID}, items, targetPositions) specializedMoves = specializedMoves + moveCount end @@ -1642,7 +1653,7 @@ function SortEngine:SortBagsPass() local regularMoves = 0 local regularBagIDs = containers.regular if table.getn(regularBagIDs) > 0 then - local allItems = CollectItems(regularBagIDs) + local allItems = CollectItems(regularBagIDs, pinnedSlots) local nonGreys, greys = SplitGreyItems(allItems) local combinedItems = {} @@ -1651,7 +1662,7 @@ function SortEngine:SortBagsPass() -- Non-greys: sorted to front positions if table.getn(nonGreys) > 0 then local sortedNonGreys = SortItems(nonGreys) - local frontPositions = BuildTargetPositions(regularBagIDs, table.getn(sortedNonGreys)) + local frontPositions = BuildTargetPositions(regularBagIDs, table.getn(sortedNonGreys), pinnedSlots) for i, item in ipairs(sortedNonGreys) do table.insert(combinedItems, item) table.insert(combinedPositions, frontPositions[i]) @@ -1660,7 +1671,7 @@ function SortEngine:SortBagsPass() -- Greys: to tail positions (end->start) if table.getn(greys) > 0 then - local tailPositions = BuildGreyTailPositions(regularBagIDs, table.getn(greys)) + local tailPositions = BuildGreyTailPositions(regularBagIDs, table.getn(greys), pinnedSlots) for i, item in ipairs(greys) do table.insert(combinedItems, item) table.insert(combinedPositions, tailPositions[i]) @@ -1764,6 +1775,9 @@ function SortEngine:SortBankPass() local bagIDs = addon.Constants.BANK_BAGS + -- Cache pinned slots for this pass + local pinnedSlots = addon.Modules.DB and addon.Modules.DB:GetPinnedSlotSet() or {} + -- Phase 1: Detect specialized bags local containers = DetectSpecializedBags(bagIDs) @@ -1778,10 +1792,10 @@ function SortEngine:SortBankPass() for _, bagType in ipairs({"enchant", "herb", "soul", "quiver", "ammo"}) do local specialBags = containers[bagType] for _, bagID in ipairs(specialBags) do - local items = CollectItems({bagID}) + local items = CollectItems({bagID}, pinnedSlots) if table.getn(items) > 0 then items = SortItems(items) - local targetPositions = BuildTargetPositions({bagID}, table.getn(items)) + local targetPositions = BuildTargetPositions({bagID}, table.getn(items), pinnedSlots) local moved = ApplySort({bagID}, items, targetPositions) specializedMoves = specializedMoves + moved end @@ -1792,7 +1806,7 @@ function SortEngine:SortBankPass() local regularBagIDs = containers.regular local regularMoves = 0 if table.getn(regularBagIDs) > 0 then - local allItems = CollectItems(regularBagIDs) + local allItems = CollectItems(regularBagIDs, pinnedSlots) local nonGreys, greys = SplitGreyItems(allItems) local combinedItems = {} @@ -1800,7 +1814,7 @@ function SortEngine:SortBankPass() if table.getn(nonGreys) > 0 then local sortedNonGreys = SortItems(nonGreys) - local frontPositions = BuildTargetPositions(regularBagIDs, table.getn(sortedNonGreys)) + local frontPositions = BuildTargetPositions(regularBagIDs, table.getn(sortedNonGreys), pinnedSlots) for i, item in ipairs(sortedNonGreys) do table.insert(combinedItems, item) table.insert(combinedPositions, frontPositions[i]) @@ -1808,7 +1822,7 @@ function SortEngine:SortBankPass() end if table.getn(greys) > 0 then - local tailPositions = BuildGreyTailPositions(regularBagIDs, table.getn(greys)) + local tailPositions = BuildGreyTailPositions(regularBagIDs, table.getn(greys), pinnedSlots) for i, item in ipairs(greys) do table.insert(combinedItems, item) table.insert(combinedPositions, tailPositions[i]) diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 8a500f5..3a2291f 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -603,6 +603,84 @@ local function HideLockIcon(button) end end +--===================================================== +-- Pin Icon Pool (indicates slot is pinned / sort-protected) +--===================================================== +local pinIconPool = {} + +local function AcquirePinIcon() + local icon = table.remove(pinIconPool) + if not icon then + icon = CreateFrame("Frame", nil, UIParent) + icon:SetFrameStrata("HIGH") + icon:SetWidth(13) + icon:SetHeight(13) + + -- Shadow (behind) + local shadow = icon:CreateTexture(nil, "BACKGROUND") + shadow:SetWidth(15) + shadow:SetHeight(15) + shadow:SetPoint("CENTER", icon, "CENTER", 0, 0) + shadow:SetTexture("Interface\\AddOns\\Guda\\Assets\\pin") + shadow:SetVertexColor(0, 0, 0, 0.9) + icon.shadow = shadow + + -- Icon (front) + local texture = icon:CreateTexture(nil, "OVERLAY") + texture:SetAllPoints(icon) + texture:SetTexture("Interface\\AddOns\\Guda\\Assets\\pin") + icon.texture = texture + end + return icon +end + +local function ReleasePinIcon(icon) + if icon then + icon:Hide() + icon:ClearAllPoints() + table.insert(pinIconPool, icon) + end +end + +local function UpdatePinIcon(button, iconSize) + local DB = addon.Modules.DB + if not DB then return end + + local isPinned = false + if button.bagID and button.slotID and not button.otherChar and not button.isReadOnly then + isPinned = DB:IsPinnedSlot(button.bagID, button.slotID) + end + + if isPinned then + if not button.pinIcon then + button.pinIcon = AcquirePinIcon() + end + local pinSize = math.max(10, math.min(14, iconSize * 0.35)) + button.pinIcon:SetWidth(pinSize) + button.pinIcon:SetHeight(pinSize) + if button.pinIcon.shadow then + button.pinIcon.shadow:SetWidth(pinSize + 2) + button.pinIcon.shadow:SetHeight(pinSize + 2) + end + button.pinIcon:ClearAllPoints() + button.pinIcon:SetPoint("TOPLEFT", button, "TOPLEFT", -2, 2) + button.pinIcon:SetFrameLevel(button:GetFrameLevel() + 5) + button.pinIcon:Show() + else + if button.pinIcon then + ReleasePinIcon(button.pinIcon) + button.pinIcon = nil + end + end +end + +local function HidePinIcon(button) + if button.pinIcon then + ReleasePinIcon(button.pinIcon) + button.pinIcon = nil + end +end + -- Hook UseContainerItem to prevent selling/disenchanting protected items local OriginalUseContainerItem = UseContainerItem UseContainerItem = function(bag, slot, ...) @@ -988,6 +1066,7 @@ function Guda_ItemButton_OnLoad(self) self:SetScript("OnHide", function() HideJunkIcon(this) HideLockIcon(this) + HidePinIcon(this) end) -- Track cursor item on drag start for category drag-drop @@ -1043,6 +1122,30 @@ function Guda_ItemButton_OnLoad(self) return end + -- Pin/unpin slot with Alt+Right-Click + if IsAltKeyDown() and arg1 == "RightButton" and not this.otherChar and not this.isReadOnly then + if this.bagID ~= nil and this.slotID and addon.Modules.DB then + local isNowPinned = addon.Modules.DB:TogglePinnedSlot(this.bagID, this.slotID) + local itemName = "" + if this.hasItem and this.itemData and this.itemData.link then + itemName = " " .. this.itemData.link + end + if isNowPinned then + addon:Print("Slot pinned" .. itemName .. " (skipped during sort)") + else + addon:Print("Slot unpinned" .. itemName) + end + -- Refresh to show/hide pin icon + if addon.Modules.BagFrame and addon.Modules.BagFrame.Update then + addon.Modules.BagFrame:Update() + end + if addon.Modules.BankFrame and addon.Modules.BankFrame.Update then + addon.Modules.BankFrame:Update() + end + end + return + end + if IsAltKeyDown() and arg1 == "LeftButton" and this.hasItem and not this.otherChar and not this.isReadOnly then local link = GetContainerItemLink(this.bagID, this.slotID) if link and addon and addon.Modules and addon.Modules.Utils then @@ -1504,8 +1607,9 @@ local function ClearItemButton(self, emptySlotBg, countText, bagID) -- Hide junk icon HideJunkIcon(self) - -- Hide lock icon + -- Hide lock and pin icons HideLockIcon(self) + HidePinIcon(self) -- Hide normal texture self:SetNormalTexture("") @@ -1647,8 +1751,9 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha end end - -- Update lock icon + -- Update lock and pin icons UpdateLockIcon(self, iconSize) + UpdatePinIcon(self, iconSize) -- Update empty slot bg anchors/texture based on slot style UpdateEmptySlotBackground(self, emptySlotBg, iconSize) @@ -1838,13 +1943,13 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha if not self.categoryMarkIcon then self.categoryMarkIcon = self:CreateTexture(nil, "OVERLAY", 7) end - local markSize = math.max(10, math.floor(iconSize * 0.3)) + 6 + local markSize = math.max(10, math.floor(iconSize * 0.3)) + 3 self.categoryMarkIcon:SetWidth(markSize) self.categoryMarkIcon:SetHeight(markSize) self.categoryMarkIcon:ClearAllPoints() - local markOffX, markOffY = 2, 2 + local markOffX, markOffY = 1, 2 if addon.Modules and addon.Modules.Theme and addon.Modules.Theme:GetSlotStyle() == "square" then - markOffX, markOffY = -2, -2 + markOffX, markOffY = -1, -1 end self.categoryMarkIcon:SetPoint("BOTTOMLEFT", anchor, "BOTTOMLEFT", markOffX, markOffY) self.categoryMarkIcon:SetTexture(categoryMarkTexture) From 631f463b7392d4e3b2245a374c85f8ef90874c7d Mon Sep 17 00:00:00 2001 From: Vati Date: Wed, 18 Mar 2026 02:30:49 +0400 Subject: [PATCH 2/4] feat: pin slot --- Sorting/SortEngine.lua | 23 ++++++++++++++++------- UI/ItemButton.lua | 2 +- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index 9c00dd3..853c528 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -642,7 +642,7 @@ end -- PHASE 2: Specialized Item Routing --=========================================================================== -local function RouteSpecializedItems(bagIDs, containers) +local function RouteSpecializedItems(bagIDs, containers, pinnedSlots) local routingPlan = {} -- Scan all items and plan moves to specialized containers @@ -650,6 +650,8 @@ local function RouteSpecializedItems(bagIDs, containers) local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID) if numSlots and numSlots > 0 then for slot = 1, numSlots do + -- Skip pinned slots + if not (pinnedSlots and pinnedSlots[bagID * 1000 + slot]) then local link = GetContainerItemLink(bagID, slot) if link then local preferredType = addon.Modules.Utils:GetItemPreferredContainer(link) @@ -665,7 +667,8 @@ local function RouteSpecializedItems(bagIDs, containers) if not foundSlot then local targetSlots = addon.Modules.Utils:GetBagSlotCount(targetBagID) for targetSlot = 1, targetSlots do - if not GetContainerItemLink(targetBagID, targetSlot) then + if not GetContainerItemLink(targetBagID, targetSlot) + and not (pinnedSlots and pinnedSlots[targetBagID * 1000 + targetSlot]) then table.insert(routingPlan, { fromBag = bagID, fromSlot = slot, @@ -681,6 +684,7 @@ local function RouteSpecializedItems(bagIDs, containers) end end end + end -- pinned check end end end @@ -701,7 +705,7 @@ end -- PHASE 3: Stack Consolidation --=========================================================================== -local function ConsolidateStacks(bagIDs) +local function ConsolidateStacks(bagIDs, pinnedSlots) local itemGroups = {} -- Collect all items with their locations @@ -709,6 +713,10 @@ local function ConsolidateStacks(bagIDs) local numSlots = addon.Modules.Utils:GetBagSlotCount(bagID) if numSlots and numSlots > 0 then for slot = 1, numSlots do + -- Skip pinned slots + if pinnedSlots and pinnedSlots[bagID * 1000 + slot] then + -- do nothing + else local link = GetContainerItemLink(bagID, slot) if link then local texture, count = GetContainerItemInfo(bagID, slot) @@ -733,6 +741,7 @@ local function ConsolidateStacks(bagIDs) priority = tonumber(addon.Modules.Utils:GetContainerPriority(bagID)) or 0 }) end + end -- pinned check end end end @@ -1626,10 +1635,10 @@ function SortEngine:SortBagsPass() local containers = DetectSpecializedBags(bagIDs) -- Phase 2: Route specialized items to their bags - local routeCount = RouteSpecializedItems(bagIDs, containers) + local routeCount = RouteSpecializedItems(bagIDs, containers, pinnedSlots) -- Phase 3: Consolidate stacks in ALL bags (including specialized) - local consolidateCount = ConsolidateStacks(bagIDs) + local consolidateCount = ConsolidateStacks(bagIDs, pinnedSlots) -- Phase 4: Sort items WITHIN each specialized bag (enchant, herb, soul, quiver, ammo) local specializedMoves = 0 @@ -1782,10 +1791,10 @@ function SortEngine:SortBankPass() local containers = DetectSpecializedBags(bagIDs) -- Phase 2: Route specialized items - local routeCount = RouteSpecializedItems(bagIDs, containers) + local routeCount = RouteSpecializedItems(bagIDs, containers, pinnedSlots) -- Phase 3: Consolidate stacks - local consolidateCount = ConsolidateStacks(bagIDs) + local consolidateCount = ConsolidateStacks(bagIDs, pinnedSlots) -- Phase 4: Sort items WITHIN each specialized bag (single pass) local specializedMoves = 0 diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 3a2291f..898f34a 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -1953,7 +1953,7 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha end self.categoryMarkIcon:SetPoint("BOTTOMLEFT", anchor, "BOTTOMLEFT", markOffX, markOffY) self.categoryMarkIcon:SetTexture(categoryMarkTexture) - self.categoryMarkIcon:SetVertexColor(0.85, 0.65, 0.13, 1) + self.categoryMarkIcon:SetVertexColor(1, 1, 1, 1) self.categoryMarkIcon:SetAlpha(1) self.categoryMarkIcon:Show() else From b482d3c10ca43d5e7048a42c1eaf35c81b27be16 Mon Sep 17 00:00:00 2001 From: Vati Date: Wed, 18 Mar 2026 02:41:32 +0400 Subject: [PATCH 3/4] fix: pin slot and sort issue --- Sorting/SortEngine.lua | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index 853c528..695e727 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -1208,9 +1208,9 @@ local function CollectItems(bagIDs, pinnedSlots) }) end end -- if pinnedSlots - end - end - end + end -- for slot + end -- if isBagValid + end -- for bagID return items end @@ -1524,6 +1524,7 @@ end function SortEngine:AnalyzeContainer(bagIDs, containerType) -- Detect specialized bags (same as SortBags) local containers = DetectSpecializedBags(bagIDs) + local pinnedSlots = addon.Modules.DB and addon.Modules.DB:GetPinnedSlotSet() or {} local totalOutOfPlace = 0 local totalItems = 0 @@ -1532,10 +1533,10 @@ function SortEngine:AnalyzeContainer(bagIDs, containerType) for _, bagType in ipairs({"enchant", "herb", "soul", "quiver", "ammo"}) do local specialBags = containers[bagType] for _, bagID in ipairs(specialBags) do - local items = CollectItems({bagID}) + local items = CollectItems({bagID}, pinnedSlots) if table.getn(items) > 0 then local sortedItems = SortItems(items) - local targetPositions = BuildTargetPositions({bagID}, table.getn(items)) + local targetPositions = BuildTargetPositions({bagID}, table.getn(items), pinnedSlots) for i, item in ipairs(sortedItems) do local target = targetPositions[i] @@ -1560,7 +1561,7 @@ function SortEngine:AnalyzeContainer(bagIDs, containerType) end if table.getn(validRegularBags) > 0 then - local allItems = CollectItems(validRegularBags) + local allItems = CollectItems(validRegularBags, pinnedSlots) totalItems = totalItems + table.getn(allItems) -- Split greys/non-greys like SortBags does @@ -1569,7 +1570,7 @@ function SortEngine:AnalyzeContainer(bagIDs, containerType) -- Check non-grey positioning (should be in front positions) if table.getn(nonGreys) > 0 then local sortedNonGreys = SortItems(nonGreys) - local frontPositions = BuildTargetPositions(validRegularBags, table.getn(sortedNonGreys)) + local frontPositions = BuildTargetPositions(validRegularBags, table.getn(sortedNonGreys), pinnedSlots) for i, item in ipairs(sortedNonGreys) do local target = frontPositions[i] @@ -1582,7 +1583,7 @@ function SortEngine:AnalyzeContainer(bagIDs, containerType) -- Check grey positioning (should be in tail positions) if table.getn(greys) > 0 then -- CRITICAL FIX: Only use bags that actually exist and have slots - local tailPositions = BuildGreyTailPositions(validRegularBags, table.getn(greys)) + local tailPositions = BuildGreyTailPositions(validRegularBags, table.getn(greys), pinnedSlots) for i, item in ipairs(greys) do local target = tailPositions[i] From add444dbe5153f761a27f860aef3ed4e257de244 Mon Sep 17 00:00:00 2001 From: Vati Date: Wed, 18 Mar 2026 02:59:01 +0400 Subject: [PATCH 4/4] version: 2.1.5 --- Core/Database.lua | 1 + Guda.toc | 2 +- UI/ItemButton.lua | 6 ++-- UI/QuestItemBar.lua | 76 ++++++++++++++++++++++++++++++++++++-------- UI/SettingsPopup.lua | 6 ++++ 5 files changed, 75 insertions(+), 16 deletions(-) diff --git a/Core/Database.lua b/Core/Database.lua index 834eb4f..0f3024e 100644 --- a/Core/Database.lua +++ b/Core/Database.lua @@ -149,6 +149,7 @@ function DB:Initialize() Guda_CharDB.settings.theme = "pfui" Guda_CharDB.settings.hideBorders = true Guda_CharDB.settings.bgTransparency = 0 + Guda_CharDB.settings.iconSpacing = 8 end end diff --git a/Guda.toc b/Guda.toc index 68a03ef..f72e2e7 100644 --- a/Guda.toc +++ b/Guda.toc @@ -2,7 +2,7 @@ ## Title: Guda ## Notes: All-in-one bag and bank addon for World of Warcraft 1.12.1 (Turtle WoW) ## Author: Vati -## Version: 2.1.4 +## Version: 2.1.5 ## SavedVariables: Guda_DB ## SavedVariablesPerCharacter: Guda_CharDB diff --git a/UI/ItemButton.lua b/UI/ItemButton.lua index 898f34a..e85e769 100644 --- a/UI/ItemButton.lua +++ b/UI/ItemButton.lua @@ -530,7 +530,6 @@ local function AcquireLockIcon() local icon = table.remove(lockIconPool) if not icon then icon = CreateFrame("Frame", nil, UIParent) - icon:SetFrameStrata("HIGH") icon:SetWidth(13) icon:SetHeight(13) @@ -556,6 +555,7 @@ local function ReleaseLockIcon(icon) if icon then icon:Hide() icon:ClearAllPoints() + icon:SetParent(UIParent) table.insert(lockIconPool, icon) end end @@ -577,6 +577,7 @@ local function UpdateLockIcon(button, iconSize) if not button.lockIcon then button.lockIcon = AcquireLockIcon() end + button.lockIcon:SetParent(button) local lockSize = math.max(8, math.min(12, iconSize * 0.35 - 3)) button.lockIcon:SetWidth(lockSize) button.lockIcon:SetHeight(lockSize) @@ -612,7 +613,6 @@ local function AcquirePinIcon() local icon = table.remove(pinIconPool) if not icon then icon = CreateFrame("Frame", nil, UIParent) - icon:SetFrameStrata("HIGH") icon:SetWidth(13) icon:SetHeight(13) @@ -638,6 +638,7 @@ local function ReleasePinIcon(icon) if icon then icon:Hide() icon:ClearAllPoints() + icon:SetParent(UIParent) table.insert(pinIconPool, icon) end end @@ -655,6 +656,7 @@ local function UpdatePinIcon(button, iconSize) if not button.pinIcon then button.pinIcon = AcquirePinIcon() end + button.pinIcon:SetParent(button) local pinSize = math.max(10, math.min(14, iconSize * 0.35)) button.pinIcon:SetWidth(pinSize) button.pinIcon:SetHeight(pinSize) diff --git a/UI/QuestItemBar.lua b/UI/QuestItemBar.lua index 967f01c..49f36a8 100644 --- a/UI/QuestItemBar.lua +++ b/UI/QuestItemBar.lua @@ -259,8 +259,17 @@ function QuestItemBar:Update() button.slotID = nil local icon = getglobal(button:GetName() .. "IconTexture") - icon:SetTexture("Interface\\Buttons\\UI-EmptySlot") - icon:SetVertexColor(0.5, 0.5, 0.5, 0.5) + local slotStyle = "rounded" + if addon.Modules and addon.Modules.Theme then + slotStyle = addon.Modules.Theme:GetSlotStyle() + end + if slotStyle == "square" then + icon:SetTexture("Interface\\Buttons\\WHITE8x8") + icon:SetVertexColor(0.05, 0.05, 0.05, 0.5) + else + icon:SetTexture("Interface\\Buttons\\UI-EmptySlot") + icon:SetVertexColor(0.5, 0.5, 0.5, 0.5) + end local countText = getglobal(button:GetName() .. "Count") countText:Hide() @@ -324,18 +333,44 @@ function QuestItemBar:Update() end -- Scale border proportionally (64/37 is the standard ratio for WoW item buttons) - local borderSize = buttonSize * 64 / 37 + local slotStyle = "rounded" + if addon.Modules and addon.Modules.Theme then + slotStyle = addon.Modules.Theme:GetSlotStyle() + end + local normalTex = getglobal(button:GetName() .. "NormalTexture") - if normalTex then - normalTex:SetWidth(borderSize) - normalTex:SetHeight(borderSize) + if slotStyle == "square" then + -- Hide rounded border in pfUI mode + button:SetNormalTexture("") + if normalTex then + normalTex:SetTexture(nil) + normalTex:Hide() + end + -- Crop icon for pfUI style + if icon then + icon:SetTexCoord(.08, .92, .08, .92) + end + else + local borderSize = buttonSize * 64 / 37 + if normalTex then + normalTex:SetWidth(borderSize) + normalTex:SetHeight(borderSize) + end end -- Resize empty slot background local emptyBg = getglobal(button:GetName() .. "_EmptySlotBg") if emptyBg then - emptyBg:SetWidth(buttonSize) - emptyBg:SetHeight(buttonSize) + if slotStyle == "square" then + emptyBg:SetTexture("Interface\\Buttons\\WHITE8x8") + emptyBg:SetVertexColor(0.05, 0.05, 0.05, 1) + emptyBg:ClearAllPoints() + emptyBg:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0) + emptyBg:SetPoint("BOTTOMRIGHT", button, "BOTTOMRIGHT", 0, 0) + else + emptyBg:SetWidth(buttonSize) + emptyBg:SetHeight(buttonSize) + end end -- Update visual overlays (cooldown, etc) @@ -562,12 +597,27 @@ function QuestItemBar:UpdateFlyout(parent) btnIcon:SetHeight(buttonSize) end - -- Scale border proportionally (64/37 is the standard ratio for WoW item buttons) - local borderSize = buttonSize * 64 / 37 + -- Scale or hide border based on theme local btnNormalTex = getglobal(btn:GetName() .. "NormalTexture") - if btnNormalTex then - btnNormalTex:SetWidth(borderSize) - btnNormalTex:SetHeight(borderSize) + local flyoutSlotStyle = "rounded" + if addon.Modules and addon.Modules.Theme then + flyoutSlotStyle = addon.Modules.Theme:GetSlotStyle() + end + if flyoutSlotStyle == "square" then + btn:SetNormalTexture("") + if btnNormalTex then + btnNormalTex:SetTexture(nil) + btnNormalTex:Hide() + end + if btnIcon then + btnIcon:SetTexCoord(.08, .92, .08, .92) + end + else + local borderSize = buttonSize * 64 / 37 + if btnNormalTex then + btnNormalTex:SetWidth(borderSize) + btnNormalTex:SetHeight(borderSize) + end end -- Resize empty slot background diff --git a/UI/SettingsPopup.lua b/UI/SettingsPopup.lua index 9374ff0..fbbfb0b 100644 --- a/UI/SettingsPopup.lua +++ b/UI/SettingsPopup.lua @@ -58,7 +58,13 @@ function Guda_SettingsPopup_OnLoad(self) "Ctrl + Right Click on any item to lock/unlock it.\n" .. "Locked items cannot be sold at vendors, deleted, or disenchanted.\n" .. "Equipment set items are automatically protected.\n" .. + "Ctrl + Right Click a set item to toggle its protection.\n" .. "A lock icon appears on the bottom-right corner.\n\n" .. + "|cffffd100Pin Slot:|r\n" .. + "Alt + Right Click on any bag slot to pin/unpin it.\n" .. + "Pinned slots are skipped during sorting.\n" .. + "The pin stays on the slot, not the item.\n" .. + "A pin icon appears on the top-left corner.\n\n" .. "|cffffd100Moving Bars:|r\n" .. "Shift + Left Click and drag any item on the Quest Item Bar or Tracked Item Bar to move the bar.\n" instructions:SetText(text)