diff --git a/Sorting/SortEngine.lua b/Sorting/SortEngine.lua index 0127b65..376bc01 100644 --- a/Sorting/SortEngine.lua +++ b/Sorting/SortEngine.lua @@ -56,14 +56,15 @@ local CATEGORY_ORDER = { ["Projectile"] = 3, ["Weapon"] = 4, -- Non-equippable weapons ["Armor"] = 5, -- Non-equippable armor - ["Quiver"] = 6, - ["Reagent"] = 7, - ["Trade Goods"] = 8, - ["Recipe"] = 9, - ["Container"] = 10, - ["Quest"] = 11, - ["Key"] = 12, - ["Miscellaneous"] = 13, + ["Tools"] = 6, + ["Quiver"] = 7, + ["Reagent"] = 8, + ["Trade Goods"] = 9, + ["Recipe"] = 10, + ["Container"] = 11, + ["Quest"] = 12, + ["Key"] = 13, + ["Miscellaneous"] = 14, } -- Subclass ordering for grouping related items diff --git a/UI/BagFrame.lua b/UI/BagFrame.lua index 5ae802f..be199ce 100644 --- a/UI/BagFrame.lua +++ b/UI/BagFrame.lua @@ -406,6 +406,12 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName) "Weapon", "Armor", "Consumable", "Food", "Drink", "Quest", "Trade Goods", "Reagent", "Recipe", "Quiver", "Container", "Soul Bag", "Keyring", "Miscellaneous" } for _, cat in ipairs(categoryList) do categories[cat] = {} end + + local specialItems = { + Hearthstone = {}, + Mount = {}, + Tools = {} + } for _, bagID in ipairs(addon.Constants.BAGS) do if not hiddenBags[bagID] then @@ -414,34 +420,48 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName) for slotID, itemData in pairs(bag.slots) do if itemData then local cat = itemData.class or "Miscellaneous" - + local itemName = itemData.name or "" + + -- Detect Hearthstone + if string.find(itemName, "Hearthstone") then + table.insert(specialItems.Hearthstone, {bagID = bagID, slotID = slotID, itemData = itemData}) + -- Detect Mounts + elseif addon.Modules.SortEngine and addon.Modules.SortEngine.IsMount and addon.Modules.SortEngine.IsMount(itemData.texture) then + table.insert(specialItems.Mount, {bagID = bagID, slotID = slotID, itemData = itemData}) + -- Detect Tools category + elseif string.find(itemName, "Runed .* Rod") or + string.find(itemName, "Fishing Pole") or + string.find(itemName, "Mining Pick") or + string.find(itemName, "Blacksmith Hammer") or + itemName == "Arclight Spanner" or + itemName == "Gyromatic Micro-Adjustor" or + itemName == "Philosopher's Stone" or + string.find(itemName, "Skinning Knife") or + itemName == "Blood Scythe" then + table.insert(specialItems.Tools, {bagID = bagID, slotID = slotID, itemData = itemData}) -- Split Equipment into Weapon and Armor - if itemData.equipSlot and itemData.equipSlot ~= "" then + elseif itemData.equipSlot and itemData.equipSlot ~= "" then if itemData.class == "Weapon" or itemData.class == "Armor" then cat = itemData.class else cat = "Armor" -- Accessories etc usually fall here if equippable end - end - + table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData}) -- Detect Food and Drink - if itemData.class == "Consumable" then + elseif itemData.class == "Consumable" then local sub = itemData.subclass or "" if sub == "Food & Drink" or string.find(sub, "Food") or string.find(sub, "Drink") then - -- Try to be more specific if possible, but "Food & Drink" is the standard subclass - -- For simplicity we can check item name or texture if we wanted to split, - -- but user asked for "Food", "Drink". - -- If we can't easily distinguish, we'll put them in "Food" or "Drink" based on subclass text if string.find(sub, "Drink") then cat = "Drink" else cat = "Food" end end + table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData}) + else + if not categories[cat] then cat = "Miscellaneous" end + table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData}) end - - if not categories[cat] then cat = "Miscellaneous" end - table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData}) end end end @@ -461,13 +481,16 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName) end -- Layout - local x, y = 10, -10 + local startX, startY = 10, -10 + local currentX, currentY = 0, 0 + local rowMaxHeight = 0 local headerIdx = 1 - local totalHeight = 20 + local totalWidth = perRow * (buttonSize + spacing) for _, catName in ipairs(categoryList) do local items = categories[catName] - if items and table.getn(items) > 0 then + local numItems = items and table.getn(items) or 0 + if numItems > 0 then -- Sort items in category: Subclass > Quality > Name table.sort(items, function(a, b) if a.itemData.subclass ~= b.itemData.subclass then @@ -479,17 +502,30 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName) return (a.itemData.name or "") < (b.itemData.name or "") end) + local blockCols = numItems + if blockCols > perRow then blockCols = perRow end + local blockRows = math.ceil(numItems / perRow) + local blockWidth = blockCols * (buttonSize + spacing) + local blockHeight = 20 + (blockRows * (buttonSize + spacing)) + 5 -- 20 header, 5 padding + + -- Check if it fits in current row + if currentX > 0 and currentX + blockWidth > totalWidth + 5 then + currentX = 0 + currentY = currentY + rowMaxHeight + rowMaxHeight = 0 + end + -- Add Header local header = self:GetSectionHeader(headerIdx) headerIdx = headerIdx + 1 - header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", 0, y) - header:SetWidth(perRow * (buttonSize + spacing)) + header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY) + header:SetWidth(blockWidth) header.text:SetText(catName) header:Show() - y = y - 20 - + local itemY = currentY + 20 local col = 0 + local row = 0 for _, item in ipairs(items) do local bagID = item.bagID local slot = item.slotID @@ -502,28 +538,123 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName) button:SetWidth(buttonSize) button:SetHeight(buttonSize) button:ClearAllPoints() - button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + (col * (buttonSize + spacing)), y) + button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX + (col * (buttonSize + spacing)), startY - (itemY + (row * (buttonSize + spacing)))) button:Show() - local matchesFilter = true - if searchText ~= "" then - matchesFilter = BagFrame:MatchesSearch(itemData, searchText) - end - - Guda_ItemButton_SetItem(button, bagID, slot, itemData, false, isOtherChar and charName or nil, matchesFilter, true) + local matchesFilter = self:PassesSearchFilter(itemData) + Guda_ItemButton_SetItem(button, bagID, slot, itemData, false, isOtherChar and charName or nil, matchesFilter, isOtherChar) button.inUse = true col = col + 1 - if col >= perRow then + if col >= blockCols then col = 0 - y = y - (buttonSize + spacing) + row = row + 1 end end - if col > 0 then - y = y - (buttonSize + spacing) + if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end + currentX = currentX + blockWidth + end + end + + -- Update Y for bottom sections + local y = currentY + rowMaxHeight + + -- Special sections at bottom (Hearthstone, Mount, Tools) + local bottomSections = { + { name = "Home", items = specialItems.Hearthstone }, + { name = "Mounts", items = specialItems.Mount }, + { name = "Tools", items = specialItems.Tools } + } + + local x = startX + y = startY - y -- convert to coordinate relative to TOPLEFT + + -- Add spacing before special section + local hasAnyBottom = false + for _, sec in ipairs(bottomSections) do + if table.getn(sec.items) > 0 then + hasAnyBottom = true + break + end + end + + if hasAnyBottom then + y = y - 10 + local col = 0 + local sectionMaxHeight = 0 + + for _, sec in ipairs(bottomSections) do + local items = sec.items + local numItems = table.getn(items) + if numItems > 0 then + -- Sort Tools (Hearthstone/Mounts don't usually need it but good for consistency) + if sec.name == "Tools" then + table.sort(items, function(a, b) + if a.itemData.quality ~= b.itemData.quality then + return a.itemData.quality > b.itemData.quality + end + return (a.itemData.name or "") < (b.itemData.name or "") + end) + end + + local blockCols = numItems + if blockCols > perRow then blockCols = perRow end + local blockRows = math.ceil(numItems / perRow) + local blockWidth = blockCols * (buttonSize + spacing) + local blockHeight = 20 + (blockRows * (buttonSize + spacing)) + + -- Check if it fits in current row (Inline block for bottom sections too) + if col > 0 and (col * (buttonSize + spacing)) + blockWidth > totalWidth + 5 then + col = 0 + y = y - sectionMaxHeight - 5 + sectionMaxHeight = 0 + end + + -- Add Header + local header = self:GetSectionHeader(headerIdx) + headerIdx = headerIdx + 1 + header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + (col * (buttonSize + spacing)), y) + header:SetWidth(blockWidth) + header.text:SetText(sec.name) + header:Show() + + local itemY = y - 20 + local sCol = 0 + local sRow = 0 + for _, item in ipairs(items) do + local bagParent = self:GetBagParent(item.bagID) + local button = Guda_GetItemButton(bagParent) + button:SetParent(bagParent) + button:SetWidth(buttonSize) + button:SetHeight(buttonSize) + button:ClearAllPoints() + button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + ((col + sCol) * (buttonSize + spacing)), itemY - (sRow * (buttonSize + spacing))) + button:Show() + Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, false, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), isOtherChar) + button.inUse = true + + sCol = sCol + 1 + if sCol >= blockCols then + sCol = 0 + sRow = sRow + 1 + end + end + + if blockHeight > sectionMaxHeight then sectionMaxHeight = blockHeight end + col = col + blockCols + + -- If we wrapped exactly at the end of a block + if col >= perRow then + col = 0 + y = y - sectionMaxHeight - 5 + sectionMaxHeight = 0 + end end - y = y - 5 -- Padding between sections + end + + if col > 0 then + y = y - sectionMaxHeight end end diff --git a/UI/BankFrame.lua b/UI/BankFrame.lua index 1329c6a..2754141 100644 --- a/UI/BankFrame.lua +++ b/UI/BankFrame.lua @@ -259,10 +259,44 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName) } for _, cat in ipairs(categoryList) do categories[cat] = {} end + local specialItems = { + Hearthstone = {}, + Mount = {}, + Tools = {} + } + -- Helper to assign category - local function GetItemCategory(itemData) + local function CategorizeItem(itemData, bagID, slotID) + local itemName = itemData.name or "" + + -- Detect Hearthstone + if string.find(itemName, "Hearthstone") then + table.insert(specialItems.Hearthstone, {bagID = bagID, slotID = slotID, itemData = itemData}) + return + end + + -- Detect Mounts + if addon.Modules.SortEngine and addon.Modules.SortEngine.IsMount and addon.Modules.SortEngine.IsMount(itemData.texture) then + table.insert(specialItems.Mount, {bagID = bagID, slotID = slotID, itemData = itemData}) + return + end + + -- Detect Tools category + if string.find(itemName, "Runed .* Rod") or + string.find(itemName, "Fishing Pole") or + string.find(itemName, "Mining Pick") or + string.find(itemName, "Blacksmith Hammer") or + itemName == "Arclight Spanner" or + itemName == "Gyromatic Micro-Adjustor" or + itemName == "Philosopher's Stone" or + string.find(itemName, "Skinning Knife") or + itemName == "Blood Scythe" then + table.insert(specialItems.Tools, {bagID = bagID, slotID = slotID, itemData = itemData}) + return + end + local cat = itemData.class or "Miscellaneous" - + -- Split Equipment into Weapon and Armor if itemData.equipSlot and itemData.equipSlot ~= "" then if itemData.class == "Weapon" or itemData.class == "Armor" then @@ -285,7 +319,7 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName) end if not categories[cat] then cat = "Miscellaneous" end - return cat + table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData}) end -- Bank main slots (bagID -1) @@ -293,8 +327,7 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName) if bankMain and bankMain.slots then for slotID, itemData in pairs(bankMain.slots) do if itemData then - local cat = GetItemCategory(itemData) - table.insert(categories[cat], {bagID = -1, slotID = slotID, itemData = itemData}) + CategorizeItem(itemData, -1, slotID) end end end @@ -306,8 +339,7 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName) if bag and bag.slots then for slotID, itemData in pairs(bag.slots) do if itemData then - local cat = GetItemCategory(itemData) - table.insert(categories[cat], {bagID = bagID, slotID = slotID, itemData = itemData}) + CategorizeItem(itemData, bagID, slotID) end end end @@ -315,12 +347,16 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName) end -- Layout - local x, y = 5, -10 + local startX, startY = 5, -10 + local currentX, currentY = 0, 0 + local rowMaxHeight = 0 local headerIdx = 1 + local totalWidth = perRow * (buttonSize + spacing) for _, catName in ipairs(categoryList) do local items = categories[catName] - if items and table.getn(items) > 0 then + local numItems = items and table.getn(items) or 0 + if numItems > 0 then -- Sort items in category: Subclass > Quality > Name table.sort(items, function(a, b) if a.itemData.subclass ~= b.itemData.subclass then @@ -332,17 +368,30 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName) return (a.itemData.name or "") < (b.itemData.name or "") end) + local blockCols = numItems + if blockCols > perRow then blockCols = perRow end + local blockRows = math.ceil(numItems / perRow) + local blockWidth = blockCols * (buttonSize + spacing) + local blockHeight = 20 + (blockRows * (buttonSize + spacing)) + 5 + + -- Check if it fits in current row + if currentX > 0 and currentX + blockWidth > totalWidth + 5 then + currentX = 0 + currentY = currentY + rowMaxHeight + rowMaxHeight = 0 + end + -- Add Header local header = self:GetSectionHeader(headerIdx) headerIdx = headerIdx + 1 - header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", 0, y) - header:SetWidth(perRow * (buttonSize + spacing)) + header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY) + header:SetWidth(blockWidth) header.text:SetText(catName) header:Show() - y = y - 20 - + local itemY = currentY + 20 local col = 0 + local row = 0 for _, item in ipairs(items) do local bagID = item.bagID local slot = item.slotID @@ -355,28 +404,126 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName) button:SetWidth(buttonSize) button:SetHeight(buttonSize) button:ClearAllPoints() - button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + (col * (buttonSize + spacing)), y) + button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX + (col * (buttonSize + spacing)), startY - (itemY + (row * (buttonSize + spacing)))) button:Show() local matchesFilter = self:PassesSearchFilter(itemData) - Guda_ItemButton_SetItem(button, bagID, slot, itemData, true, isOtherChar and charName or nil, matchesFilter, true) button.inUse = true col = col + 1 - if col >= perRow then + if col >= blockCols then col = 0 - y = y - (buttonSize + spacing) + row = row + 1 end end - if col > 0 then - y = y - (buttonSize + spacing) - end - y = y - 5 -- Padding between sections + if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end + currentX = currentX + blockWidth end end + -- Update Y for bottom sections + local y = currentY + rowMaxHeight + + -- Special sections at bottom (Hearthstone, Mount, Tools) + local bottomSections = { + { name = "Home", items = specialItems.Hearthstone }, + { name = "Mounts", items = specialItems.Mount }, + { name = "Tools", items = specialItems.Tools } + } + + local x = startX + y = startY - y -- convert to coordinate relative to TOPLEFT + + -- Add spacing before special section + local hasAnyBottom = false + for _, sec in ipairs(bottomSections) do + if table.getn(sec.items) > 0 then + hasAnyBottom = true + break + end + end + + if hasAnyBottom then + y = y - 10 + local col = 0 + local sectionMaxHeight = 0 + + for _, sec in ipairs(bottomSections) do + local items = sec.items + local numItems = table.getn(items) + if numItems > 0 then + -- Sort Tools (Hearthstone/Mounts don't usually need it but good for consistency) + if sec.name == "Tools" then + table.sort(items, function(a, b) + if a.itemData.quality ~= b.itemData.quality then + return a.itemData.quality > b.itemData.quality + end + return (a.itemData.name or "") < (b.itemData.name or "") + end) + end + + local blockCols = numItems + if blockCols > perRow then blockCols = perRow end + local blockRows = math.ceil(numItems / perRow) + local blockWidth = blockCols * (buttonSize + spacing) + local blockHeight = 20 + (blockRows * (buttonSize + spacing)) + + -- Check if it fits in current row (Inline block for bottom sections too) + if col > 0 and (col * (buttonSize + spacing)) + blockWidth > totalWidth + 5 then + col = 0 + y = y - sectionMaxHeight - 5 + sectionMaxHeight = 0 + end + + -- Add Header + local header = self:GetSectionHeader(headerIdx) + headerIdx = headerIdx + 1 + header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + (col * (buttonSize + spacing)), y) + header:SetWidth(blockWidth) + header.text:SetText(sec.name) + header:Show() + + local itemY = y - 20 + local sCol = 0 + local sRow = 0 + for _, item in ipairs(items) do + local bagParent = self:GetBagParent(item.bagID) + local button = Guda_GetItemButton(bagParent) + button:SetParent(bagParent) + button:SetWidth(buttonSize) + button:SetHeight(buttonSize) + button:ClearAllPoints() + button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + ((col + sCol) * (buttonSize + spacing)), itemY - (sRow * (buttonSize + spacing))) + button:Show() + Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, true, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), true) + button.inUse = true + + sCol = sCol + 1 + if sCol >= blockCols then + sCol = 0 + sRow = sRow + 1 + end + end + + if blockHeight > sectionMaxHeight then sectionMaxHeight = blockHeight end + col = col + blockCols + + -- If we wrapped exactly at the end of a block + if col >= perRow then + col = 0 + y = y - sectionMaxHeight - 5 + sectionMaxHeight = 0 + end + end + end + + if col > 0 then + y = y - sectionMaxHeight + end + end + -- Update container height local finalHeight = math.abs(y) + 20 itemContainer:SetHeight(finalHeight)