feat: update categories and item slots
This commit is contained in:
+566
-20
@@ -52,13 +52,21 @@ end
|
|||||||
-- texturePattern: Match icon texture path
|
-- texturePattern: Match icon texture path
|
||||||
-- itemID: Specific item IDs (table of IDs)
|
-- itemID: Specific item IDs (table of IDs)
|
||||||
|
|
||||||
|
-- Group constants
|
||||||
|
local GROUP_MAIN = "Main"
|
||||||
|
local GROUP_OTHER = "Other"
|
||||||
|
local GROUP_CLASS = "Class"
|
||||||
|
|
||||||
-- Default category definitions that replicate the existing hardcoded behavior
|
-- Default category definitions that replicate the existing hardcoded behavior
|
||||||
local DEFAULT_CATEGORIES = {
|
local DEFAULT_CATEGORIES = {
|
||||||
order = {
|
order = {
|
||||||
"Home", "BoE", "Weapon", "Armor", "Consumable", "Food", "Drink",
|
"BoE", "Weapon", "Armor", "Consumable", "Food", "Drink",
|
||||||
"Trade Goods", "Reagent", "Recipe", "Quiver", "Container",
|
"Trade Goods", "Reagent", "Recipe", "Quiver", "Container",
|
||||||
"Soul Bag", "Miscellaneous", "Quest", "Junk", "Class Items", "Keyring"
|
"Soul Bag", "Miscellaneous", "Quest", "Junk",
|
||||||
|
"Class Items", "Keyring",
|
||||||
|
"Home", "Tools", "Empty"
|
||||||
},
|
},
|
||||||
|
itemOverrides = {}, -- flat map: [itemID] = categoryId
|
||||||
definitions = {
|
definitions = {
|
||||||
["BoE"] = {
|
["BoE"] = {
|
||||||
name = "BoE",
|
name = "BoE",
|
||||||
@@ -70,6 +78,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 75,
|
priority = 75,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Weapon"] = {
|
["Weapon"] = {
|
||||||
name = "Weapon",
|
name = "Weapon",
|
||||||
@@ -81,6 +90,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 70,
|
priority = 70,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Armor"] = {
|
["Armor"] = {
|
||||||
name = "Armor",
|
name = "Armor",
|
||||||
@@ -92,6 +102,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 70,
|
priority = 70,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Consumable"] = {
|
["Consumable"] = {
|
||||||
name = "Consumable",
|
name = "Consumable",
|
||||||
@@ -103,6 +114,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 50,
|
priority = 50,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Food"] = {
|
["Food"] = {
|
||||||
name = "Food",
|
name = "Food",
|
||||||
@@ -115,6 +127,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 55,
|
priority = 55,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Drink"] = {
|
["Drink"] = {
|
||||||
name = "Drink",
|
name = "Drink",
|
||||||
@@ -127,6 +140,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 55,
|
priority = 55,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Trade Goods"] = {
|
["Trade Goods"] = {
|
||||||
name = "Trade Goods",
|
name = "Trade Goods",
|
||||||
@@ -138,6 +152,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 40,
|
priority = 40,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Reagent"] = {
|
["Reagent"] = {
|
||||||
name = "Reagent",
|
name = "Reagent",
|
||||||
@@ -149,6 +164,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 40,
|
priority = 40,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Recipe"] = {
|
["Recipe"] = {
|
||||||
name = "Recipe",
|
name = "Recipe",
|
||||||
@@ -160,6 +176,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 40,
|
priority = 40,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Quiver"] = {
|
["Quiver"] = {
|
||||||
name = "Quiver",
|
name = "Quiver",
|
||||||
@@ -171,6 +188,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 40,
|
priority = 40,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Container"] = {
|
["Container"] = {
|
||||||
name = "Container",
|
name = "Container",
|
||||||
@@ -182,6 +200,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 40,
|
priority = 40,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Soul Bag"] = {
|
["Soul Bag"] = {
|
||||||
name = "Soul Bag",
|
name = "Soul Bag",
|
||||||
@@ -193,6 +212,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 45,
|
priority = 45,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Miscellaneous"] = {
|
["Miscellaneous"] = {
|
||||||
name = "Miscellaneous",
|
name = "Miscellaneous",
|
||||||
@@ -203,6 +223,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
isFallback = true,
|
isFallback = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Quest"] = {
|
["Quest"] = {
|
||||||
name = "Quest",
|
name = "Quest",
|
||||||
@@ -214,6 +235,7 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 80,
|
priority = 80,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Junk"] = {
|
["Junk"] = {
|
||||||
name = "Junk",
|
name = "Junk",
|
||||||
@@ -225,17 +247,20 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 85,
|
priority = 85,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
},
|
},
|
||||||
["Class Items"] = {
|
["Class Items"] = {
|
||||||
name = "Class Items",
|
name = "Class Items",
|
||||||
icon = "Interface\\Icons\\INV_Misc_Ammo_Arrow_01",
|
icon = "Interface\\Icons\\INV_Misc_Ammo_Arrow_01",
|
||||||
rules = {
|
rules = {
|
||||||
{ type = "itemType", value = "Projectile" }
|
{ type = "itemType", value = "Projectile" },
|
||||||
|
{ type = "isSoulShard", value = true }
|
||||||
},
|
},
|
||||||
matchMode = "any",
|
matchMode = "any",
|
||||||
priority = 90,
|
priority = 90,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_CLASS,
|
||||||
},
|
},
|
||||||
["Keyring"] = {
|
["Keyring"] = {
|
||||||
name = "Keyring",
|
name = "Keyring",
|
||||||
@@ -247,20 +272,56 @@ local DEFAULT_CATEGORIES = {
|
|||||||
priority = 40,
|
priority = 40,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
|
group = GROUP_CLASS,
|
||||||
},
|
},
|
||||||
["Home"] = {
|
["Home"] = {
|
||||||
name = "Home",
|
name = "Home",
|
||||||
icon = "Interface\\Icons\\INV_Misc_Rune_01",
|
icon = "Interface\\Icons\\INV_Misc_Rune_01",
|
||||||
|
rules = {
|
||||||
|
{ type = "itemID", value = {6948} }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 100,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
group = GROUP_OTHER,
|
||||||
|
},
|
||||||
|
["Tools"] = {
|
||||||
|
name = "Tools",
|
||||||
|
icon = "Interface\\Icons\\Trade_BlackSmithing",
|
||||||
|
rules = {
|
||||||
|
{ type = "isProfessionTool", value = true }
|
||||||
|
},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 60,
|
||||||
|
enabled = true,
|
||||||
|
isBuiltIn = true,
|
||||||
|
group = GROUP_OTHER,
|
||||||
|
},
|
||||||
|
["Empty"] = {
|
||||||
|
name = "Empty",
|
||||||
|
icon = "Interface\\PaperDoll\\UI-PaperDoll-Slot-Bag",
|
||||||
rules = {},
|
rules = {},
|
||||||
matchMode = "all",
|
matchMode = "all",
|
||||||
priority = 0,
|
priority = -10,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = true,
|
isBuiltIn = true,
|
||||||
hideControls = true,
|
hideControls = true,
|
||||||
|
isEmptyCategory = true,
|
||||||
|
group = GROUP_OTHER,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- Group definitions for display order and built-in group mapping
|
||||||
|
local GROUP_ORDER = { GROUP_MAIN, GROUP_CLASS, GROUP_OTHER }
|
||||||
|
|
||||||
|
-- Map of built-in category IDs to their default groups (for migration)
|
||||||
|
local BUILTIN_GROUP_MAP = {}
|
||||||
|
for id, def in pairs(DEFAULT_CATEGORIES.definitions) do
|
||||||
|
BUILTIN_GROUP_MAP[id] = def.group
|
||||||
|
end
|
||||||
|
|
||||||
-- Deep copy a table
|
-- Deep copy a table
|
||||||
local function deepCopy(orig)
|
local function deepCopy(orig)
|
||||||
local copy
|
local copy
|
||||||
@@ -404,6 +465,130 @@ function CategoryManager:MigrateCategories()
|
|||||||
addon:Debug("CategoryManager: Migrated Junk category to use isJunk rule")
|
addon:Debug("CategoryManager: Migrated Junk category to use isJunk rule")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Migrate: Add group to all categories that lack it
|
||||||
|
for id, def in pairs(cats.definitions) do
|
||||||
|
if not def.group then
|
||||||
|
-- Use built-in mapping if available, otherwise default to Main
|
||||||
|
def.group = BUILTIN_GROUP_MAP[id] or GROUP_MAIN
|
||||||
|
addon:Debug("CategoryManager: Added group '%s' to category: %s", def.group, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Migrate: Convert per-category itemOverrides arrays to flat map at categories level
|
||||||
|
if not cats.itemOverrides then
|
||||||
|
cats.itemOverrides = {}
|
||||||
|
end
|
||||||
|
local migratedOverrides = false
|
||||||
|
for catId, def in pairs(cats.definitions) do
|
||||||
|
if def.itemOverrides and type(def.itemOverrides) == "table" then
|
||||||
|
-- Check if it's an array (old format) by looking for numeric keys
|
||||||
|
local isArray = false
|
||||||
|
for k, v in pairs(def.itemOverrides) do
|
||||||
|
if type(k) == "number" then
|
||||||
|
isArray = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if isArray then
|
||||||
|
for _, itemID in ipairs(def.itemOverrides) do
|
||||||
|
cats.itemOverrides[itemID] = catId
|
||||||
|
migratedOverrides = true
|
||||||
|
end
|
||||||
|
def.itemOverrides = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if migratedOverrides then
|
||||||
|
addon:Debug("CategoryManager: Migrated per-category itemOverrides to flat map")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Migrate Home category: add rules and remove hideControls
|
||||||
|
local homeCat = cats.definitions["Home"]
|
||||||
|
if homeCat and homeCat.isBuiltIn then
|
||||||
|
-- Remove hideControls
|
||||||
|
if homeCat.hideControls then
|
||||||
|
homeCat.hideControls = nil
|
||||||
|
addon:Debug("CategoryManager: Removed hideControls from Home")
|
||||||
|
end
|
||||||
|
-- Add rules if empty
|
||||||
|
if not homeCat.rules or table.getn(homeCat.rules) == 0 then
|
||||||
|
homeCat.rules = { { type = "itemID", value = {6948} } }
|
||||||
|
homeCat.priority = 100
|
||||||
|
addon:Debug("CategoryManager: Added itemID rule to Home category")
|
||||||
|
end
|
||||||
|
-- Ensure group is Other
|
||||||
|
if homeCat.group ~= GROUP_OTHER then
|
||||||
|
homeCat.group = GROUP_OTHER
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Migrate Class Items: add isSoulShard rule if missing
|
||||||
|
local classItemsCat = cats.definitions["Class Items"]
|
||||||
|
if classItemsCat and classItemsCat.isBuiltIn then
|
||||||
|
local hasSoulShard = false
|
||||||
|
if classItemsCat.rules then
|
||||||
|
for _, rule in ipairs(classItemsCat.rules) do
|
||||||
|
if rule.type == "isSoulShard" then
|
||||||
|
hasSoulShard = true
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not hasSoulShard then
|
||||||
|
if not classItemsCat.rules then classItemsCat.rules = {} end
|
||||||
|
table.insert(classItemsCat.rules, { type = "isSoulShard", value = true })
|
||||||
|
classItemsCat.matchMode = "any"
|
||||||
|
addon:Debug("CategoryManager: Added isSoulShard rule to Class Items")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Ensure new categories in the order list are in correct positions
|
||||||
|
-- Check if order needs rebuilding to include new group-based ordering
|
||||||
|
local hasTools, hasEmpty = false, false
|
||||||
|
for _, id in ipairs(cats.order) do
|
||||||
|
if id == "Tools" then hasTools = true end
|
||||||
|
if id == "Empty" then hasEmpty = true end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- If Tools or Empty were just added by the built-in migration above,
|
||||||
|
-- they're already at end of order. Move them to the Other group area.
|
||||||
|
if hasTools or hasEmpty then
|
||||||
|
-- Rebuild order to respect groups: Other, Main, Class
|
||||||
|
local grouped = {}
|
||||||
|
for _, g in ipairs(GROUP_ORDER) do
|
||||||
|
grouped[g] = {}
|
||||||
|
end
|
||||||
|
grouped["_ungrouped"] = {}
|
||||||
|
|
||||||
|
for _, id in ipairs(cats.order) do
|
||||||
|
local def = cats.definitions[id]
|
||||||
|
if def then
|
||||||
|
local g = def.group or GROUP_MAIN
|
||||||
|
if grouped[g] then
|
||||||
|
table.insert(grouped[g], id)
|
||||||
|
else
|
||||||
|
table.insert(grouped["_ungrouped"], id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Rebuild order
|
||||||
|
local newOrder = {}
|
||||||
|
for _, g in ipairs(GROUP_ORDER) do
|
||||||
|
if grouped[g] then
|
||||||
|
for _, id in ipairs(grouped[g]) do
|
||||||
|
table.insert(newOrder, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
for _, id in ipairs(grouped["_ungrouped"]) do
|
||||||
|
table.insert(newOrder, id)
|
||||||
|
end
|
||||||
|
|
||||||
|
cats.order = newOrder
|
||||||
|
addon:Debug("CategoryManager: Rebuilt category order for group ordering")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Get all categories
|
-- Get all categories
|
||||||
@@ -438,20 +623,50 @@ function CategoryManager:SaveCategories(categories)
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Add a new custom category
|
-- Add a new custom category
|
||||||
|
-- If categoryId is nil, auto-generates a unique ID like "Custom_<time>_<random>"
|
||||||
function CategoryManager:AddCategory(categoryId, definition)
|
function CategoryManager:AddCategory(categoryId, definition)
|
||||||
local cats = self:GetCategories()
|
local cats = self:GetCategories()
|
||||||
|
|
||||||
|
-- Auto-generate ID if not provided
|
||||||
|
if not categoryId then
|
||||||
|
categoryId = "Custom_" .. time() .. "_" .. math.random(1000, 9999)
|
||||||
|
-- Ensure unique
|
||||||
|
while cats.definitions[categoryId] do
|
||||||
|
categoryId = "Custom_" .. time() .. "_" .. math.random(1000, 9999)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
if cats.definitions[categoryId] then
|
if cats.definitions[categoryId] then
|
||||||
addon:Debug("CategoryManager: Category already exists: " .. categoryId)
|
addon:Debug("CategoryManager: Category already exists: " .. categoryId)
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
definition.isBuiltIn = false
|
definition.isBuiltIn = definition.isBuiltIn or false
|
||||||
|
if not definition.group then
|
||||||
|
definition.group = GROUP_MAIN
|
||||||
|
end
|
||||||
cats.definitions[categoryId] = definition
|
cats.definitions[categoryId] = definition
|
||||||
table.insert(cats.order, categoryId)
|
|
||||||
|
-- Insert at end of the category's group in the order list
|
||||||
|
local insertPos = nil
|
||||||
|
local group = definition.group
|
||||||
|
-- Find last category in the same group
|
||||||
|
for i = table.getn(cats.order), 1, -1 do
|
||||||
|
local existDef = cats.definitions[cats.order[i]]
|
||||||
|
if existDef and existDef.group == group then
|
||||||
|
insertPos = i + 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if insertPos then
|
||||||
|
-- Lua 5.0 table.insert with position
|
||||||
|
table.insert(cats.order, insertPos, categoryId)
|
||||||
|
else
|
||||||
|
table.insert(cats.order, categoryId)
|
||||||
|
end
|
||||||
|
|
||||||
self:SaveCategories(cats)
|
self:SaveCategories(cats)
|
||||||
return true
|
return true, categoryId
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update an existing category
|
-- Update an existing category
|
||||||
@@ -499,32 +714,106 @@ function CategoryManager:DeleteCategory(categoryId)
|
|||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Move category up in order
|
-- Check if a category can move up within its group
|
||||||
function CategoryManager:MoveCategoryUp(categoryId)
|
function CategoryManager:CanMoveUp(categoryId)
|
||||||
local cats = self:GetCategories()
|
local cats = self:GetCategories()
|
||||||
|
local def = cats.definitions[categoryId]
|
||||||
|
if not def or def.hideControls then return false end
|
||||||
|
|
||||||
for i, id in ipairs(cats.order) do
|
for i, id in ipairs(cats.order) do
|
||||||
if id == categoryId and i > 1 then
|
if id == categoryId then
|
||||||
cats.order[i] = cats.order[i - 1]
|
-- Find any previous non-hideControls category (can cross group boundaries)
|
||||||
cats.order[i - 1] = categoryId
|
for j = i - 1, 1, -1 do
|
||||||
self:SaveCategories(cats)
|
local prevDef = cats.definitions[cats.order[j]]
|
||||||
return true
|
if prevDef and not prevDef.hideControls then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false -- very first movable category
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Move category down in order
|
-- Check if a category can move down (crosses group boundaries)
|
||||||
|
function CategoryManager:CanMoveDown(categoryId)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local def = cats.definitions[categoryId]
|
||||||
|
if not def or def.hideControls then return false end
|
||||||
|
|
||||||
|
local count = table.getn(cats.order)
|
||||||
|
for i, id in ipairs(cats.order) do
|
||||||
|
if id == categoryId then
|
||||||
|
-- Find any next non-hideControls category (can cross group boundaries)
|
||||||
|
for j = i + 1, count do
|
||||||
|
local nextDef = cats.definitions[cats.order[j]]
|
||||||
|
if nextDef and not nextDef.hideControls then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false -- very last movable category
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move category up in order (crosses group boundaries, changes group when crossing)
|
||||||
|
function CategoryManager:MoveCategoryUp(categoryId)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local def = cats.definitions[categoryId]
|
||||||
|
if not def or def.hideControls then return false end
|
||||||
|
|
||||||
|
for i, id in ipairs(cats.order) do
|
||||||
|
if id == categoryId then
|
||||||
|
-- Find previous non-hideControls category
|
||||||
|
for j = i - 1, 1, -1 do
|
||||||
|
local prevDef = cats.definitions[cats.order[j]]
|
||||||
|
if prevDef and not prevDef.hideControls then
|
||||||
|
-- Swap positions
|
||||||
|
cats.order[i] = cats.order[j]
|
||||||
|
cats.order[j] = categoryId
|
||||||
|
-- If crossing into a different group, adopt that group
|
||||||
|
local prevGroup = prevDef.group or GROUP_MAIN
|
||||||
|
if (def.group or GROUP_MAIN) ~= prevGroup then
|
||||||
|
def.group = prevGroup
|
||||||
|
end
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Move category down in order (crosses group boundaries, changes group when crossing)
|
||||||
function CategoryManager:MoveCategoryDown(categoryId)
|
function CategoryManager:MoveCategoryDown(categoryId)
|
||||||
local cats = self:GetCategories()
|
local cats = self:GetCategories()
|
||||||
|
local def = cats.definitions[categoryId]
|
||||||
|
if not def or def.hideControls then return false end
|
||||||
|
|
||||||
local count = table.getn(cats.order)
|
local count = table.getn(cats.order)
|
||||||
|
|
||||||
for i, id in ipairs(cats.order) do
|
for i, id in ipairs(cats.order) do
|
||||||
if id == categoryId and i < count then
|
if id == categoryId then
|
||||||
cats.order[i] = cats.order[i + 1]
|
-- Find next non-hideControls category
|
||||||
cats.order[i + 1] = categoryId
|
for j = i + 1, count do
|
||||||
self:SaveCategories(cats)
|
local nextDef = cats.definitions[cats.order[j]]
|
||||||
return true
|
if nextDef and not nextDef.hideControls then
|
||||||
|
-- Swap positions
|
||||||
|
cats.order[i] = cats.order[j]
|
||||||
|
cats.order[j] = categoryId
|
||||||
|
-- If crossing into a different group, adopt that group
|
||||||
|
local nextGroup = nextDef.group or GROUP_MAIN
|
||||||
|
if (def.group or GROUP_MAIN) ~= nextGroup then
|
||||||
|
def.group = nextGroup
|
||||||
|
end
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
@@ -742,6 +1031,26 @@ function CategoryManager:CategorizeItem(itemData, bagID, slotID, isOtherChar)
|
|||||||
end
|
end
|
||||||
cacheMisses = cacheMisses + 1
|
cacheMisses = cacheMisses + 1
|
||||||
|
|
||||||
|
-- Check item overrides first (flat map: itemID -> categoryId)
|
||||||
|
if itemData and itemData.link then
|
||||||
|
local itemID = addon.Modules.Utils:ExtractItemID(itemData.link)
|
||||||
|
if itemID then
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
if cats.itemOverrides then
|
||||||
|
local overrideCatId = cats.itemOverrides[itemID]
|
||||||
|
if overrideCatId then
|
||||||
|
local overrideDef = cats.definitions[overrideCatId]
|
||||||
|
if overrideDef and overrideDef.enabled then
|
||||||
|
if cacheKey then
|
||||||
|
categoryCache[cacheKey] = overrideCatId
|
||||||
|
end
|
||||||
|
return overrideCatId
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
local sortedCats = self:GetCategoriesByPriority()
|
local sortedCats = self:GetCategoriesByPriority()
|
||||||
|
|
||||||
-- Debug: show white item categorization
|
-- Debug: show white item categorization
|
||||||
@@ -791,6 +1100,243 @@ function CategoryManager:BuildCategoryList()
|
|||||||
return list
|
return list
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Group Management
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
-- Get ordered list of unique groups from the category order
|
||||||
|
function CategoryManager:GetGroups()
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local seen = {}
|
||||||
|
local groups = {}
|
||||||
|
for _, id in ipairs(cats.order) do
|
||||||
|
local def = cats.definitions[id]
|
||||||
|
if def then
|
||||||
|
local g = def.group or GROUP_MAIN
|
||||||
|
if not seen[g] then
|
||||||
|
seen[g] = true
|
||||||
|
table.insert(groups, g)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return groups
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get categories organized by group: { groupName => {catIds} }
|
||||||
|
-- Also returns ungrouped list for any categories without a group
|
||||||
|
function CategoryManager:GetCategoriesByGroup()
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local result = {}
|
||||||
|
local ungrouped = {}
|
||||||
|
|
||||||
|
for _, id in ipairs(cats.order) do
|
||||||
|
local def = cats.definitions[id]
|
||||||
|
if def then
|
||||||
|
local g = def.group
|
||||||
|
if g then
|
||||||
|
if not result[g] then result[g] = {} end
|
||||||
|
table.insert(result[g], id)
|
||||||
|
else
|
||||||
|
table.insert(ungrouped, id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return result, ungrouped
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Change a category's group
|
||||||
|
function CategoryManager:SetCategoryGroup(categoryId, groupName)
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local def = cats.definitions[categoryId]
|
||||||
|
if not def then return false end
|
||||||
|
|
||||||
|
local oldGroup = def.group or GROUP_MAIN
|
||||||
|
if oldGroup == groupName then return true end
|
||||||
|
|
||||||
|
def.group = groupName
|
||||||
|
|
||||||
|
-- Remove from current position in order
|
||||||
|
local currentPos = nil
|
||||||
|
for i, id in ipairs(cats.order) do
|
||||||
|
if id == categoryId then
|
||||||
|
currentPos = i
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if currentPos then
|
||||||
|
table.remove(cats.order, currentPos)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Insert at end of new group
|
||||||
|
local insertPos = nil
|
||||||
|
for i = table.getn(cats.order), 1, -1 do
|
||||||
|
local existDef = cats.definitions[cats.order[i]]
|
||||||
|
if existDef and (existDef.group or GROUP_MAIN) == groupName then
|
||||||
|
insertPos = i + 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if insertPos then
|
||||||
|
table.insert(cats.order, insertPos, categoryId)
|
||||||
|
else
|
||||||
|
table.insert(cats.order, categoryId)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get the group order (unique groups in display order)
|
||||||
|
function CategoryManager:GetGroupOrder()
|
||||||
|
return self:GetGroups()
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
-- Get group constants
|
||||||
|
function CategoryManager:GetGroupMain() return GROUP_MAIN end
|
||||||
|
function CategoryManager:GetGroupOther() return GROUP_OTHER end
|
||||||
|
function CategoryManager:GetGroupClass() return GROUP_CLASS end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Item Override System
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
-- Assign an item to a specific category by item ID
|
||||||
|
-- Uses flat map at categories level: cats.itemOverrides[itemID] = categoryId
|
||||||
|
function CategoryManager:AssignItemToCategory(itemID, categoryId)
|
||||||
|
if not itemID or not categoryId then return false end
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local def = cats.definitions[categoryId]
|
||||||
|
if not def then return false end
|
||||||
|
|
||||||
|
if not cats.itemOverrides then
|
||||||
|
cats.itemOverrides = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
cats.itemOverrides[itemID] = categoryId
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
self:ClearCache()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove an item from a specific category's overrides
|
||||||
|
function CategoryManager:RemoveItemFromCategory(itemID, categoryId)
|
||||||
|
if not itemID or not categoryId then return false end
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
if not cats.itemOverrides then return false end
|
||||||
|
|
||||||
|
if cats.itemOverrides[itemID] == categoryId then
|
||||||
|
cats.itemOverrides[itemID] = nil
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
self:ClearCache()
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove an item override regardless of category
|
||||||
|
function CategoryManager:RemoveItemOverride(itemID)
|
||||||
|
if not itemID then return end
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
if cats.itemOverrides then
|
||||||
|
cats.itemOverrides[itemID] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Equipment Set Category Sync
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
-- Saved properties for equipment set categories that were user-edited
|
||||||
|
-- Preserves user changes (enabled state, order position) when sets are deleted/recreated
|
||||||
|
local savedEquipSetProps = {}
|
||||||
|
|
||||||
|
-- Sync equipment set categories with current set data from EquipmentSets module
|
||||||
|
function CategoryManager:SyncEquipmentSetCategories()
|
||||||
|
local equipSets = addon.Modules.EquipmentSets
|
||||||
|
if not equipSets then return end
|
||||||
|
|
||||||
|
local showEquipSets = addon.Modules.DB:GetSetting("showEquipSetCategories")
|
||||||
|
if showEquipSets == false then return end
|
||||||
|
|
||||||
|
local setNames = equipSets:GetAllSetNames()
|
||||||
|
if not setNames then return end
|
||||||
|
|
||||||
|
local cats = self:GetCategories()
|
||||||
|
local existingSetCats = {}
|
||||||
|
|
||||||
|
-- Find existing EquipSet categories
|
||||||
|
for id, def in pairs(cats.definitions) do
|
||||||
|
if string.find(id, "^EquipSet:") then
|
||||||
|
existingSetCats[id] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Create/update categories for current sets
|
||||||
|
for _, setName in ipairs(setNames) do
|
||||||
|
local catId = "EquipSet:" .. setName
|
||||||
|
if not cats.definitions[catId] then
|
||||||
|
-- Check for saved properties from a previously deleted set
|
||||||
|
local props = savedEquipSetProps[catId]
|
||||||
|
local defaultMark = "Interface\\AddOns\\Guda\\Assets\\equipment"
|
||||||
|
local newDef = {
|
||||||
|
name = setName,
|
||||||
|
icon = "Interface\\Icons\\INV_Chest_Chain_04",
|
||||||
|
rules = {},
|
||||||
|
matchMode = "all",
|
||||||
|
priority = 65,
|
||||||
|
enabled = props and props.enabled or true,
|
||||||
|
isBuiltIn = false,
|
||||||
|
isEquipSetCategory = true,
|
||||||
|
group = GROUP_MAIN,
|
||||||
|
categoryMark = props and props.categoryMark or defaultMark,
|
||||||
|
}
|
||||||
|
cats.definitions[catId] = newDef
|
||||||
|
|
||||||
|
-- Add to order
|
||||||
|
local insertPos = nil
|
||||||
|
for i = table.getn(cats.order), 1, -1 do
|
||||||
|
local existDef = cats.definitions[cats.order[i]]
|
||||||
|
if existDef and (existDef.group or GROUP_MAIN) == GROUP_MAIN then
|
||||||
|
insertPos = i + 1
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if insertPos then
|
||||||
|
table.insert(cats.order, insertPos, catId)
|
||||||
|
else
|
||||||
|
table.insert(cats.order, catId)
|
||||||
|
end
|
||||||
|
|
||||||
|
addon:Debug("CategoryManager: Created equipment set category: " .. catId)
|
||||||
|
end
|
||||||
|
existingSetCats[catId] = nil -- Mark as still active
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Remove categories for sets that no longer exist
|
||||||
|
for catId in pairs(existingSetCats) do
|
||||||
|
local def = cats.definitions[catId]
|
||||||
|
if def then
|
||||||
|
-- Save user-edited properties before deletion
|
||||||
|
savedEquipSetProps[catId] = {
|
||||||
|
enabled = def.enabled,
|
||||||
|
categoryMark = def.categoryMark,
|
||||||
|
}
|
||||||
|
end
|
||||||
|
cats.definitions[catId] = nil
|
||||||
|
for i, id in ipairs(cats.order) do
|
||||||
|
if id == catId then
|
||||||
|
table.remove(cats.order, i)
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
addon:Debug("CategoryManager: Removed equipment set category: " .. catId)
|
||||||
|
end
|
||||||
|
|
||||||
|
self:SaveCategories(cats)
|
||||||
|
end
|
||||||
|
|
||||||
-- Get available rule types for UI
|
-- Get available rule types for UI
|
||||||
function CategoryManager:GetRuleTypes()
|
function CategoryManager:GetRuleTypes()
|
||||||
return {
|
return {
|
||||||
|
|||||||
+13
-4
@@ -36,8 +36,8 @@ function DB:Initialize()
|
|||||||
bankBagColumns = 8,
|
bankBagColumns = 8,
|
||||||
bankColumns = 10,
|
bankColumns = 10,
|
||||||
sortMethod = "quality", -- quality, name, type
|
sortMethod = "quality", -- quality, name, type
|
||||||
iconSize = 40,
|
iconSize = 37,
|
||||||
iconSpacing = 0,
|
iconSpacing = 3,
|
||||||
iconFontSize = 12,
|
iconFontSize = 12,
|
||||||
showQualityBorderEquipment = true,
|
showQualityBorderEquipment = true,
|
||||||
showQualityBorderOther = true,
|
showQualityBorderOther = true,
|
||||||
@@ -98,10 +98,10 @@ function DB:Initialize()
|
|||||||
Guda_CharDB.settings.bankColumns = 10
|
Guda_CharDB.settings.bankColumns = 10
|
||||||
end
|
end
|
||||||
if not Guda_CharDB.settings.iconSize then
|
if not Guda_CharDB.settings.iconSize then
|
||||||
Guda_CharDB.settings.iconSize = 40
|
Guda_CharDB.settings.iconSize = 37
|
||||||
end
|
end
|
||||||
if not Guda_CharDB.settings.iconSpacing then
|
if not Guda_CharDB.settings.iconSpacing then
|
||||||
Guda_CharDB.settings.iconSpacing = 0
|
Guda_CharDB.settings.iconSpacing = 3
|
||||||
end
|
end
|
||||||
if not Guda_CharDB.settings.iconFontSize then
|
if not Guda_CharDB.settings.iconFontSize then
|
||||||
Guda_CharDB.settings.iconFontSize = 12
|
Guda_CharDB.settings.iconFontSize = 12
|
||||||
@@ -121,6 +121,15 @@ function DB:Initialize()
|
|||||||
if Guda_CharDB.settings.markUnusableItems == nil then
|
if Guda_CharDB.settings.markUnusableItems == nil then
|
||||||
Guda_CharDB.settings.markUnusableItems = true
|
Guda_CharDB.settings.markUnusableItems = true
|
||||||
end
|
end
|
||||||
|
if Guda_CharDB.settings.mergedGroups == nil then
|
||||||
|
Guda_CharDB.settings.mergedGroups = {}
|
||||||
|
end
|
||||||
|
if Guda_CharDB.settings.showEquipSetCategories == nil then
|
||||||
|
Guda_CharDB.settings.showEquipSetCategories = true
|
||||||
|
end
|
||||||
|
if Guda_CharDB.settings.markEquipmentSets == nil then
|
||||||
|
Guda_CharDB.settings.markEquipmentSets = true
|
||||||
|
end
|
||||||
|
|
||||||
-- Initialize CategoryManager for custom categories
|
-- Initialize CategoryManager for custom categories
|
||||||
if addon.Modules.CategoryManager then
|
if addon.Modules.CategoryManager then
|
||||||
|
|||||||
+2
-2
@@ -53,8 +53,8 @@ addon.Constants = {
|
|||||||
SAVE_INTERVAL = 1800, -- 30 minutes in seconds
|
SAVE_INTERVAL = 1800, -- 30 minutes in seconds
|
||||||
|
|
||||||
-- UI Constants
|
-- UI Constants
|
||||||
BUTTON_SIZE = 40,
|
BUTTON_SIZE = 37,
|
||||||
BUTTON_SPACING = 0,
|
BUTTON_SPACING = 3,
|
||||||
BUTTONS_PER_ROW = 10,
|
BUTTONS_PER_ROW = 10,
|
||||||
MIN_ICON_SIZE = 30,
|
MIN_ICON_SIZE = 30,
|
||||||
MAX_ICON_SIZE = 64,
|
MAX_ICON_SIZE = 64,
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ function Main:Initialize()
|
|||||||
addon.Modules.MoneyTracker:Initialize()
|
addon.Modules.MoneyTracker:Initialize()
|
||||||
addon.Modules.EquipmentScanner:Initialize()
|
addon.Modules.EquipmentScanner:Initialize()
|
||||||
|
|
||||||
|
-- Initialize equipment sets (Outfitter/ItemRack integration)
|
||||||
|
if addon.Modules.EquipmentSets then
|
||||||
|
addon.Modules.EquipmentSets:Initialize()
|
||||||
|
end
|
||||||
|
|
||||||
-- Initialize UI
|
-- Initialize UI
|
||||||
addon:Print("Initializing UI...")
|
addon:Print("Initializing UI...")
|
||||||
addon.Modules.BagFrame:Initialize()
|
addon.Modules.BagFrame:Initialize()
|
||||||
|
|||||||
@@ -0,0 +1,280 @@
|
|||||||
|
-- Guda Equipment Sets Module
|
||||||
|
-- Detects and tracks equipment sets from Outfitter and ItemRack addons
|
||||||
|
-- Provides API for checking if items belong to equipment sets
|
||||||
|
|
||||||
|
local addon = Guda
|
||||||
|
|
||||||
|
local EquipmentSets = {}
|
||||||
|
addon.Modules.EquipmentSets = EquipmentSets
|
||||||
|
|
||||||
|
-- Internal state
|
||||||
|
local setData = {} -- { setName => { itemIDs = {[itemID] = true} } }
|
||||||
|
local itemToSets = {} -- { [itemID] => { setName1 = true, setName2 = true } }
|
||||||
|
local initialized = false
|
||||||
|
local outfitterReady = false
|
||||||
|
local itemRackReady = false
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Public API
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
-- Check if an item ID belongs to any equipment set
|
||||||
|
function EquipmentSets:IsInSet(itemID)
|
||||||
|
if not itemID then return false end
|
||||||
|
return itemToSets[itemID] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get set names that contain a specific item ID
|
||||||
|
-- Returns a table of set names or nil
|
||||||
|
function EquipmentSets:GetSetNames(itemID)
|
||||||
|
if not itemID then return nil end
|
||||||
|
local sets = itemToSets[itemID]
|
||||||
|
if not sets then return nil end
|
||||||
|
|
||||||
|
local names = {}
|
||||||
|
for name in pairs(sets) do
|
||||||
|
table.insert(names, name)
|
||||||
|
end
|
||||||
|
if table.getn(names) == 0 then return nil end
|
||||||
|
return names
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get all known set names (sorted)
|
||||||
|
function EquipmentSets:GetAllSetNames()
|
||||||
|
local names = {}
|
||||||
|
for name in pairs(setData) do
|
||||||
|
table.insert(names, name)
|
||||||
|
end
|
||||||
|
table.sort(names)
|
||||||
|
return names
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Internal: Rebuild item-to-set index
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
local function RebuildItemIndex()
|
||||||
|
itemToSets = {}
|
||||||
|
for setName, data in pairs(setData) do
|
||||||
|
if data.itemIDs then
|
||||||
|
for itemID in pairs(data.itemIDs) do
|
||||||
|
if not itemToSets[itemID] then
|
||||||
|
itemToSets[itemID] = {}
|
||||||
|
end
|
||||||
|
itemToSets[itemID][setName] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Outfitter Integration
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
local function ScanOutfitter()
|
||||||
|
-- Check if Outfitter is loaded and initialized
|
||||||
|
if not Outfitter_GetCategoryOrder then return false end
|
||||||
|
|
||||||
|
addon:Debug("EquipmentSets: Scanning Outfitter outfits...")
|
||||||
|
|
||||||
|
local categoryOrder = Outfitter_GetCategoryOrder()
|
||||||
|
if not categoryOrder then return false end
|
||||||
|
|
||||||
|
local scannedSets = 0
|
||||||
|
for _, catID in ipairs(categoryOrder) do
|
||||||
|
local outfits = nil
|
||||||
|
if Outfitter_GetOutfitsByCategoryID then
|
||||||
|
outfits = Outfitter_GetOutfitsByCategoryID(catID)
|
||||||
|
end
|
||||||
|
if outfits then
|
||||||
|
for _, outfit in ipairs(outfits) do
|
||||||
|
local setName = outfit.Name
|
||||||
|
if setName and outfit.Items then
|
||||||
|
local itemIDs = {}
|
||||||
|
for slotName, item in pairs(outfit.Items) do
|
||||||
|
if item then
|
||||||
|
local itemID = nil
|
||||||
|
-- Outfitter stores item codes
|
||||||
|
if item.Code then
|
||||||
|
itemID = tonumber(item.Code)
|
||||||
|
elseif item.ItemID then
|
||||||
|
itemID = tonumber(item.ItemID)
|
||||||
|
end
|
||||||
|
if itemID and itemID > 0 then
|
||||||
|
itemIDs[itemID] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setData[setName] = { itemIDs = itemIDs, source = "Outfitter" }
|
||||||
|
scannedSets = scannedSets + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
addon:Debug("EquipmentSets: Scanned %d Outfitter outfits", scannedSets)
|
||||||
|
return scannedSets > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local function HookOutfitterEvents()
|
||||||
|
if not Outfitter_RegisterOutfitEvent then return end
|
||||||
|
|
||||||
|
local events = { "ADD", "DELETE", "EDIT", "RENAME" }
|
||||||
|
for _, eventName in ipairs(events) do
|
||||||
|
local success, err = pcall(function()
|
||||||
|
Outfitter_RegisterOutfitEvent(eventName, function()
|
||||||
|
-- Rescan after a brief delay to let Outfitter finish its update
|
||||||
|
addon:Debug("EquipmentSets: Outfitter event '%s', rescanning...", eventName)
|
||||||
|
ScanOutfitter()
|
||||||
|
RebuildItemIndex()
|
||||||
|
-- Sync categories
|
||||||
|
if addon.Modules.CategoryManager then
|
||||||
|
addon.Modules.CategoryManager:SyncEquipmentSetCategories()
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
if not success then
|
||||||
|
addon:Debug("EquipmentSets: Failed to hook Outfitter event '%s': %s", eventName, tostring(err))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- ItemRack Integration
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
local function ScanItemRack()
|
||||||
|
-- Check if ItemRack is loaded
|
||||||
|
if not ItemRackUser or not ItemRackUser.Sets then return false end
|
||||||
|
|
||||||
|
addon:Debug("EquipmentSets: Scanning ItemRack sets...")
|
||||||
|
|
||||||
|
local scannedSets = 0
|
||||||
|
for setName, setInfo in pairs(ItemRackUser.Sets) do
|
||||||
|
-- Skip internal sets (start with special chars)
|
||||||
|
if not string.find(setName, "^~") then
|
||||||
|
local itemIDs = {}
|
||||||
|
if setInfo.equip then
|
||||||
|
for slot, itemString in pairs(setInfo.equip) do
|
||||||
|
-- ItemRack format: "itemID:enchant:suffix:unique"
|
||||||
|
if type(itemString) == "string" then
|
||||||
|
local _, _, idStr = string.find(itemString, "^(%d+)")
|
||||||
|
local itemID = tonumber(idStr)
|
||||||
|
if itemID and itemID > 0 then
|
||||||
|
itemIDs[itemID] = true
|
||||||
|
end
|
||||||
|
elseif type(itemString) == "number" then
|
||||||
|
if itemString > 0 then
|
||||||
|
itemIDs[itemString] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
setData[setName] = { itemIDs = itemIDs, source = "ItemRack" }
|
||||||
|
scannedSets = scannedSets + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
addon:Debug("EquipmentSets: Scanned %d ItemRack sets", scannedSets)
|
||||||
|
return scannedSets > 0
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Full Scan (all sources)
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
local function FullScan()
|
||||||
|
setData = {}
|
||||||
|
|
||||||
|
local hasOutfitter = ScanOutfitter()
|
||||||
|
local hasItemRack = ScanItemRack()
|
||||||
|
|
||||||
|
RebuildItemIndex()
|
||||||
|
|
||||||
|
-- Sync equipment set categories
|
||||||
|
if addon.Modules.CategoryManager then
|
||||||
|
addon.Modules.CategoryManager:SyncEquipmentSetCategories()
|
||||||
|
end
|
||||||
|
|
||||||
|
if hasOutfitter or hasItemRack then
|
||||||
|
addon:Debug("EquipmentSets: Full scan complete, %d total sets", table.getn(EquipmentSets:GetAllSetNames()))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-------------------------------------------
|
||||||
|
-- Initialization
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
function EquipmentSets:Initialize()
|
||||||
|
if initialized then return end
|
||||||
|
initialized = true
|
||||||
|
|
||||||
|
-- Register for ADDON_LOADED to catch late-loading addons
|
||||||
|
addon.Modules.Events:Register("ADDON_LOADED", function(event, addonName)
|
||||||
|
if addonName == "Outfitter" then
|
||||||
|
-- Outfitter needs its INIT event before scanning
|
||||||
|
outfitterReady = true
|
||||||
|
addon:Debug("EquipmentSets: Outfitter loaded, waiting for INIT...")
|
||||||
|
elseif addonName == "ItemRack" then
|
||||||
|
itemRackReady = true
|
||||||
|
addon:Debug("EquipmentSets: ItemRack loaded, scanning...")
|
||||||
|
FullScan()
|
||||||
|
end
|
||||||
|
end, "EquipmentSets")
|
||||||
|
|
||||||
|
-- Register for PLAYER_ENTERING_WORLD to catch already-loaded addons
|
||||||
|
addon.Modules.Events:Register("PLAYER_ENTERING_WORLD", function()
|
||||||
|
-- Check if Outfitter is already loaded
|
||||||
|
if Outfitter_GetCategoryOrder or gOutfitter_Initialized then
|
||||||
|
outfitterReady = true
|
||||||
|
HookOutfitterEvents()
|
||||||
|
FullScan()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Check if ItemRack is already loaded
|
||||||
|
if ItemRackUser and ItemRackUser.Sets then
|
||||||
|
itemRackReady = true
|
||||||
|
FullScan()
|
||||||
|
end
|
||||||
|
end, "EquipmentSets")
|
||||||
|
|
||||||
|
-- Hook Outfitter's INIT event if available (fires after Outfitter finishes setup)
|
||||||
|
-- This uses a frame to check periodically since OUTFITTER_INIT is a custom event
|
||||||
|
local initCheckFrame = CreateFrame("Frame")
|
||||||
|
initCheckFrame.elapsed = 0
|
||||||
|
initCheckFrame.checks = 0
|
||||||
|
initCheckFrame:SetScript("OnUpdate", function()
|
||||||
|
this.elapsed = this.elapsed + arg1
|
||||||
|
if this.elapsed < 1 then return end
|
||||||
|
this.elapsed = 0
|
||||||
|
this.checks = this.checks + 1
|
||||||
|
|
||||||
|
-- Check if Outfitter became available
|
||||||
|
if not outfitterReady and (gOutfitter_Initialized or Outfitter_GetCategoryOrder) then
|
||||||
|
outfitterReady = true
|
||||||
|
HookOutfitterEvents()
|
||||||
|
FullScan()
|
||||||
|
this:Hide()
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Stop checking after 30 seconds
|
||||||
|
if this.checks > 30 then
|
||||||
|
this:Hide()
|
||||||
|
-- Do a final scan anyway in case addons loaded without events
|
||||||
|
if Outfitter_GetCategoryOrder or (ItemRackUser and ItemRackUser.Sets) then
|
||||||
|
FullScan()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
initCheckFrame:Show()
|
||||||
|
|
||||||
|
addon:Debug("EquipmentSets: Module initialized")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Force a rescan of all equipment set sources
|
||||||
|
function EquipmentSets:Rescan()
|
||||||
|
FullScan()
|
||||||
|
end
|
||||||
@@ -24,6 +24,7 @@ Data\BankScanner.lua
|
|||||||
Data\MailboxScanner.lua
|
Data\MailboxScanner.lua
|
||||||
Data\MoneyTracker.lua
|
Data\MoneyTracker.lua
|
||||||
Data\EquipmentScanner.lua
|
Data\EquipmentScanner.lua
|
||||||
|
Data\EquipmentSets.lua
|
||||||
|
|
||||||
Sorting\SortEngine.lua
|
Sorting\SortEngine.lua
|
||||||
|
|
||||||
|
|||||||
+167
-110
@@ -996,55 +996,131 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
local categoryItemsProcessed = 0
|
local categoryItemsProcessed = 0
|
||||||
local CATEGORY_ITEMS_PER_BUDGET_CHECK = 8
|
local CATEGORY_ITEMS_PER_BUDGET_CHECK = 8
|
||||||
|
|
||||||
local totalButtonsCreated = 0
|
-- Check merged groups setting
|
||||||
for _, catName in ipairs(categoryList) do
|
local mergedGroups = addon.Modules.DB:GetSetting("mergedGroups") or {}
|
||||||
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)
|
|
||||||
|
|
||||||
local blockCols = numItems
|
-- Build category order index map for sorting within merged groups
|
||||||
if blockCols > perRow then blockCols = perRow end
|
local catOrderIndex = {}
|
||||||
local blockRows = math.ceil(numItems / perRow)
|
for idx, catId in ipairs(categoryList) do
|
||||||
local blockWidth = blockCols * (buttonSize + spacing)
|
catOrderIndex[catId] = idx
|
||||||
local blockHeight = 20 + (blockRows * (buttonSize + spacing)) + 5
|
end
|
||||||
|
|
||||||
-- Check if it fits in current row
|
-- Build merged group display lists if any groups are merged
|
||||||
if currentX > 0 and currentX + blockWidth + 20 > totalWidth + 5 then
|
local mergedDisplayList = {} -- { { name, items, icon, catDef } }
|
||||||
currentX = 0
|
local processedCats = {} -- Track which categories were merged
|
||||||
currentY = currentY + rowMaxHeight
|
|
||||||
rowMaxHeight = 0
|
|
||||||
addon:DebugCategory(" -> wrap to new row, Y=%d", currentY)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Add Header
|
if addon.Modules.CategoryManager then
|
||||||
local header = self:GetSectionHeader(headerIdx)
|
local groupsByName = addon.Modules.CategoryManager:GetCategoriesByGroup()
|
||||||
headerIdx = headerIdx + 1
|
for groupName, catIds in pairs(groupsByName) do
|
||||||
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY)
|
if mergedGroups[groupName] then
|
||||||
header:SetWidth(blockWidth)
|
-- Merge all items from categories in this group into one section
|
||||||
|
local mergedItems = {}
|
||||||
-- Get display name from category definition
|
local mergedName = groupName
|
||||||
local displayName = catName
|
local mergedIcon = nil
|
||||||
if addon.Modules.CategoryManager then
|
for _, catId in ipairs(catIds) do
|
||||||
local catDef = addon.Modules.CategoryManager:GetCategory(catName)
|
local items = categories[catId]
|
||||||
if catDef and catDef.name then
|
if items then
|
||||||
displayName = catDef.name
|
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
|
||||||
end
|
end
|
||||||
header.fullName = displayName
|
end
|
||||||
header.isShortened = false
|
end
|
||||||
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()
|
|
||||||
|
|
||||||
local itemY = currentY + 20
|
-- Track total buttons created across all category blocks
|
||||||
local col = 0
|
local totalButtonsCreated = 0
|
||||||
local row = 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
|
for _, item in ipairs(items) do
|
||||||
local bagID = item.bagID
|
local bagID = item.bagID
|
||||||
local slot = item.slotID
|
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)
|
Guda_ItemButton_SetItem(button, bagID, slot, itemData, false, isOtherChar and charName or nil, matchesFilter, isOtherChar)
|
||||||
button.inUse = true
|
button.inUse = true
|
||||||
table.insert(itemButtons, button)
|
table.insert(itemButtons, button)
|
||||||
-- Populate slot lookup for O(1) access
|
|
||||||
if not slotToButton[bagID] then slotToButton[bagID] = {} end
|
if not slotToButton[bagID] then slotToButton[bagID] = {} end
|
||||||
slotToButton[bagID][slot] = button
|
slotToButton[bagID][slot] = button
|
||||||
|
|
||||||
@@ -1074,7 +1149,6 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
row = row + 1
|
row = row + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Frame budget check
|
|
||||||
categoryItemsProcessed = categoryItemsProcessed + 1
|
categoryItemsProcessed = categoryItemsProcessed + 1
|
||||||
if categoryItemsProcessed >= CATEGORY_ITEMS_PER_BUDGET_CHECK then
|
if categoryItemsProcessed >= CATEGORY_ITEMS_PER_BUDGET_CHECK then
|
||||||
categoryItemsProcessed = 0
|
categoryItemsProcessed = 0
|
||||||
@@ -1083,25 +1157,50 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
totalButtonsCreated = totalButtonsCreated + numItems
|
totalButtonsCreated = totalButtonsCreated + effectiveItems
|
||||||
if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end
|
if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end
|
||||||
currentX = currentX + blockWidth + 20
|
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
|
||||||
end
|
end
|
||||||
addon:DebugCategory("DisplayItemsByCategory: totalButtonsCreated=%d from categories", totalButtonsCreated)
|
addon:DebugCategory("DisplayItemsByCategory: totalButtonsCreated=%d from categories", totalButtonsCreated)
|
||||||
|
|
||||||
-- Update Y for bottom sections
|
-- Update Y for bottom sections
|
||||||
local y = currentY + rowMaxHeight
|
local y = currentY + rowMaxHeight
|
||||||
|
|
||||||
-- Special sections at bottom (Hearthstone, Mount, Tools, Empty)
|
-- Special sections at bottom (only Mounts now)
|
||||||
local bottomSections = {
|
local bottomSections = {
|
||||||
{ name = "Home", items = specialItems.Hearthstone },
|
|
||||||
{ name = "Mounts", items = specialItems.Mount },
|
{ name = "Mounts", items = specialItems.Mount },
|
||||||
{ name = "Tools", items = specialItems.Tools },
|
|
||||||
{ name = "Empty", items = {} }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local x = startX
|
local x = startX
|
||||||
y = startY - y
|
y = startY - y
|
||||||
|
|
||||||
@@ -1112,10 +1211,6 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- Also check if Empty section should show
|
|
||||||
if totalFreeSlots > 0 then
|
|
||||||
hasAnyBottom = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if hasAnyBottom then
|
if hasAnyBottom then
|
||||||
y = y - 10
|
y = y - 10
|
||||||
@@ -1125,22 +1220,7 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
for _, sec in ipairs(bottomSections) do
|
for _, sec in ipairs(bottomSections) do
|
||||||
local items = sec.items
|
local items = sec.items
|
||||||
local numItems = table.getn(items)
|
local numItems = table.getn(items)
|
||||||
if sec.name == "Empty" then
|
|
||||||
numItems = (totalFreeSlots > 0) and 1 or 0
|
|
||||||
end
|
|
||||||
if numItems > 0 then
|
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
|
local blockCols = numItems
|
||||||
if blockCols > perRow then blockCols = perRow end
|
if blockCols > perRow then blockCols = perRow end
|
||||||
local blockRows = math.ceil(numItems / perRow)
|
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:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + currentBottomX, y)
|
||||||
header:SetWidth(blockWidth)
|
header:SetWidth(blockWidth)
|
||||||
header.text:SetText(sec.name)
|
header.text:SetText(sec.name)
|
||||||
|
if header.countText then header.countText:SetText("(" .. numItems .. ")"); header.countText:Show() end
|
||||||
header:Show()
|
header:Show()
|
||||||
|
|
||||||
local itemY = y - 20
|
local itemY = y - 20
|
||||||
local sCol = 0
|
local sCol = 0
|
||||||
local sRow = 0
|
local sRow = 0
|
||||||
|
|
||||||
if sec.name == "Empty" then
|
for _, item in ipairs(items) do
|
||||||
local bagID = firstFreeBag or 0
|
local bagParent = self:GetBagParent(item.bagID)
|
||||||
local slotID = firstFreeSlot or 1
|
|
||||||
local bagParent = self:GetBagParent(bagID)
|
|
||||||
local button = Guda_GetItemButton(bagParent)
|
local button = Guda_GetItemButton(bagParent)
|
||||||
button:SetParent(bagParent)
|
button:SetParent(bagParent)
|
||||||
button:SetWidth(buttonSize)
|
button:SetWidth(buttonSize)
|
||||||
button:SetHeight(buttonSize)
|
button:SetHeight(buttonSize)
|
||||||
button:ClearAllPoints()
|
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()
|
button:Show()
|
||||||
|
Guda_ItemButton_SetItem(button, item.bagID, item.slotID, item.itemData, false, isOtherChar and charName or nil, self:PassesSearchFilter(item.itemData), isOtherChar)
|
||||||
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
|
button.inUse = true
|
||||||
table.insert(itemButtons, button)
|
table.insert(itemButtons, button)
|
||||||
else
|
if not slotToButton[item.bagID] then slotToButton[item.bagID] = {} end
|
||||||
for _, item in ipairs(items) do
|
slotToButton[item.bagID][item.slotID] = button
|
||||||
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
|
|
||||||
|
|
||||||
sCol = sCol + 1
|
sCol = sCol + 1
|
||||||
if sCol >= blockCols then
|
if sCol >= blockCols then
|
||||||
sCol = 0
|
sCol = 0
|
||||||
sRow = sRow + 1
|
sRow = sRow + 1
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if blockHeight > sectionMaxHeight then sectionMaxHeight = blockHeight end
|
if blockHeight > sectionMaxHeight then sectionMaxHeight = blockHeight end
|
||||||
currentBottomX = currentBottomX + blockWidth + 20
|
currentBottomX = currentBottomX + blockWidth + 20
|
||||||
|
|
||||||
if currentBottomX >= totalWidth then
|
if currentBottomX >= totalWidth then
|
||||||
currentBottomX = 0
|
currentBottomX = 0
|
||||||
y = y - sectionMaxHeight - 5
|
y = y - sectionMaxHeight - 5
|
||||||
@@ -1220,7 +1277,7 @@ function BagFrame:DisplayItemsByCategory(bagData, isOtherChar, charName)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if currentBottomX > 0 then
|
if currentBottomX > 0 then
|
||||||
y = y - sectionMaxHeight
|
y = y - sectionMaxHeight
|
||||||
end
|
end
|
||||||
|
|||||||
+38
-63
@@ -779,6 +779,13 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
|||||||
local CATEGORY_ITEMS_PER_BUDGET_CHECK = 8
|
local CATEGORY_ITEMS_PER_BUDGET_CHECK = 8
|
||||||
|
|
||||||
for _, catName in ipairs(categoryList) do
|
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 items = categories[catName]
|
||||||
local numItems = items and table.getn(items) or 0
|
local numItems = items and table.getn(items) or 0
|
||||||
if numItems > 0 then
|
if numItems > 0 then
|
||||||
@@ -803,9 +810,19 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
|||||||
headerIdx = headerIdx + 1
|
headerIdx = headerIdx + 1
|
||||||
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY)
|
header:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", startX + currentX, startY - currentY)
|
||||||
header:SetWidth(blockWidth)
|
header:SetWidth(blockWidth)
|
||||||
|
|
||||||
local displayName = catName
|
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
|
header.isShortened = false
|
||||||
if string.len(displayName) > 10 and numItems < 2 then
|
if string.len(displayName) > 10 and numItems < 2 then
|
||||||
displayName = string.sub(displayName, 1, 7) .. "..."
|
displayName = string.sub(displayName, 1, 7) .. "..."
|
||||||
@@ -865,22 +882,20 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
|||||||
if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end
|
if blockHeight > rowMaxHeight then rowMaxHeight = blockHeight end
|
||||||
currentX = currentX + blockWidth + 20
|
currentX = currentX + blockWidth + 20
|
||||||
end
|
end
|
||||||
|
end -- isEmptyCategory else
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Update Y for bottom sections
|
-- Update Y for bottom sections
|
||||||
local y = currentY + rowMaxHeight
|
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 = {
|
local bottomSections = {
|
||||||
{ name = "Home", items = specialItems.Hearthstone },
|
|
||||||
{ name = "Mounts", items = specialItems.Mount },
|
{ name = "Mounts", items = specialItems.Mount },
|
||||||
{ name = "Tools", items = specialItems.Tools },
|
|
||||||
{ name = "Empty", items = {} }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local x = startX
|
local x = startX
|
||||||
y = startY - y
|
y = startY - y
|
||||||
|
|
||||||
local hasAnyBottom = false
|
local hasAnyBottom = false
|
||||||
for _, sec in ipairs(bottomSections) do
|
for _, sec in ipairs(bottomSections) do
|
||||||
if table.getn(sec.items) > 0 then
|
if table.getn(sec.items) > 0 then
|
||||||
@@ -888,10 +903,6 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
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
|
if hasAnyBottom then
|
||||||
y = y - 10
|
y = y - 10
|
||||||
@@ -901,22 +912,7 @@ function BankFrame:DisplayItemsByCategory(bankData, isOtherChar, charName)
|
|||||||
for _, sec in ipairs(bottomSections) do
|
for _, sec in ipairs(bottomSections) do
|
||||||
local items = sec.items
|
local items = sec.items
|
||||||
local numItems = table.getn(items)
|
local numItems = table.getn(items)
|
||||||
if sec.name == "Empty" then
|
|
||||||
numItems = (totalFreeSlots > 0) and 1 or 0
|
|
||||||
end
|
|
||||||
if numItems > 0 then
|
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
|
local blockCols = numItems
|
||||||
if blockCols > perRow then blockCols = perRow end
|
if blockCols > perRow then blockCols = perRow end
|
||||||
local blockRows = math.ceil(numItems / perRow)
|
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:SetPoint("TOPLEFT", itemContainer, "TOPLEFT", x + currentBottomX, y)
|
||||||
header:SetWidth(blockWidth)
|
header:SetWidth(blockWidth)
|
||||||
header.text:SetText(sec.name)
|
header.text:SetText(sec.name)
|
||||||
|
if header.countText then header.countText:SetText("(" .. numItems .. ")"); header.countText:Show() end
|
||||||
header:Show()
|
header:Show()
|
||||||
|
|
||||||
local itemY = y - 20
|
local itemY = y - 20
|
||||||
local sCol = 0
|
local sCol = 0
|
||||||
local sRow = 0
|
local sRow = 0
|
||||||
|
|
||||||
if sec.name == "Empty" then
|
for _, item in ipairs(items) do
|
||||||
local bagID = firstFreeBag or -1
|
local bagParent = self:GetBagParent(item.bagID)
|
||||||
local slotID = firstFreeSlot or 1
|
|
||||||
local bagParent = self:GetBagParent(bagID)
|
|
||||||
local button = Guda_GetItemButton(bagParent)
|
local button = Guda_GetItemButton(bagParent)
|
||||||
button:SetParent(bagParent)
|
button:SetParent(bagParent)
|
||||||
button:SetWidth(buttonSize)
|
button:SetWidth(buttonSize)
|
||||||
button:SetHeight(buttonSize)
|
button:SetHeight(buttonSize)
|
||||||
button:ClearAllPoints()
|
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()
|
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)
|
||||||
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
|
|
||||||
button.inUse = true
|
button.inUse = true
|
||||||
else
|
-- Populate slot lookup for O(1) access
|
||||||
for _, item in ipairs(items) do
|
if not bankSlotToButton[item.bagID] then bankSlotToButton[item.bagID] = {} end
|
||||||
local bagParent = self:GetBagParent(item.bagID)
|
bankSlotToButton[item.bagID][item.slotID] = button
|
||||||
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
|
|
||||||
|
|
||||||
sCol = sCol + 1
|
sCol = sCol + 1
|
||||||
if sCol >= blockCols then
|
if sCol >= blockCols then
|
||||||
sCol = 0
|
sCol = 0
|
||||||
sRow = sRow + 1
|
sRow = sRow + 1
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if blockHeight > sectionMaxHeight then sectionMaxHeight = blockHeight end
|
if blockHeight > sectionMaxHeight then sectionMaxHeight = blockHeight end
|
||||||
currentBottomX = currentBottomX + blockWidth + 20
|
currentBottomX = currentBottomX + blockWidth + 20
|
||||||
|
|
||||||
if currentBottomX >= totalWidth then
|
if currentBottomX >= totalWidth then
|
||||||
currentBottomX = 0
|
currentBottomX = 0
|
||||||
y = y - sectionMaxHeight - 5
|
y = y - sectionMaxHeight - 5
|
||||||
|
|||||||
+21
-36
@@ -21,7 +21,6 @@ end
|
|||||||
-- Returns nothing, modifies tables in place
|
-- Returns nothing, modifies tables in place
|
||||||
function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems, isOtherChar)
|
function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems, isOtherChar)
|
||||||
local itemName = itemData.name or ""
|
local itemName = itemData.name or ""
|
||||||
local itemType = itemData.type or ""
|
|
||||||
local cat = "Miscellaneous"
|
local cat = "Miscellaneous"
|
||||||
|
|
||||||
-- Detect consumable restore/eat/drink tag for current character only
|
-- Detect consumable restore/eat/drink tag for current character only
|
||||||
@@ -32,38 +31,11 @@ function Guda_CategorizeItem(itemData, bagID, slotID, categories, specialItems,
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Priority 1: Special items (Hearthstone, Mounts, Tools)
|
-- Special items: only Mounts are handled separately now
|
||||||
-- These are handled separately and go into specialItems table, not categories
|
-- Home and Tools are real categories handled by CategoryManager rules
|
||||||
if string.find(itemName, "Hearthstone") then
|
if addon.Modules.SortEngine and addon.Modules.SortEngine.IsMount and addon.Modules.SortEngine.IsMount(itemData.texture) 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
|
|
||||||
table.insert(specialItems.Mount, {bagID = bagID, slotID = slotID, itemData = itemData})
|
table.insert(specialItems.Mount, {bagID = bagID, slotID = slotID, itemData = itemData})
|
||||||
return
|
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
|
end
|
||||||
|
|
||||||
-- Use CategoryManager rule engine if available, otherwise fall back to legacy logic
|
-- Use CategoryManager rule engine if available, otherwise fall back to legacy logic
|
||||||
@@ -218,22 +190,20 @@ function Guda_InitCategories()
|
|||||||
end
|
end
|
||||||
|
|
||||||
-- Reuse or create specialItems table
|
-- Reuse or create specialItems table
|
||||||
|
-- Only Mount remains as a special item; Home and Tools are now real categories
|
||||||
if not specialItemsCache then
|
if not specialItemsCache then
|
||||||
specialItemsCache = {
|
specialItemsCache = {
|
||||||
Hearthstone = {},
|
|
||||||
Mount = {},
|
Mount = {},
|
||||||
Tools = {}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
WipeTable(specialItemsCache.Hearthstone)
|
|
||||||
WipeTable(specialItemsCache.Mount)
|
WipeTable(specialItemsCache.Mount)
|
||||||
WipeTable(specialItemsCache.Tools)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return categoriesCache, specialItemsCache
|
return categoriesCache, specialItemsCache
|
||||||
end
|
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)
|
function Guda_SortCategoryItems(items)
|
||||||
if not items then return end
|
if not items then return end
|
||||||
table.sort(items, function(a, b)
|
table.sort(items, function(a, b)
|
||||||
@@ -243,6 +213,13 @@ function Guda_SortCategoryItems(items)
|
|||||||
if not a.itemData then return false end
|
if not a.itemData then return false end
|
||||||
if not b.itemData then return true 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
|
-- Rank Trade Goods: meat (name ends with 'meat') = 2, egg (contains 'egg') = 1, others = 0
|
||||||
local function tgRank(d)
|
local function tgRank(d)
|
||||||
if not d or not d.name then return 0 end
|
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 = CreateFrame("Frame", name, container)
|
||||||
header:SetHeight(20)
|
header:SetHeight(20)
|
||||||
header:EnableMouse(true)
|
header:EnableMouse(true)
|
||||||
|
|
||||||
|
-- Category name text
|
||||||
local text = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
local text = header:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
text:SetPoint("LEFT", header, "LEFT", 0, 0)
|
text:SetPoint("LEFT", header, "LEFT", 0, 0)
|
||||||
header.text = text
|
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()
|
header:SetScript("OnEnter", function()
|
||||||
if this.fullName and this.isShortened then
|
if this.fullName and this.isShortened then
|
||||||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||||||
|
|||||||
+64
-39
@@ -639,6 +639,8 @@ local function ResetButtonVisualState(self)
|
|||||||
HideInnerShadow(self.innerShadow)
|
HideInnerShadow(self.innerShadow)
|
||||||
if self.unusableOverlay then self.unusableOverlay:Hide() end
|
if self.unusableOverlay then self.unusableOverlay:Hide() end
|
||||||
HideJunkIcon(self)
|
HideJunkIcon(self)
|
||||||
|
if self.categoryMarkIcon then self.categoryMarkIcon:Hide() end
|
||||||
|
if self.categoryMarkShadow then self.categoryMarkShadow:Hide() end
|
||||||
|
|
||||||
-- Clear cooldown overlay
|
-- Clear cooldown overlay
|
||||||
local cd = getglobal(self:GetName().."Cooldown") or self.cooldown
|
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
|
if not emptySlotBg then return end
|
||||||
|
|
||||||
emptySlotBg:ClearAllPoints()
|
emptySlotBg:ClearAllPoints()
|
||||||
-- Use smaller padding for small icons
|
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
|
||||||
local bgPadding = iconSize < 44 and 1 or 2
|
emptySlotBg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0)
|
||||||
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", -bgPadding, bgPadding)
|
emptySlotBg:SetTexCoord(0.17, 0.83, 0.17, 0.83)
|
||||||
emptySlotBg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", bgPadding, -bgPadding)
|
|
||||||
emptySlotBg:SetTexCoord(0.1, 0.9, 0.1, 0.9)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Resize texture elements to match button size
|
-- 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
|
if not iconTexture or not self.hasItem then return end
|
||||||
|
|
||||||
-- Calculate icon inset based on size
|
-- Icon fills the full button slot
|
||||||
local iconInset = iconSize < 44 and 10 or 15
|
local iconDisplaySize = iconSize
|
||||||
local iconDisplaySize = iconSize - iconInset
|
|
||||||
|
|
||||||
iconTexture:ClearAllPoints()
|
iconTexture:ClearAllPoints()
|
||||||
iconTexture:SetPoint("CENTER", self, "CENTER", -0.5, 0.5)
|
iconTexture:SetPoint("CENTER", self, "CENTER", 0, 0)
|
||||||
iconTexture:SetWidth(iconDisplaySize)
|
iconTexture:SetWidth(iconDisplaySize)
|
||||||
iconTexture:SetHeight(iconDisplaySize)
|
iconTexture:SetHeight(iconDisplaySize)
|
||||||
iconTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
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
|
-- Position quality border around the icon
|
||||||
if self.qualityBorder then
|
if self.qualityBorder then
|
||||||
self.qualityBorder:ClearAllPoints()
|
self.qualityBorder:ClearAllPoints()
|
||||||
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -1, 1)
|
||||||
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 1, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Position quest border around the icon
|
-- Position quest border around the icon
|
||||||
if self.questBorder then
|
if self.questBorder then
|
||||||
self.questBorder:ClearAllPoints()
|
self.questBorder:ClearAllPoints()
|
||||||
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -1, 1)
|
||||||
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 1, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Position quest icon in top-right corner
|
-- Position quest icon in top-right corner
|
||||||
@@ -1056,15 +1055,12 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
|||||||
end
|
end
|
||||||
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
|
if emptySlotBg then
|
||||||
emptySlotBg:ClearAllPoints()
|
emptySlotBg:ClearAllPoints()
|
||||||
-- Use smaller padding for small icons
|
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", 0, 0)
|
||||||
local bgPadding = iconSize < 44 and 1 or 2
|
emptySlotBg:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", 0, 0)
|
||||||
emptySlotBg:SetPoint("TOPLEFT", self, "TOPLEFT", -bgPadding, bgPadding)
|
emptySlotBg:SetTexCoord(0.17, 0.83, 0.17, 0.83)
|
||||||
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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Also resize the underlying slot textures so the border/background scale with the button.
|
-- 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
|
||||||
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 isJunk = false
|
||||||
|
local categoryMarkTexture = nil
|
||||||
if itemData and addon.Modules.CategoryManager then
|
if itemData and addon.Modules.CategoryManager then
|
||||||
local category = addon.Modules.CategoryManager:CategorizeItem(itemData, bagID, slotID, self.otherChar)
|
local category = addon.Modules.CategoryManager:CategorizeItem(itemData, bagID, slotID, self.otherChar)
|
||||||
isJunk = (category == "Junk")
|
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
|
end
|
||||||
|
|
||||||
-- Search filtering and junk opacity
|
-- Search filtering and junk opacity
|
||||||
@@ -1410,37 +1444,28 @@ function Guda_ItemButton_SetItem(self, bagID, slotID, itemData, isBank, otherCha
|
|||||||
|
|
||||||
if iconTexture then
|
if iconTexture then
|
||||||
if self.hasItem then
|
if self.hasItem then
|
||||||
-- Scale icon proportionally based on button size
|
-- Icon fills the full button slot
|
||||||
-- For icons < 44: use smaller inset (4px) for better fit
|
local iconDisplaySize = iconSize
|
||||||
-- 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
|
|
||||||
iconTexture:ClearAllPoints()
|
iconTexture:ClearAllPoints()
|
||||||
iconTexture:SetPoint("CENTER", self, "CENTER", -0.5, 0.5)
|
iconTexture:SetPoint("CENTER", self, "CENTER", 0, 0)
|
||||||
iconTexture:SetWidth(iconDisplaySize)
|
iconTexture:SetWidth(iconDisplaySize)
|
||||||
iconTexture:SetHeight(iconDisplaySize)
|
iconTexture:SetHeight(iconDisplaySize)
|
||||||
-- Crop icon edges slightly
|
-- Crop icon edges slightly
|
||||||
iconTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
iconTexture:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||||||
iconTexture:Show()
|
iconTexture:Show()
|
||||||
|
|
||||||
-- Position quality border around the icon (not the slot)
|
-- Position quality border around the icon
|
||||||
if self.qualityBorder then
|
if self.qualityBorder then
|
||||||
self.qualityBorder:ClearAllPoints()
|
self.qualityBorder:ClearAllPoints()
|
||||||
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
self.qualityBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -1, 1)
|
||||||
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
self.qualityBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 1, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Position quest border around the icon (same as quality border)
|
-- Position quest border around the icon
|
||||||
if self.questBorder then
|
if self.questBorder then
|
||||||
self.questBorder:ClearAllPoints()
|
self.questBorder:ClearAllPoints()
|
||||||
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -5, 5)
|
self.questBorder:SetPoint("TOPLEFT", iconTexture, "TOPLEFT", -1, 1)
|
||||||
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 5, -5)
|
self.questBorder:SetPoint("BOTTOMRIGHT", iconTexture, "BOTTOMRIGHT", 1, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Position quest icon in top-right corner
|
-- Position quest icon in top-right corner
|
||||||
|
|||||||
+222
-43
@@ -92,9 +92,9 @@ function Guda_SettingsPopup_OnShow(self)
|
|||||||
-- Load current settings
|
-- Load current settings
|
||||||
local bagColumns = Guda.Modules.DB:GetSetting("bagColumns") or 10
|
local bagColumns = Guda.Modules.DB:GetSetting("bagColumns") or 10
|
||||||
local bankColumns = Guda.Modules.DB:GetSetting("bankColumns") 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 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")
|
local lockBags = Guda.Modules.DB:GetSetting("lockBags")
|
||||||
if lockBags == nil then
|
if lockBags == nil then
|
||||||
lockBags = false
|
lockBags = false
|
||||||
@@ -438,7 +438,7 @@ function Guda_SettingsPopup_IconSizeSlider_OnLoad(self)
|
|||||||
self:SetMinMaxValues(22, 64)
|
self:SetMinMaxValues(22, 64)
|
||||||
self:SetValueStep(1)
|
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)
|
self:SetValue(currentValue)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -503,7 +503,7 @@ end
|
|||||||
|
|
||||||
-- Icon Spacing Slider OnLoad
|
-- Icon Spacing Slider OnLoad
|
||||||
function Guda_SettingsPopup_IconSpacingSlider_OnLoad(self)
|
function Guda_SettingsPopup_IconSpacingSlider_OnLoad(self)
|
||||||
getglobal(self:GetName().."Low"):SetText("-10px")
|
getglobal(self:GetName().."Low"):SetText("0px")
|
||||||
getglobal(self:GetName().."High"):SetText("20px")
|
getglobal(self:GetName().."High"):SetText("20px")
|
||||||
|
|
||||||
local text = getglobal(self:GetName().."Text")
|
local text = getglobal(self:GetName().."Text")
|
||||||
@@ -515,10 +515,10 @@ function Guda_SettingsPopup_IconSpacingSlider_OnLoad(self)
|
|||||||
text:SetFont(font, 12, flags)
|
text:SetFont(font, 12, flags)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:SetMinMaxValues(-10, 20)
|
self:SetMinMaxValues(0, 20)
|
||||||
self:SetValueStep(1)
|
self:SetValueStep(1)
|
||||||
|
|
||||||
local currentValue = Guda.Modules.DB:GetSetting("iconSpacing") or 0
|
local currentValue = Guda.Modules.DB:GetSetting("iconSpacing") or 3
|
||||||
self:SetValue(currentValue)
|
self:SetValue(currentValue)
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1409,6 +1409,30 @@ local function GetCategoryRowFrame(index)
|
|||||||
return row
|
return row
|
||||||
end
|
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
|
-- Update the category list display
|
||||||
function Guda_SettingsPopup_CategoriesTab_Update()
|
function Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
if not Guda.Modules.CategoryManager then return end
|
if not Guda.Modules.CategoryManager then return end
|
||||||
@@ -1416,11 +1440,10 @@ function Guda_SettingsPopup_CategoriesTab_Update()
|
|||||||
local scrollFrame = getglobal("Guda_SettingsPopup_CategoriesScrollFrame")
|
local scrollFrame = getglobal("Guda_SettingsPopup_CategoriesScrollFrame")
|
||||||
if not scrollFrame then return end
|
if not scrollFrame then return end
|
||||||
|
|
||||||
local categoryOrder = Guda.Modules.CategoryManager:GetCategoryOrder()
|
local displayList, totalEntries = BuildCategoryDisplayList()
|
||||||
local totalCategories = table.getn(categoryOrder)
|
|
||||||
|
|
||||||
-- Update scroll frame
|
-- 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)
|
local offset = FauxScrollFrame_GetOffset(scrollFrame)
|
||||||
|
|
||||||
@@ -1428,13 +1451,28 @@ function Guda_SettingsPopup_CategoriesTab_Update()
|
|||||||
local row = GetCategoryRowFrame(i)
|
local row = GetCategoryRowFrame(i)
|
||||||
if row then
|
if row then
|
||||||
local dataIndex = i + offset
|
local dataIndex = i + offset
|
||||||
if dataIndex <= totalCategories then
|
if dataIndex <= totalEntries then
|
||||||
local categoryId = categoryOrder[dataIndex]
|
local entry = displayList[dataIndex]
|
||||||
local categoryDef = Guda.Modules.CategoryManager:GetCategory(categoryId)
|
|
||||||
|
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.categoryId = categoryId
|
||||||
row.nameText:SetText(categoryDef.name or categoryId)
|
row.nameText:SetText(categoryDef.name or categoryId)
|
||||||
|
row.checkbox:Show()
|
||||||
row.checkbox:SetChecked(categoryDef.enabled and 1 or 0)
|
row.checkbox:SetChecked(categoryDef.enabled and 1 or 0)
|
||||||
|
|
||||||
-- Show/hide delete button based on whether it's built-in
|
-- Show/hide delete button based on whether it's built-in
|
||||||
@@ -1458,24 +1496,17 @@ function Guda_SettingsPopup_CategoriesTab_Update()
|
|||||||
row.upBtn:Show()
|
row.upBtn:Show()
|
||||||
row.downBtn:Show()
|
row.downBtn:Show()
|
||||||
|
|
||||||
-- Enable/disable move buttons based on position
|
-- Enable/disable move buttons based on group-aware boundaries
|
||||||
if dataIndex == 1 then
|
if Guda.Modules.CategoryManager:CanMoveUp(categoryId) then
|
||||||
row.upBtn:Disable()
|
row.upBtn:Enable()
|
||||||
else
|
else
|
||||||
-- Check if category above has hideControls (can't move above it)
|
row.upBtn:Disable()
|
||||||
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
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if dataIndex == totalCategories then
|
if Guda.Modules.CategoryManager:CanMoveDown(categoryId) then
|
||||||
row.downBtn:Disable()
|
|
||||||
else
|
|
||||||
row.downBtn:Enable()
|
row.downBtn:Enable()
|
||||||
|
else
|
||||||
|
row.downBtn:Disable()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -1512,30 +1543,21 @@ end
|
|||||||
function Guda_SettingsPopup_AddCategory_OnClick()
|
function Guda_SettingsPopup_AddCategory_OnClick()
|
||||||
if not Guda.Modules.CategoryManager then return end
|
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
|
-- Create new category definition
|
||||||
local newDef = {
|
local newDef = {
|
||||||
name = "Custom " .. counter,
|
name = "Custom Category",
|
||||||
icon = "Interface\\Icons\\INV_Misc_QuestionMark",
|
icon = "Interface\\Icons\\INV_Misc_QuestionMark",
|
||||||
rules = {},
|
rules = {},
|
||||||
matchMode = "any",
|
matchMode = "any",
|
||||||
priority = 80,
|
priority = 80,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
isBuiltIn = false,
|
isBuiltIn = false,
|
||||||
|
group = Guda.Modules.CategoryManager:GetGroupMain(),
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Add to database
|
-- Add to database with auto-generated ID
|
||||||
if Guda.Modules.CategoryManager:AddCategory(newId, newDef) then
|
local success, newId = Guda.Modules.CategoryManager:AddCategory(nil, newDef)
|
||||||
|
if success and newId then
|
||||||
-- Update display
|
-- Update display
|
||||||
Guda_SettingsPopup_CategoriesTab_Update()
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
-- Open editor for the new category
|
-- Open editor for the new category
|
||||||
@@ -1577,8 +1599,19 @@ end
|
|||||||
|
|
||||||
local editorCategoryId = nil
|
local editorCategoryId = nil
|
||||||
local editorMatchMode = "any"
|
local editorMatchMode = "any"
|
||||||
|
local editorGroup = "Main"
|
||||||
|
local editorMark = nil -- current category mark texture path or nil
|
||||||
local editorRules = {}
|
local editorRules = {}
|
||||||
local editorRuleFrames = {}
|
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 RULE_ROW_HEIGHT = 28
|
||||||
local MAX_RULES = 22
|
local MAX_RULES = 22
|
||||||
|
|
||||||
@@ -1625,6 +1658,91 @@ function Guda_CategoryEditor_OnLoad(self)
|
|||||||
local cancelBtn = getglobal("Guda_CategoryEditor_CancelButton")
|
local cancelBtn = getglobal("Guda_CategoryEditor_CancelButton")
|
||||||
if cancelBtn then cancelBtn:SetText("Cancel") end
|
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
|
-- Create radio button labels
|
||||||
local anyRadio = getglobal("Guda_CategoryEditor_MatchAny")
|
local anyRadio = getglobal("Guda_CategoryEditor_MatchAny")
|
||||||
if anyRadio then
|
if anyRadio then
|
||||||
@@ -1641,6 +1759,19 @@ function Guda_CategoryEditor_OnLoad(self)
|
|||||||
end
|
end
|
||||||
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
|
-- OnShow for Category Editor
|
||||||
function Guda_CategoryEditor_OnShow(self)
|
function Guda_CategoryEditor_OnShow(self)
|
||||||
Guda_CategoryEditor_UpdateRulesDisplay()
|
Guda_CategoryEditor_UpdateRulesDisplay()
|
||||||
@@ -1655,6 +1786,8 @@ function Guda_CategoryEditor_Open(categoryId)
|
|||||||
|
|
||||||
editorCategoryId = categoryId
|
editorCategoryId = categoryId
|
||||||
editorMatchMode = categoryDef.matchMode or "any"
|
editorMatchMode = categoryDef.matchMode or "any"
|
||||||
|
editorGroup = categoryDef.group or "Main"
|
||||||
|
editorMark = categoryDef.categoryMark or nil
|
||||||
|
|
||||||
-- Copy rules
|
-- Copy rules
|
||||||
editorRules = {}
|
editorRules = {}
|
||||||
@@ -1690,9 +1823,29 @@ function Guda_CategoryEditor_Open(categoryId)
|
|||||||
end
|
end
|
||||||
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
|
-- Set match mode
|
||||||
Guda_CategoryEditor_SetMatchMode(editorMatchMode)
|
Guda_CategoryEditor_SetMatchMode(editorMatchMode)
|
||||||
|
|
||||||
|
-- Update mark button highlights
|
||||||
|
Guda_CategoryEditor_UpdateMarkButtons()
|
||||||
|
|
||||||
-- Show editor
|
-- Show editor
|
||||||
local editor = getglobal("Guda_CategoryEditor")
|
local editor = getglobal("Guda_CategoryEditor")
|
||||||
if editor then
|
if editor then
|
||||||
@@ -1700,6 +1853,8 @@ function Guda_CategoryEditor_Open(categoryId)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- (Group is now an EditBox — no dropdown needed)
|
||||||
|
|
||||||
-- Set match mode (radio buttons)
|
-- Set match mode (radio buttons)
|
||||||
function Guda_CategoryEditor_SetMatchMode(mode)
|
function Guda_CategoryEditor_SetMatchMode(mode)
|
||||||
editorMatchMode = mode
|
editorMatchMode = mode
|
||||||
@@ -2065,6 +2220,9 @@ function Guda_CategoryEditor_Save()
|
|||||||
-- Set match mode
|
-- Set match mode
|
||||||
categoryDef.matchMode = editorMatchMode
|
categoryDef.matchMode = editorMatchMode
|
||||||
|
|
||||||
|
-- Set category mark
|
||||||
|
categoryDef.categoryMark = editorMark
|
||||||
|
|
||||||
-- Set rules
|
-- Set rules
|
||||||
categoryDef.rules = {}
|
categoryDef.rules = {}
|
||||||
for _, rule in ipairs(editorRules) do
|
for _, rule in ipairs(editorRules) do
|
||||||
@@ -2073,8 +2231,29 @@ function Guda_CategoryEditor_Save()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Save to database
|
-- Read group from EditBox
|
||||||
Guda.Modules.CategoryManager:UpdateCategory(editorCategoryId, categoryDef)
|
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
|
-- Refresh displays
|
||||||
Guda_SettingsPopup_CategoriesTab_Update()
|
Guda_SettingsPopup_CategoriesTab_Update()
|
||||||
|
|||||||
@@ -919,7 +919,7 @@
|
|||||||
<!-- Name EditBox -->
|
<!-- Name EditBox -->
|
||||||
<EditBox name="Guda_CategoryEditor_NameEditBox" autoFocus="false" inherits="InputBoxTemplate">
|
<EditBox name="Guda_CategoryEditor_NameEditBox" autoFocus="false" inherits="InputBoxTemplate">
|
||||||
<Size>
|
<Size>
|
||||||
<AbsDimension x="200" y="20"/>
|
<AbsDimension x="150" y="20"/>
|
||||||
</Size>
|
</Size>
|
||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="TOPLEFT">
|
<Anchor point="TOPLEFT">
|
||||||
@@ -946,7 +946,7 @@
|
|||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="TOPLEFT">
|
<Anchor point="TOPLEFT">
|
||||||
<Offset>
|
<Offset>
|
||||||
<AbsDimension x="100" y="-77"/>
|
<AbsDimension x="100" y="-97"/>
|
||||||
</Offset>
|
</Offset>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Anchors>
|
</Anchors>
|
||||||
@@ -979,12 +979,12 @@
|
|||||||
<!-- Rules ScrollFrame -->
|
<!-- Rules ScrollFrame -->
|
||||||
<ScrollFrame name="Guda_CategoryEditor_RulesScrollFrame" inherits="FauxScrollFrameTemplate">
|
<ScrollFrame name="Guda_CategoryEditor_RulesScrollFrame" inherits="FauxScrollFrameTemplate">
|
||||||
<Size>
|
<Size>
|
||||||
<AbsDimension x="335" y="280"/>
|
<AbsDimension x="335" y="260"/>
|
||||||
</Size>
|
</Size>
|
||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="TOPLEFT">
|
<Anchor point="TOPLEFT">
|
||||||
<Offset>
|
<Offset>
|
||||||
<AbsDimension x="20" y="-130"/>
|
<AbsDimension x="20" y="-150"/>
|
||||||
</Offset>
|
</Offset>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Anchors>
|
</Anchors>
|
||||||
@@ -1013,7 +1013,7 @@
|
|||||||
<Anchors>
|
<Anchors>
|
||||||
<Anchor point="TOPRIGHT">
|
<Anchor point="TOPRIGHT">
|
||||||
<Offset>
|
<Offset>
|
||||||
<AbsDimension x="-20" y="-112"/>
|
<AbsDimension x="-20" y="-132"/>
|
||||||
</Offset>
|
</Offset>
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Anchors>
|
</Anchors>
|
||||||
|
|||||||
Reference in New Issue
Block a user