feat: update categories and item slots
This commit is contained in:
+167
-110
@@ -996,55 +996,131 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
||||
local categoryItemsProcessed = 0
|
||||
local CATEGORY_ITEMS_PER_BUDGET_CHECK = 8
|
||||
|
||||
local totalButtonsCreated = 0
|
||||
for _, catName in ipairs(categoryList) do
|
||||
local items = categories[catName]
|
||||
local numItems = items and table.getn(items) or 0
|
||||
if numItems > 0 then
|
||||
addon:DebugCategory(" Category '%s': %d items, pos=(%d,%d)", catName, numItems, currentX, currentY)
|
||||
-- Sort items using centralized sorter
|
||||
Guda_SortCategoryItems(items)
|
||||
-- Check merged groups setting
|
||||
local mergedGroups = addon.Modules.DB:GetSetting("mergedGroups") or {}
|
||||
|
||||
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
|
||||
-- Build category order index map for sorting within merged groups
|
||||
local catOrderIndex = {}
|
||||
for idx, catId in ipairs(categoryList) do
|
||||
catOrderIndex[catId] = idx
|
||||
end
|
||||
|
||||
-- Check if it fits in current row
|
||||
if currentX > 0 and currentX + blockWidth + 20 > totalWidth + 5 then
|
||||
currentX = 0
|
||||
currentY = currentY + rowMaxHeight
|
||||
rowMaxHeight = 0
|
||||
addon:DebugCategory(" -> wrap to new row, Y=%d", currentY)
|
||||
end
|
||||
-- Build merged group display lists if any groups are merged
|
||||
local mergedDisplayList = {} -- { { name, items, icon, catDef } }
|
||||
local processedCats = {} -- Track which categories were merged
|
||||
|
||||
-- Add Header
|
||||
local header = self:GetSectionHeader(headerIdx)
|
||||
headerIdx = headerIdx + 1
|
||||
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY)
|
||||
header:SetWidth(blockWidth)
|
||||
|
||||
-- Get display name from category definition
|
||||
local displayName = catName
|
||||
if addon.Modules.CategoryManager then
|
||||
local catDef = addon.Modules.CategoryManager:GetCategory(catName)
|
||||
if catDef and catDef.name then
|
||||
displayName = catDef.name
|
||||
if addon.Modules.CategoryManager then
|
||||
local groupsByName = addon.Modules.CategoryManager:GetCategoriesByGroup()
|
||||
for groupName, catIds in pairs(groupsByName) do
|
||||
if mergedGroups[groupName] then
|
||||
-- Merge all items from categories in this group into one section
|
||||
local mergedItems = {}
|
||||
local mergedName = groupName
|
||||
local mergedIcon = nil
|
||||
for _, catId in ipairs(catIds) do
|
||||
local items = categories[catId]
|
||||
if items then
|
||||
local orderIdx = catOrderIndex[catId] or 999
|
||||
for _, item in ipairs(items) do
|
||||
item.categoryOrderIndex = orderIdx
|
||||
table.insert(mergedItems, item)
|
||||
end
|
||||
end
|
||||
processedCats[catId] = true
|
||||
end
|
||||
if table.getn(mergedItems) > 0 then
|
||||
table.insert(mergedDisplayList, {
|
||||
name = mergedName,
|
||||
items = mergedItems,
|
||||
icon = nil, -- Group header, no single icon
|
||||
})
|
||||
end
|
||||
end
|
||||
header.fullName = displayName
|
||||
header.isShortened = false
|
||||
if string.len(displayName) > 8 and numItems < 2 then
|
||||
displayName = string.sub(displayName, 1, 6) .. "..."
|
||||
header.isShortened = true
|
||||
end
|
||||
header.text:SetText(displayName)
|
||||
header:Show()
|
||||
end
|
||||
end
|
||||
|
||||
local itemY = currentY + 20
|
||||
local col = 0
|
||||
local row = 0
|
||||
-- Track total buttons created across all category blocks
|
||||
local totalButtonsCreated = 0
|
||||
|
||||
-- Helper to render a category block
|
||||
local function RenderCategoryBlock(catName, items, numItems, catDef, isEmptyCat)
|
||||
addon:DebugCategory(" Category '%s': %d items, pos=(%d,%d)", catName, numItems, currentX, currentY)
|
||||
Guda_SortCategoryItems(items)
|
||||
|
||||
local effectiveItems = numItems
|
||||
if isEmptyCat then effectiveItems = 1 end
|
||||
|
||||
local blockCols = effectiveItems
|
||||
if blockCols > perRow then blockCols = perRow end
|
||||
local blockRows = math.ceil(effectiveItems / 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 + 20 > 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", startX + currentX, startY - currentY)
|
||||
header:SetWidth(blockWidth)
|
||||
|
||||
-- Get display name from category definition
|
||||
local displayName = catName
|
||||
if catDef and catDef.name then
|
||||
displayName = catDef.name
|
||||
end
|
||||
|
||||
-- Set item count
|
||||
if header.countText then
|
||||
if numItems > 0 then
|
||||
header.countText:SetText("(" .. numItems .. ")")
|
||||
header.countText:Show()
|
||||
else
|
||||
header.countText:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
header.fullName = displayName
|
||||
header.isShortened = false
|
||||
if string.len(displayName) > 8 and effectiveItems < 2 then
|
||||
displayName = string.sub(displayName, 1, 6) .. "..."
|
||||
header.isShortened = true
|
||||
end
|
||||
header.text:SetText(displayName)
|
||||
header:Show()
|
||||
|
||||
local itemY = currentY + 20
|
||||
local col = 0
|
||||
local row = 0
|
||||
|
||||
if isEmptyCat then
|
||||
-- Render empty slot indicator
|
||||
local bagID = firstFreeBag or 0
|
||||
local slotID = firstFreeSlot or 1
|
||||
local bagParent = self:GetBagParent(bagID)
|
||||
local button = Guda_GetItemButton(bagParent)
|
||||
button:SetParent(bagParent)
|
||||
button:SetWidth(buttonSize)
|
||||
button:SetHeight(buttonSize)
|
||||
button:ClearAllPoints()
|
||||
button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - itemY)
|
||||
button:Show()
|
||||
|
||||
local emptyItemData = {
|
||||
texture = "Interface\\PaperDoll\\UI-PaperDoll-Slot-Bag",
|
||||
count = totalFreeSlots,
|
||||
name = "Empty Slots"
|
||||
}
|
||||
Guda_ItemButton_SetItem(button, bagID, slotID, emptyItemData, false, isOtherChar and charName or nil, true, true)
|
||||
button.isReadOnly = false
|
||||
button.inUse = true
|
||||
table.insert(itemButtons, button)
|
||||
else
|
||||
for _, item in ipairs(items) do
|
||||
local bagID = item.bagID
|
||||
local slot = item.slotID
|
||||
@@ -1064,7 +1140,6 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
||||
Guda_ItemButton_SetItem(button, bagID, slot, itemData, false, isOtherChar and charName or nil, matchesFilter, isOtherChar)
|
||||
button.inUse = true
|
||||
table.insert(itemButtons, button)
|
||||
-- Populate slot lookup for O(1) access
|
||||
if not slotToButton[bagID] then slotToButton[bagID] = {} end
|
||||
slotToButton[bagID][slot] = button
|
||||
|
||||
@@ -1074,7 +1149,6 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
||||
row = row + 1
|
||||
end
|
||||
|
||||
-- Frame budget check
|
||||
categoryItemsProcessed = categoryItemsProcessed + 1
|
||||
if categoryItemsProcessed >= CATEGORY_ITEMS_PER_BUDGET_CHECK then
|
||||
categoryItemsProcessed = 0
|
||||
@@ -1083,25 +1157,50 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
totalButtonsCreated = totalButtonsCreated + numItems
|
||||
if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end
|
||||
currentX = currentX + blockWidth + 20
|
||||
totalButtonsCreated = totalButtonsCreated + effectiveItems
|
||||
if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end
|
||||
currentX = currentX + blockWidth + 20
|
||||
end
|
||||
|
||||
-- Render merged group sections first
|
||||
for _, merged in ipairs(mergedDisplayList) do
|
||||
local numItems = table.getn(merged.items)
|
||||
if numItems > 0 then
|
||||
RenderCategoryBlock(merged.name, merged.items, numItems, { name = merged.name, icon = merged.icon })
|
||||
end
|
||||
end
|
||||
|
||||
-- Render individual categories (skip merged ones and Empty)
|
||||
for _, catName in ipairs(categoryList) do
|
||||
if not processedCats[catName] then
|
||||
local catDef = addon.Modules.CategoryManager and addon.Modules.CategoryManager:GetCategory(catName) or nil
|
||||
|
||||
-- Handle Empty category specially
|
||||
if catDef and catDef.isEmptyCategory then
|
||||
if totalFreeSlots > 0 and catDef.enabled then
|
||||
RenderCategoryBlock(catName, {}, 0, catDef, true)
|
||||
end
|
||||
else
|
||||
local items = categories[catName]
|
||||
local numItems = items and table.getn(items) or 0
|
||||
if numItems > 0 then
|
||||
RenderCategoryBlock(catName, items, numItems, catDef, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
addon:DebugCategory("DisplayItemsByCategory: totalButtonsCreated=%d from categories", totalButtonsCreated)
|
||||
|
||||
-- Update Y for bottom sections
|
||||
local y = currentY + rowMaxHeight
|
||||
|
||||
-- Special sections at bottom (Hearthstone, Mount, Tools, Empty)
|
||||
|
||||
-- Special sections at bottom (only Mounts now)
|
||||
local bottomSections = {
|
||||
{ name = "Home", items = specialItems.Hearthstone },
|
||||
{ name = "Mounts", items = specialItems.Mount },
|
||||
{ name = "Tools", items = specialItems.Tools },
|
||||
{ name = "Empty", items = {} }
|
||||
}
|
||||
|
||||
|
||||
local x = startX
|
||||
y = startY - y
|
||||
|
||||
@@ -1112,10 +1211,6 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Also check if Empty section should show
|
||||
if totalFreeSlots > 0 then
|
||||
hasAnyBottom = true
|
||||
end
|
||||
|
||||
if hasAnyBottom then
|
||||
y = y - 10
|
||||
@@ -1125,22 +1220,7 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
||||
for _, sec in ipairs(bottomSections) do
|
||||
local items = sec.items
|
||||
local numItems = table.getn(items)
|
||||
if sec.name == "Empty" then
|
||||
numItems = (totalFreeSlots > 0) and 1 or 0
|
||||
end
|
||||
if numItems > 0 then
|
||||
if sec.name == "Tools" then
|
||||
table.sort(items, function(a, b)
|
||||
-- Guard against nil entries
|
||||
if not a or not a.itemData then return false end
|
||||
if not b or not b.itemData then return true end
|
||||
if (a.itemData.quality or 0) ~= (b.itemData.quality or 0) then
|
||||
return (a.itemData.quality or 0) > (b.itemData.quality or 0)
|
||||
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)
|
||||
@@ -1158,61 +1238,38 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
||||
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + currentBottomX, y)
|
||||
header:SetWidth(blockWidth)
|
||||
header.text:SetText(sec.name)
|
||||
if header.countText then header.countText:SetText("(" .. numItems .. ")"); header.countText:Show() end
|
||||
header:Show()
|
||||
|
||||
local itemY = y - 20
|
||||
local sCol = 0
|
||||
local sRow = 0
|
||||
|
||||
if sec.name == "Empty" then
|
||||
local bagID = firstFreeBag or 0
|
||||
local slotID = firstFreeSlot or 1
|
||||
local bagParent = self:GetBagParent(bagID)
|
||||
|
||||
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 + currentBottomX, itemY)
|
||||
button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + currentBottomX + (sCol * (buttonSize + spacing)), itemY - (sRow * (buttonSize + spacing)))
|
||||
button:Show()
|
||||
|
||||
local emptyItemData = {
|
||||
texture = "Interface\\PaperDoll\\UI-PaperDoll-Slot-Bag",
|
||||
count = totalFreeSlots,
|
||||
name = "Empty Slots"
|
||||
}
|
||||
Guda_ItemButton_SetItem(button, bagID, slotID, emptyItemData, false, isOtherChar and charName or nil, true, true)
|
||||
button.isReadOnly = false
|
||||
Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, false, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), isOtherChar)
|
||||
button.inUse = true
|
||||
table.insert(itemButtons, button)
|
||||
else
|
||||
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 + currentBottomX + (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
|
||||
table.insert(itemButtons, button)
|
||||
-- Populate slot lookup for O(1) access
|
||||
if not slotToButton[item.bagID] then slotToButton[item.bagID] = {} end
|
||||
slotToButton[item.bagID][item.slotID] = button
|
||||
if not slotToButton[item.bagID] then slotToButton[item.bagID] = {} end
|
||||
slotToButton[item.bagID][item.slotID] = button
|
||||
|
||||
sCol = sCol + 1
|
||||
if sCol >= blockCols then
|
||||
sCol = 0
|
||||
sRow = sRow + 1
|
||||
end
|
||||
sCol = sCol + 1
|
||||
if sCol >= blockCols then
|
||||
sCol = 0
|
||||
sRow = sRow + 1
|
||||
end
|
||||
end
|
||||
|
||||
if blockHeight > sectionMaxHeight then sectionMaxHeight = blockHeight end
|
||||
currentBottomX = currentBottomX + blockWidth + 20
|
||||
|
||||
|
||||
if currentBottomX >= totalWidth then
|
||||
currentBottomX = 0
|
||||
y = y - sectionMaxHeight - 5
|
||||
@@ -1220,7 +1277,7 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if currentBottomX > 0 then
|
||||
y = y - sectionMaxHeight
|
||||
end
|
||||
|
||||
+38
-63
@@ -779,6 +779,13 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
||||
local CATEGORY_ITEMS_PER_BUDGET_CHECK = 8
|
||||
|
||||
for _, catName in ipairs(categoryList) do
|
||||
local catDef = addon.Modules.CategoryManager and addon.Modules.CategoryManager:GetCategory(catName) or nil
|
||||
|
||||
-- Skip Empty category in bank (bank doesn't need empty slot indicator in category loop)
|
||||
if catDef and catDef.isEmptyCategory then
|
||||
-- Skip empty categories in bank view
|
||||
else
|
||||
|
||||
local items = categories[catName]
|
||||
local numItems = items and table.getn(items) or 0
|
||||
if numItems > 0 then
|
||||
@@ -803,9 +810,19 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
||||
headerIdx = headerIdx + 1
|
||||
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY)
|
||||
header:SetWidth(blockWidth)
|
||||
|
||||
|
||||
local displayName = catName
|
||||
header.fullName = catName
|
||||
if catDef and catDef.name then
|
||||
displayName = catDef.name
|
||||
end
|
||||
|
||||
-- Set item count
|
||||
if header.countText then
|
||||
header.countText:SetText("(" .. numItems .. ")")
|
||||
header.countText:Show()
|
||||
end
|
||||
|
||||
header.fullName = displayName
|
||||
header.isShortened = false
|
||||
if string.len(displayName) > 10 and numItems < 2 then
|
||||
displayName = string.sub(displayName, 1, 7) .. "..."
|
||||
@@ -865,22 +882,20 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
||||
if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end
|
||||
currentX = currentX + blockWidth + 20
|
||||
end
|
||||
end -- isEmptyCategory else
|
||||
end
|
||||
|
||||
-- Update Y for bottom sections
|
||||
local y = currentY + rowMaxHeight
|
||||
|
||||
-- Special sections at bottom (Hearthstone, Mount, Tools, Empty)
|
||||
|
||||
-- Special sections at bottom (only Mounts now; Home/Tools/Empty are real categories)
|
||||
local bottomSections = {
|
||||
{ name = "Home", items = specialItems.Hearthstone },
|
||||
{ name = "Mounts", items = specialItems.Mount },
|
||||
{ name = "Tools", items = specialItems.Tools },
|
||||
{ name = "Empty", items = {} }
|
||||
}
|
||||
|
||||
|
||||
local x = startX
|
||||
y = startY - y
|
||||
|
||||
|
||||
local hasAnyBottom = false
|
||||
for _, sec in ipairs(bottomSections) do
|
||||
if table.getn(sec.items) > 0 then
|
||||
@@ -888,10 +903,6 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
||||
break
|
||||
end
|
||||
end
|
||||
-- Also show bottom section if there are free slots (for Empty drop target)
|
||||
if totalFreeSlots > 0 then
|
||||
hasAnyBottom = true
|
||||
end
|
||||
|
||||
if hasAnyBottom then
|
||||
y = y - 10
|
||||
@@ -901,22 +912,7 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
||||
for _, sec in ipairs(bottomSections) do
|
||||
local items = sec.items
|
||||
local numItems = table.getn(items)
|
||||
if sec.name == "Empty" then
|
||||
numItems = (totalFreeSlots > 0) and 1 or 0
|
||||
end
|
||||
if numItems > 0 then
|
||||
if sec.name == "Tools" then
|
||||
table.sort(items, function(a, b)
|
||||
-- Guard against nil entries
|
||||
if not a or not a.itemData then return false end
|
||||
if not b or not b.itemData then return true end
|
||||
if (a.itemData.quality or 0) ~= (b.itemData.quality or 0) then
|
||||
return (a.itemData.quality or 0) > (b.itemData.quality or 0)
|
||||
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)
|
||||
@@ -934,59 +930,38 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
||||
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + currentBottomX, y)
|
||||
header:SetWidth(blockWidth)
|
||||
header.text:SetText(sec.name)
|
||||
if header.countText then header.countText:SetText("(" .. numItems .. ")"); header.countText:Show() end
|
||||
header:Show()
|
||||
|
||||
local itemY = y - 20
|
||||
local sCol = 0
|
||||
local sRow = 0
|
||||
|
||||
if sec.name == "Empty" then
|
||||
local bagID = firstFreeBag or -1
|
||||
local slotID = firstFreeSlot or 1
|
||||
local bagParent = self:GetBagParent(bagID)
|
||||
|
||||
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 + currentBottomX, itemY)
|
||||
button:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + currentBottomX + (sCol * (buttonSize + spacing)), itemY - (sRow * (buttonSize + spacing)))
|
||||
button:Show()
|
||||
|
||||
local emptyItemData = {
|
||||
texture = "Interface\\PaperDoll\\UI-PaperDoll-Slot-Bag",
|
||||
count = totalFreeSlots,
|
||||
name = "Empty Slots"
|
||||
}
|
||||
Guda_ItemButton_SetItem(button, bagID, slotID, emptyItemData, true, isOtherChar and charName or nil, true, true)
|
||||
button.isReadOnly = false
|
||||
Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, true, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), isOtherChar or isReadOnlyMode)
|
||||
button.inUse = true
|
||||
else
|
||||
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 + currentBottomX + (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), isOtherChar or isReadOnlyMode)
|
||||
button.inUse = true
|
||||
-- Populate slot lookup for O(1) access
|
||||
if not bankSlotToButton[item.bagID] then bankSlotToButton[item.bagID] = {} end
|
||||
bankSlotToButton[item.bagID][item.slotID] = button
|
||||
-- Populate slot lookup for O(1) access
|
||||
if not bankSlotToButton[item.bagID] then bankSlotToButton[item.bagID] = {} end
|
||||
bankSlotToButton[item.bagID][item.slotID] = button
|
||||
|
||||
sCol = sCol + 1
|
||||
if sCol >= blockCols then
|
||||
sCol = 0
|
||||
sRow = sRow + 1
|
||||
end
|
||||
sCol = sCol + 1
|
||||
if sCol >= blockCols then
|
||||
sCol = 0
|
||||
sRow = sRow + 1
|
||||
end
|
||||
end
|
||||
|
||||
if blockHeight > sectionMaxHeight then sectionMaxHeight = blockHeight end
|
||||
currentBottomX = currentBottomX + blockWidth + 20
|
||||
|
||||
|
||||
if currentBottomX >= totalWidth then
|
||||
currentBottomX = 0
|
||||
y = y - sectionMaxHeight - 5
|
||||
|
||||
+21
-36
@@ -21,7 +21,6 @@ end
|
||||
-- Returns nothing, modifies tables in place
|
||||
function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems, isOtherChar)
|
||||
local itemName = itemData.name or ""
|
||||
local itemType = itemData.type or ""
|
||||
local cat = "Miscellaneous"
|
||||
|
||||
-- Detect consumable restore/eat/drink tag for current character only
|
||||
@@ -32,38 +31,11 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
|
||||
end
|
||||
end
|
||||
|
||||
-- Priority 1: Special items (Hearthstone, Mounts, Tools)
|
||||
-- These are handled separately and go into specialItems table, not categories
|
||||
if string.find(itemName, "Hearthstone") then
|
||||
-- Only show in Home section if Home category is enabled
|
||||
local showHome = true
|
||||
if addon.Modules.CategoryManager then
|
||||
local homeCat = addon.Modules.CategoryManager:GetCategory("Home")
|
||||
if homeCat then
|
||||
showHome = homeCat.enabled
|
||||
end
|
||||
end
|
||||
if showHome then
|
||||
table.insert(specialItems.Hearthstone, {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||
end
|
||||
-- Always return - if Home is disabled, Hearthstone is hidden completely
|
||||
return
|
||||
elseif addon.Modules.SortEngine and addon.Modules.SortEngine.IsMount and addon.Modules.SortEngine.IsMount(itemData.texture) then
|
||||
-- Special items: only Mounts are handled separately now
|
||||
-- Home and Tools are real categories handled by CategoryManager rules
|
||||
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
|
||||
elseif string.find(itemName, "Runed .* Rod") or
|
||||
itemType == "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" or
|
||||
string.find(itemName, "Jeweler") or
|
||||
string.find(itemName, "Jewelry Kit") then
|
||||
table.insert(specialItems.Tools, {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||
return
|
||||
end
|
||||
|
||||
-- Use CategoryManager rule engine if available, otherwise fall back to legacy logic
|
||||
@@ -218,22 +190,20 @@ function Guda_InitCategories()
|
||||
end
|
||||
|
||||
-- Reuse or create specialItems table
|
||||
-- Only Mount remains as a special item; Home and Tools are now real categories
|
||||
if not specialItemsCache then
|
||||
specialItemsCache = {
|
||||
Hearthstone = {},
|
||||
Mount = {},
|
||||
Tools = {}
|
||||
}
|
||||
else
|
||||
WipeTable(specialItemsCache.Hearthstone)
|
||||
WipeTable(specialItemsCache.Mount)
|
||||
WipeTable(specialItemsCache.Tools)
|
||||
end
|
||||
|
||||
return categoriesCache, specialItemsCache
|
||||
end
|
||||
|
||||
-- Sort items within a category
|
||||
-- Sort items within a category (or merged group)
|
||||
-- Items may have a categoryOrderIndex field set by merged group display
|
||||
function Guda_SortCategoryItems(items)
|
||||
if not items then return end
|
||||
table.sort(items, function(a, b)
|
||||
@@ -243,6 +213,13 @@ function Guda_SortCategoryItems(items)
|
||||
if not a.itemData then return false end
|
||||
if not b.itemData then return true end
|
||||
|
||||
-- Primary: category order index (for merged groups)
|
||||
local oa = a.categoryOrderIndex or 0
|
||||
local ob = b.categoryOrderIndex or 0
|
||||
if oa ~= ob then
|
||||
return oa < ob
|
||||
end
|
||||
|
||||
-- Rank Trade Goods: meat (name ends with 'meat') = 2, egg (contains 'egg') = 1, others = 0
|
||||
local function tgRank(d)
|
||||
if not d or not d.name then return 0 end
|
||||
@@ -290,10 +267,18 @@ function Guda_GetSectionHeader(framePrefix, containerName, index)
|
||||
header = CreateFrame("Frame", name, container)
|
||||
header:SetHeight(20)
|
||||
header:EnableMouse(true)
|
||||
|
||||
-- Category name text
|
||||
local text = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
text:SetPoint("LEFT", header, "LEFT", 0, 0)
|
||||
header.text = text
|
||||
|
||||
-- Item count text
|
||||
local countText = header:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
|
||||
countText:SetPoint("LEFT", text, "RIGHT", 4, 0)
|
||||
countText:SetTextColor(0.6, 0.6, 0.6)
|
||||
header.countText = countText
|
||||
|
||||
header:SetScript("OnEnter", function()
|
||||
if this.fullName and this.isShortened then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||
|
||||
+64
-39
@@ -639,6 +639,8 @@ local function ResetButtonVisualState(self)
|
||||
HideInnerShadow(self.innerShadow)
|
||||
if self.unusableOverlay then self.unusableOverlay:Hide() end
|
||||
HideJunkIcon(self)
|
||||
if self.categoryMarkIcon then self.categoryMarkIcon:Hide() end
|
||||
if self.categoryMarkShadow then self.categoryMarkShadow:Hide() end
|
||||
|
||||
-- Clear cooldown overlay
|
||||
local cd = getglobal(self:GetName().."Cooldown") or self.cooldown
|
||||
@@ -738,11 +740,9 @@ local function UpdateEmptySlotBackground(self, emptySlotBg, iconSize)
|
||||
if not emptySlotBg then return end
|
||||
|
||||
emptySlotBg:ClearAllPoints()
|
||||
-- Use smaller padding for small icons
|
||||
local bgPadding = iconSize < 44 and 1 or 2
|
||||
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", -bgPadding, bgPadding)
|
||||
emptySlotBg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", bgPadding, -bgPadding)
|
||||
emptySlotBg:SetTexCoord(0.1, 0.9, 0.1, 0.9)
|
||||
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
|
||||
emptySlotBg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0)
|
||||
emptySlotBg:SetTexCoord(0.17, 0.83, 0.17, 0.83)
|
||||
end
|
||||
|
||||
-- Resize texture elements to match button size
|
||||
@@ -793,12 +793,11 @@ local function PositionIconAndBorders(self, iconSize)
|
||||
|
||||
if not iconTexture or not self.hasItem then return end
|
||||
|
||||
-- Calculate icon inset based on size
|
||||
local iconInset = iconSize < 44 and 10 or 15
|
||||
local iconDisplaySize = iconSize - iconInset
|
||||
-- Icon fills the full button slot
|
||||
local iconDisplaySize = iconSize
|
||||
|
||||
iconTexture:ClearAllPoints()
|
||||
iconTexture:SetPoint("CENTER", self, "CENTER", -0.5, 0.5)
|
||||
iconTexture:SetPoint("CENTER", self, "CENTER", 0, 0)
|
||||
iconTexture:SetWidth(iconDisplaySize)
|
||||
iconTexture:SetHeight(iconDisplaySize)
|
||||
iconTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
@@ -807,15 +806,15 @@ local function PositionIconAndBorders(self, iconSize)
|
||||
-- Position quality border around the icon
|
||||
if self.qualityBorder then
|
||||
self.qualityBorder:ClearAllPoints()
|
||||
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
||||
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
||||
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -1, 1)
|
||||
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 1, -1)
|
||||
end
|
||||
|
||||
-- Position quest border around the icon
|
||||
if self.questBorder then
|
||||
self.questBorder:ClearAllPoints()
|
||||
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
||||
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
||||
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -1, 1)
|
||||
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 1, -1)
|
||||
end
|
||||
|
||||
-- Position quest icon in top-right corner
|
||||
@@ -1056,15 +1055,12 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
end
|
||||
end
|
||||
|
||||
-- Resize empty slot background to match icon size (slightly larger to ensure coverage)
|
||||
-- Resize empty slot background to fill the full button
|
||||
if emptySlotBg then
|
||||
emptySlotBg:ClearAllPoints()
|
||||
-- Use smaller padding for small icons
|
||||
local bgPadding = iconSize < 44 and 1 or 2
|
||||
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", -bgPadding, bgPadding)
|
||||
emptySlotBg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", bgPadding, -bgPadding)
|
||||
-- Crop texture edges slightly to remove any built-in padding
|
||||
emptySlotBg:SetTexCoord(0.1, 0.9, 0.1, 0.9)
|
||||
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
|
||||
emptySlotBg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0)
|
||||
emptySlotBg:SetTexCoord(0.17, 0.83, 0.17, 0.83)
|
||||
end
|
||||
|
||||
-- Also resize the underlying slot textures so the border/background scale with the button.
|
||||
@@ -1231,11 +1227,49 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
end
|
||||
end
|
||||
|
||||
-- Check if item is junk using the CategoryManager
|
||||
-- Check if item is junk and get category mark using the CategoryManager
|
||||
local isJunk = false
|
||||
local categoryMarkTexture = nil
|
||||
if itemData and addon.Modules.CategoryManager then
|
||||
local category = addon.Modules.CategoryManager:CategorizeItem(itemData, bagID, slotID, self.otherChar)
|
||||
isJunk = (category == "Junk")
|
||||
-- Get category mark icon if set
|
||||
local catDef = addon.Modules.CategoryManager:GetCategory(category)
|
||||
if catDef and catDef.categoryMark then
|
||||
categoryMarkTexture = catDef.categoryMark
|
||||
end
|
||||
end
|
||||
|
||||
-- Update category mark overlay (bottom-left of icon texture)
|
||||
if categoryMarkTexture then
|
||||
local iconTex = getglobal(self:GetName().."IconTexture") or getglobal(self:GetName().."Icon") or self.icon or self.Icon
|
||||
local anchor = iconTex or self
|
||||
-- Create mark textures lazily
|
||||
if not self.categoryMarkShadow then
|
||||
self.categoryMarkShadow = self:CreateTexture(nil, "OVERLAY")
|
||||
self.categoryMarkShadow:SetVertexColor(0, 0, 0, 1)
|
||||
end
|
||||
if not self.categoryMarkIcon then
|
||||
self.categoryMarkIcon = self:CreateTexture(nil, "OVERLAY")
|
||||
end
|
||||
-- Size relative to icon (roughly 40% of icon display size)
|
||||
local markSize = math.max(10, math.floor(iconSize * 0.3))
|
||||
local shadowSize = markSize + 2
|
||||
self.categoryMarkShadow:SetWidth(shadowSize)
|
||||
self.categoryMarkShadow:SetHeight(shadowSize)
|
||||
self.categoryMarkShadow:ClearAllPoints()
|
||||
self.categoryMarkShadow:SetPoint("BOTTOMLEFT", anchor, "BOTTOMLEFT", -1, -1)
|
||||
self.categoryMarkIcon:SetWidth(markSize)
|
||||
self.categoryMarkIcon:SetHeight(markSize)
|
||||
self.categoryMarkIcon:ClearAllPoints()
|
||||
self.categoryMarkIcon:SetPoint("BOTTOMLEFT", anchor, "BOTTOMLEFT", 0, 0)
|
||||
self.categoryMarkShadow:SetTexture(categoryMarkTexture)
|
||||
self.categoryMarkShadow:Show()
|
||||
self.categoryMarkIcon:SetTexture(categoryMarkTexture)
|
||||
self.categoryMarkIcon:Show()
|
||||
else
|
||||
if self.categoryMarkIcon then self.categoryMarkIcon:Hide() end
|
||||
if self.categoryMarkShadow then self.categoryMarkShadow:Hide() end
|
||||
end
|
||||
|
||||
-- Search filtering and junk opacity
|
||||
@@ -1410,37 +1444,28 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
||||
|
||||
if iconTexture then
|
||||
if self.hasItem then
|
||||
-- Scale icon proportionally based on button size
|
||||
-- For icons < 44: use smaller inset (4px) for better fit
|
||||
-- For icons >= 44: use larger inset (15px) for classic look
|
||||
local iconInset
|
||||
if iconSize < 44 then
|
||||
iconInset = 10 -- Small inset for small icons
|
||||
else
|
||||
iconInset = 15 -- Larger inset for larger icons
|
||||
end
|
||||
|
||||
local iconDisplaySize = iconSize - iconInset
|
||||
-- Icon fills the full button slot
|
||||
local iconDisplaySize = iconSize
|
||||
iconTexture:ClearAllPoints()
|
||||
iconTexture:SetPoint("CENTER", self, "CENTER", -0.5, 0.5)
|
||||
iconTexture:SetPoint("CENTER", self, "CENTER", 0, 0)
|
||||
iconTexture:SetWidth(iconDisplaySize)
|
||||
iconTexture:SetHeight(iconDisplaySize)
|
||||
-- Crop icon edges slightly
|
||||
iconTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||
iconTexture:Show()
|
||||
|
||||
-- Position quality border around the icon (not the slot)
|
||||
-- Position quality border around the icon
|
||||
if self.qualityBorder then
|
||||
self.qualityBorder:ClearAllPoints()
|
||||
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
||||
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
||||
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -1, 1)
|
||||
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 1, -1)
|
||||
end
|
||||
|
||||
-- Position quest border around the icon (same as quality border)
|
||||
-- Position quest border around the icon
|
||||
if self.questBorder then
|
||||
self.questBorder:ClearAllPoints()
|
||||
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
||||
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
||||
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -1, 1)
|
||||
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 1, -1)
|
||||
end
|
||||
|
||||
-- Position quest icon in top-right corner
|
||||
|
||||
+222
-43
@@ -92,9 +92,9 @@ function Guda_SettingsPopup_OnShow(self)
|
||||
-- Load current settings
|
||||
local bagColumns = Guda.Modules.DB:GetSetting("bagColumns") or 10
|
||||
local bankColumns = Guda.Modules.DB:GetSetting("bankColumns") or 10
|
||||
local iconSize = Guda.Modules.DB:GetSetting("iconSize") or 40
|
||||
local iconSize = Guda.Modules.DB:GetSetting("iconSize") or 37
|
||||
local iconFontSize = Guda.Modules.DB:GetSetting("iconFontSize") or 12
|
||||
local iconSpacing = Guda.Modules.DB:GetSetting("iconSpacing") or 0
|
||||
local iconSpacing = Guda.Modules.DB:GetSetting("iconSpacing") or 3
|
||||
local lockBags = Guda.Modules.DB:GetSetting("lockBags")
|
||||
if lockBags == nil then
|
||||
lockBags = false
|
||||
@@ -438,7 +438,7 @@ function Guda_SettingsPopup_IconSizeSlider_OnLoad(self)
|
||||
self:SetMinMaxValues(22, 64)
|
||||
self:SetValueStep(1)
|
||||
|
||||
local currentValue = Guda.Modules.DB:GetSetting("iconSize") or addon.Constants.BUTTON_SIZE
|
||||
local currentValue = Guda.Modules.DB:GetSetting("iconSize") or 37
|
||||
self:SetValue(currentValue)
|
||||
end
|
||||
|
||||
@@ -503,7 +503,7 @@ end
|
||||
|
||||
-- Icon Spacing Slider OnLoad
|
||||
function Guda_SettingsPopup_IconSpacingSlider_OnLoad(self)
|
||||
getglobal(self:GetName().."Low"):SetText("-10px")
|
||||
getglobal(self:GetName().."Low"):SetText("0px")
|
||||
getglobal(self:GetName().."High"):SetText("20px")
|
||||
|
||||
local text = getglobal(self:GetName().."Text")
|
||||
@@ -515,10 +515,10 @@ function Guda_SettingsPopup_IconSpacingSlider_OnLoad(self)
|
||||
text:SetFont(font, 12, flags)
|
||||
end
|
||||
|
||||
self:SetMinMaxValues(-10, 20)
|
||||
self:SetMinMaxValues(0, 20)
|
||||
self:SetValueStep(1)
|
||||
|
||||
local currentValue = Guda.Modules.DB:GetSetting("iconSpacing") or 0
|
||||
local currentValue = Guda.Modules.DB:GetSetting("iconSpacing") or 3
|
||||
self:SetValue(currentValue)
|
||||
end
|
||||
|
||||
@@ -1409,6 +1409,30 @@ local function GetCategoryRowFrame(index)
|
||||
return row
|
||||
end
|
||||
|
||||
-- Build a flat display list with group headers inserted
|
||||
local function BuildCategoryDisplayList()
|
||||
if not Guda.Modules.CategoryManager then return {}, 0 end
|
||||
|
||||
local categoryOrder = Guda.Modules.CategoryManager:GetCategoryOrder()
|
||||
local displayList = {} -- entries: { type = "header"|"category", groupName, categoryId, categoryDef }
|
||||
local lastGroup = nil
|
||||
|
||||
for _, catId in ipairs(categoryOrder) do
|
||||
local catDef = Guda.Modules.CategoryManager:GetCategory(catId)
|
||||
if catDef then
|
||||
local group = catDef.group or "Main"
|
||||
-- Insert group header if group changed
|
||||
if group ~= lastGroup then
|
||||
table.insert(displayList, { type = "header", groupName = group })
|
||||
lastGroup = group
|
||||
end
|
||||
table.insert(displayList, { type = "category", categoryId = catId, categoryDef = catDef })
|
||||
end
|
||||
end
|
||||
|
||||
return displayList, table.getn(displayList)
|
||||
end
|
||||
|
||||
-- Update the category list display
|
||||
function Guda_SettingsPopup_CategoriesTab_Update()
|
||||
if not Guda.Modules.CategoryManager then return end
|
||||
@@ -1416,11 +1440,10 @@ function Guda_SettingsPopup_CategoriesTab_Update()
|
||||
local scrollFrame = getglobal("Guda_SettingsPopup_CategoriesScrollFrame")
|
||||
if not scrollFrame then return end
|
||||
|
||||
local categoryOrder = Guda.Modules.CategoryManager:GetCategoryOrder()
|
||||
local totalCategories = table.getn(categoryOrder)
|
||||
local displayList, totalEntries = BuildCategoryDisplayList()
|
||||
|
||||
-- Update scroll frame
|
||||
FauxScrollFrame_Update(scrollFrame, totalCategories, CATEGORY_VISIBLE_ROWS, CATEGORY_ROW_HEIGHT)
|
||||
FauxScrollFrame_Update(scrollFrame, totalEntries, CATEGORY_VISIBLE_ROWS, CATEGORY_ROW_HEIGHT)
|
||||
|
||||
local offset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||
|
||||
@@ -1428,13 +1451,28 @@ function Guda_SettingsPopup_CategoriesTab_Update()
|
||||
local row = GetCategoryRowFrame(i)
|
||||
if row then
|
||||
local dataIndex = i + offset
|
||||
if dataIndex <= totalCategories then
|
||||
local categoryId = categoryOrder[dataIndex]
|
||||
local categoryDef = Guda.Modules.CategoryManager:GetCategory(categoryId)
|
||||
if dataIndex <= totalEntries then
|
||||
local entry = displayList[dataIndex]
|
||||
|
||||
if entry.type == "header" then
|
||||
-- Show as group header row
|
||||
row.categoryId = nil
|
||||
row.nameText:SetText("|cffffd100-- " .. entry.groupName .. " --|r")
|
||||
row.nameText:SetTextColor(1, 0.82, 0)
|
||||
row.checkbox:Hide()
|
||||
row.editBtn:Hide()
|
||||
row.upBtn:Hide()
|
||||
row.downBtn:Hide()
|
||||
row.deleteBtn:Hide()
|
||||
row.builtInText:Hide()
|
||||
row:Show()
|
||||
elseif entry.type == "category" then
|
||||
local categoryId = entry.categoryId
|
||||
local categoryDef = entry.categoryDef
|
||||
|
||||
if categoryDef then
|
||||
row.categoryId = categoryId
|
||||
row.nameText:SetText(categoryDef.name or categoryId)
|
||||
row.checkbox:Show()
|
||||
row.checkbox:SetChecked(categoryDef.enabled and 1 or 0)
|
||||
|
||||
-- Show/hide delete button based on whether it's built-in
|
||||
@@ -1458,24 +1496,17 @@ function Guda_SettingsPopup_CategoriesTab_Update()
|
||||
row.upBtn:Show()
|
||||
row.downBtn:Show()
|
||||
|
||||
-- Enable/disable move buttons based on position
|
||||
if dataIndex == 1 then
|
||||
row.upBtn:Disable()
|
||||
-- Enable/disable move buttons based on group-aware boundaries
|
||||
if Guda.Modules.CategoryManager:CanMoveUp(categoryId) then
|
||||
row.upBtn:Enable()
|
||||
else
|
||||
-- Check if category above has hideControls (can't move above it)
|
||||
local aboveCatId = categoryOrder[dataIndex - 1]
|
||||
local aboveCatDef = Guda.Modules.CategoryManager:GetCategory(aboveCatId)
|
||||
if aboveCatDef and aboveCatDef.hideControls then
|
||||
row.upBtn:Disable()
|
||||
else
|
||||
row.upBtn:Enable()
|
||||
end
|
||||
row.upBtn:Disable()
|
||||
end
|
||||
|
||||
if dataIndex == totalCategories then
|
||||
row.downBtn:Disable()
|
||||
else
|
||||
if Guda.Modules.CategoryManager:CanMoveDown(categoryId) then
|
||||
row.downBtn:Enable()
|
||||
else
|
||||
row.downBtn:Disable()
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1512,30 +1543,21 @@ end
|
||||
function Guda_SettingsPopup_AddCategory_OnClick()
|
||||
if not Guda.Modules.CategoryManager then return end
|
||||
|
||||
-- Generate unique ID
|
||||
local baseId = "Custom"
|
||||
local counter = 1
|
||||
local newId = baseId .. counter
|
||||
|
||||
local cats = Guda.Modules.CategoryManager:GetCategories()
|
||||
while cats.definitions[newId] do
|
||||
counter = counter + 1
|
||||
newId = baseId .. counter
|
||||
end
|
||||
|
||||
-- Create new category definition
|
||||
local newDef = {
|
||||
name = "Custom " .. counter,
|
||||
name = "Custom Category",
|
||||
icon = "Interface\\Icons\\INV_Misc_QuestionMark",
|
||||
rules = {},
|
||||
matchMode = "any",
|
||||
priority = 80,
|
||||
enabled = true,
|
||||
isBuiltIn = false,
|
||||
group = Guda.Modules.CategoryManager:GetGroupMain(),
|
||||
}
|
||||
|
||||
-- Add to database
|
||||
if Guda.Modules.CategoryManager:AddCategory(newId, newDef) then
|
||||
-- Add to database with auto-generated ID
|
||||
local success, newId = Guda.Modules.CategoryManager:AddCategory(nil, newDef)
|
||||
if success and newId then
|
||||
-- Update display
|
||||
Guda_SettingsPopup_CategoriesTab_Update()
|
||||
-- Open editor for the new category
|
||||
@@ -1577,8 +1599,19 @@ end
|
||||
|
||||
local editorCategoryId = nil
|
||||
local editorMatchMode = "any"
|
||||
local editorGroup = "Main"
|
||||
local editorMark = nil -- current category mark texture path or nil
|
||||
local editorRules = {}
|
||||
local editorRuleFrames = {}
|
||||
|
||||
-- Available mark icons (texture paths)
|
||||
local MARK_ICONS = {
|
||||
"Interface\\AddOns\\Guda\\Assets\\equipment",
|
||||
"Interface\\AddOns\\Guda\\Assets\\plus",
|
||||
"Interface\\AddOns\\Guda\\Assets\\fav",
|
||||
"Interface\\AddOns\\Guda\\Assets\\combat",
|
||||
"Interface\\AddOns\\Guda\\Assets\\Cog",
|
||||
}
|
||||
local RULE_ROW_HEIGHT = 28
|
||||
local MAX_RULES = 22
|
||||
|
||||
@@ -1625,6 +1658,91 @@ function Guda_CategoryEditor_OnLoad(self)
|
||||
local cancelBtn = getglobal("Guda_CategoryEditor_CancelButton")
|
||||
if cancelBtn then cancelBtn:SetText("Cancel") end
|
||||
|
||||
-- Create group EditBox if it doesn't exist
|
||||
if not getglobal("Guda_CategoryEditor_GroupEditBox") then
|
||||
local nameBox = getglobal("Guda_CategoryEditor_NameEditBox")
|
||||
if nameBox then
|
||||
-- Label
|
||||
local groupLabel = self:CreateFontString("Guda_CategoryEditor_GroupLabel", "OVERLAY", "GameFontNormalSmall")
|
||||
groupLabel:SetPoint("LEFT", nameBox, "RIGHT", 14, 0)
|
||||
groupLabel:SetText("Group:")
|
||||
groupLabel:SetTextColor(0.7, 0.7, 0.7)
|
||||
|
||||
-- EditBox
|
||||
local groupBox = CreateFrame("EditBox", "Guda_CategoryEditor_GroupEditBox", self, "InputBoxTemplate")
|
||||
groupBox:SetWidth(100)
|
||||
groupBox:SetHeight(22)
|
||||
groupBox:SetPoint("LEFT", groupLabel, "RIGHT", 6, 0)
|
||||
groupBox:SetAutoFocus(false)
|
||||
groupBox:SetMaxLetters(20)
|
||||
groupBox:SetScript("OnEscapePressed", function() this:ClearFocus() end)
|
||||
groupBox:SetScript("OnEnterPressed", function() this:ClearFocus() end)
|
||||
end
|
||||
end
|
||||
|
||||
-- Create mark icon selector row
|
||||
if not getglobal("Guda_CategoryEditor_MarkLabel") then
|
||||
local MARK_BTN_SIZE = 22
|
||||
local MARK_BTN_SPACING = 6
|
||||
|
||||
local markLabel = self:CreateFontString("Guda_CategoryEditor_MarkLabel", "OVERLAY", "GameFontNormalSmall")
|
||||
markLabel:SetPoint("TOPLEFT", self, "TOPLEFT", 20, -68)
|
||||
markLabel:SetText("Mark:")
|
||||
markLabel:SetTextColor(0.7, 0.7, 0.7)
|
||||
|
||||
-- "None" button (first)
|
||||
local noneBtn = CreateFrame("Button", "Guda_CategoryEditor_MarkNone", self)
|
||||
noneBtn:SetWidth(MARK_BTN_SIZE)
|
||||
noneBtn:SetHeight(MARK_BTN_SIZE)
|
||||
noneBtn:SetPoint("LEFT", markLabel, "RIGHT", 8, 0)
|
||||
noneBtn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 })
|
||||
noneBtn:SetBackdropColor(0.15, 0.15, 0.15, 0.8)
|
||||
noneBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
|
||||
noneBtn.markPath = nil
|
||||
|
||||
local noneTex = noneBtn:CreateTexture(nil, "ARTWORK")
|
||||
noneTex:SetWidth(12)
|
||||
noneTex:SetHeight(12)
|
||||
noneTex:SetPoint("CENTER", noneBtn, "CENTER", 0, 0)
|
||||
noneTex:SetTexture("Interface\\Buttons\\UI-StopButton")
|
||||
noneTex:SetVertexColor(0.6, 0.6, 0.6)
|
||||
|
||||
noneBtn:SetScript("OnClick", function()
|
||||
editorMark = nil
|
||||
Guda_CategoryEditor_UpdateMarkButtons()
|
||||
end)
|
||||
|
||||
self.markButtons = { noneBtn }
|
||||
|
||||
-- Icon buttons
|
||||
local prevBtn = noneBtn
|
||||
for i = 1, table.getn(MARK_ICONS) do
|
||||
local iconPath = MARK_ICONS[i]
|
||||
local btn = CreateFrame("Button", "Guda_CategoryEditor_Mark" .. i, self)
|
||||
btn:SetWidth(MARK_BTN_SIZE)
|
||||
btn:SetHeight(MARK_BTN_SIZE)
|
||||
btn:SetPoint("LEFT", prevBtn, "RIGHT", MARK_BTN_SPACING, 0)
|
||||
btn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8x8", edgeFile = "Interface\\Buttons\\WHITE8x8", edgeSize = 1 })
|
||||
btn:SetBackdropColor(0.15, 0.15, 0.15, 0.8)
|
||||
btn:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
|
||||
btn.markPath = iconPath
|
||||
|
||||
local tex = btn:CreateTexture(nil, "ARTWORK")
|
||||
tex:SetWidth(16)
|
||||
tex:SetHeight(16)
|
||||
tex:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||||
tex:SetTexture(iconPath)
|
||||
|
||||
btn:SetScript("OnClick", function()
|
||||
editorMark = this.markPath
|
||||
Guda_CategoryEditor_UpdateMarkButtons()
|
||||
end)
|
||||
|
||||
table.insert(self.markButtons, btn)
|
||||
prevBtn = btn
|
||||
end
|
||||
end
|
||||
|
||||
-- Create radio button labels
|
||||
local anyRadio = getglobal("Guda_CategoryEditor_MatchAny")
|
||||
if anyRadio then
|
||||
@@ -1641,6 +1759,19 @@ function Guda_CategoryEditor_OnLoad(self)
|
||||
end
|
||||
end
|
||||
|
||||
-- Update mark button highlights (gold = selected, gray = unselected)
|
||||
function Guda_CategoryEditor_UpdateMarkButtons()
|
||||
local editor = getglobal("Guda_CategoryEditor")
|
||||
if not editor or not editor.markButtons then return end
|
||||
for _, btn in ipairs(editor.markButtons) do
|
||||
if btn.markPath == editorMark then
|
||||
btn:SetBackdropBorderColor(1, 0.82, 0, 1)
|
||||
else
|
||||
btn:SetBackdropBorderColor(0.4, 0.4, 0.4, 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- OnShow for Category Editor
|
||||
function Guda_CategoryEditor_OnShow(self)
|
||||
Guda_CategoryEditor_UpdateRulesDisplay()
|
||||
@@ -1655,6 +1786,8 @@ function Guda_CategoryEditor_Open(categoryId)
|
||||
|
||||
editorCategoryId = categoryId
|
||||
editorMatchMode = categoryDef.matchMode or "any"
|
||||
editorGroup = categoryDef.group or "Main"
|
||||
editorMark = categoryDef.categoryMark or nil
|
||||
|
||||
-- Copy rules
|
||||
editorRules = {}
|
||||
@@ -1690,9 +1823,29 @@ function Guda_CategoryEditor_Open(categoryId)
|
||||
end
|
||||
end
|
||||
|
||||
-- Set group EditBox
|
||||
local groupBox = getglobal("Guda_CategoryEditor_GroupEditBox")
|
||||
if groupBox then
|
||||
groupBox:SetText(editorGroup or "")
|
||||
-- Disable group editing for built-in categories
|
||||
if categoryDef.isBuiltIn then
|
||||
groupBox:EnableMouse(false)
|
||||
groupBox:EnableKeyboard(false)
|
||||
groupBox:SetTextColor(0.5, 0.5, 0.5)
|
||||
else
|
||||
groupBox:EnableMouse(true)
|
||||
groupBox:EnableKeyboard(true)
|
||||
groupBox:SetTextColor(1, 1, 1)
|
||||
end
|
||||
groupBox:ClearFocus()
|
||||
end
|
||||
|
||||
-- Set match mode
|
||||
Guda_CategoryEditor_SetMatchMode(editorMatchMode)
|
||||
|
||||
-- Update mark button highlights
|
||||
Guda_CategoryEditor_UpdateMarkButtons()
|
||||
|
||||
-- Show editor
|
||||
local editor = getglobal("Guda_CategoryEditor")
|
||||
if editor then
|
||||
@@ -1700,6 +1853,8 @@ function Guda_CategoryEditor_Open(categoryId)
|
||||
end
|
||||
end
|
||||
|
||||
-- (Group is now an EditBox — no dropdown needed)
|
||||
|
||||
-- Set match mode (radio buttons)
|
||||
function Guda_CategoryEditor_SetMatchMode(mode)
|
||||
editorMatchMode = mode
|
||||
@@ -2065,6 +2220,9 @@ function Guda_CategoryEditor_Save()
|
||||
-- Set match mode
|
||||
categoryDef.matchMode = editorMatchMode
|
||||
|
||||
-- Set category mark
|
||||
categoryDef.categoryMark = editorMark
|
||||
|
||||
-- Set rules
|
||||
categoryDef.rules = {}
|
||||
for _, rule in ipairs(editorRules) do
|
||||
@@ -2073,8 +2231,29 @@ function Guda_CategoryEditor_Save()
|
||||
end
|
||||
end
|
||||
|
||||
-- Save to database
|
||||
Guda.Modules.CategoryManager:UpdateCategory(editorCategoryId, categoryDef)
|
||||
-- Read group from EditBox
|
||||
local groupBox = getglobal("Guda_CategoryEditor_GroupEditBox")
|
||||
local newGroup = "Main" -- default
|
||||
if groupBox then
|
||||
local text = groupBox:GetText() or ""
|
||||
-- Trim whitespace
|
||||
text = string.gsub(text, "^%s+", "")
|
||||
text = string.gsub(text, "%s+$", "")
|
||||
if text ~= "" then
|
||||
newGroup = text
|
||||
end
|
||||
end
|
||||
|
||||
-- Set group (handle group change via SetCategoryGroup for proper reordering)
|
||||
local oldGroup = categoryDef.group or "Main"
|
||||
if newGroup ~= oldGroup then
|
||||
-- Save definition first, then move group
|
||||
Guda.Modules.CategoryManager:UpdateCategory(editorCategoryId, categoryDef)
|
||||
Guda.Modules.CategoryManager:SetCategoryGroup(editorCategoryId, newGroup)
|
||||
else
|
||||
-- Save to database
|
||||
Guda.Modules.CategoryManager:UpdateCategory(editorCategoryId, categoryDef)
|
||||
end
|
||||
|
||||
-- Refresh displays
|
||||
Guda_SettingsPopup_CategoriesTab_Update()
|
||||
|
||||
@@ -919,7 +919,7 @@
|
||||
<!-- Name EditBox -->
|
||||
<EditBox name="Guda_CategoryEditor_NameEditBox" autoFocus="false" inherits="InputBoxTemplate">
|
||||
<Size>
|
||||
<AbsDimension x="200" y="20"/>
|
||||
<AbsDimension x="150" y="20"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT">
|
||||
@@ -946,7 +946,7 @@
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="100" y="-77"/>
|
||||
<AbsDimension x="100" y="-97"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
@@ -979,12 +979,12 @@
|
||||
<!-- Rules ScrollFrame -->
|
||||
<ScrollFrame name="Guda_CategoryEditor_RulesScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||
<Size>
|
||||
<AbsDimension x="335" y="280"/>
|
||||
<AbsDimension x="335" y="260"/>
|
||||
</Size>
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT">
|
||||
<Offset>
|
||||
<AbsDimension x="20" y="-130"/>
|
||||
<AbsDimension x="20" y="-150"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
@@ -1013,7 +1013,7 @@
|
||||
<Anchors>
|
||||
<Anchor point="TOPRIGHT">
|
||||
<Offset>
|
||||
<AbsDimension x="-20" y="-112"/>
|
||||
<AbsDimension x="-20" y="-132"/>
|
||||
</Offset>
|
||||
</Anchor>
|
||||
</Anchors>
|
||||
|
||||
Reference in New Issue
Block a user