diff --git a/Core/CategoryManager.lua b/Core/CategoryManager.lua index f43ce18..49bf35f 100644 --- a/Core/CategoryManager.lua +++ b/Core/CategoryManager.lua @@ -31,7 +31,7 @@ local DEFAULT_CATEGORIES = { { type = "isBoE", value = true } }, matchMode = "all", - priority = 60, + priority = 75, enabled = true, isBuiltIn = true, }, @@ -322,6 +322,13 @@ function CategoryManager:MigrateCategories() addon:Debug("CategoryManager: Migrated Drink category to use restoreTag") end end + + -- Migrate BoE priority to be higher than Weapon/Armor (75 > 70) + local boeCat = cats.definitions["BoE"] + if boeCat and boeCat.isBuiltIn and boeCat.priority and boeCat.priority < 75 then + boeCat.priority = 75 + addon:Debug("CategoryManager: Migrated BoE priority to 75") + end end -- Get all categories @@ -492,6 +499,10 @@ function CategoryManager:EvaluateRule(rule, itemData, bagID, slotID, isOtherChar elseif ruleType == "quality" then return itemData.quality == ruleValue + elseif ruleType == "qualityMin" then + -- Minimum quality check (item quality >= ruleValue) + return (itemData.quality or 0) >= (ruleValue or 0) + elseif ruleType == "isBoE" then if isOtherChar then return false end if itemData.class ~= "Weapon" and itemData.class ~= "Armor" then diff --git a/UI/SettingsPopup.lua b/UI/SettingsPopup.lua index 59a1591..1329e93 100644 --- a/UI/SettingsPopup.lua +++ b/UI/SettingsPopup.lua @@ -1216,15 +1216,29 @@ local function GetCategoryRowFrame(index) -- Category name local nameText = row:CreateFontString(nil, "OVERLAY", "GameFontNormal") nameText:SetPoint("LEFT", checkbox, "RIGHT", 5, 0) - nameText:SetWidth(200) + nameText:SetWidth(160) nameText:SetJustifyH("LEFT") row.nameText = nameText + -- Edit button + local editBtn = CreateFrame("Button", rowName .. "_EditBtn", row, "UIPanelButtonTemplate") + editBtn:SetWidth(40) + editBtn:SetHeight(18) + editBtn:SetPoint("LEFT", nameText, "RIGHT", 5, 0) + editBtn:SetText("Edit") + editBtn:SetScript("OnClick", function() + local catId = this:GetParent().categoryId + if catId then + Guda_CategoryEditor_Open(catId) + end + end) + row.editBtn = editBtn + -- Move Up button local upBtn = CreateFrame("Button", rowName .. "_UpBtn", row) upBtn:SetWidth(20) upBtn:SetHeight(20) - upBtn:SetPoint("LEFT", nameText, "RIGHT", 10, 0) + upBtn:SetPoint("LEFT", editBtn, "RIGHT", 5, 0) upBtn:SetNormalTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Up") upBtn:SetPushedTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Down") upBtn:SetHighlightTexture("Interface\\Buttons\\UI-ScrollBar-ScrollUpButton-Highlight") @@ -1396,6 +1410,493 @@ function Guda_SettingsPopup_RefreshBagFrames() end end +------------------------------------------- +-- Category Editor Functions +------------------------------------------- + +local editorCategoryId = nil +local editorMatchMode = "any" +local editorRules = {} +local editorRuleFrames = {} +local RULE_ROW_HEIGHT = 28 +local MAX_RULES = 6 + +-- Rule type options for dropdown +local RULE_TYPE_OPTIONS = { + { id = "itemType", name = "Item Type" }, + { id = "itemSubtype", name = "Item Subtype" }, + { id = "namePattern", name = "Name Contains" }, + { id = "quality", name = "Quality (exact)" }, + { id = "qualityMin", name = "Quality (min)" }, + { id = "isBoE", name = "Bind on Equip" }, + { id = "isQuestItem", name = "Quest Item" }, + { id = "restoreTag", name = "Restore Type" }, + { id = "isSoulShard", name = "Soul Shard" }, + { id = "isProjectile", name = "Projectile" }, +} + +-- Value options for specific rule types +local RULE_VALUE_OPTIONS = { + itemType = { "Armor", "Weapon", "Consumable", "Container", "Trade Goods", "Projectile", "Quiver", "Reagent", "Recipe", "Key", "Miscellaneous", "Quest" }, + quality = { "0 - Poor", "1 - Common", "2 - Uncommon", "3 - Rare", "4 - Epic", "5 - Legendary" }, + qualityMin = { "0 - Poor", "1 - Common", "2 - Uncommon", "3 - Rare", "4 - Epic", "5 - Legendary" }, + isBoE = { "true", "false" }, + isQuestItem = { "true", "false" }, + isSoulShard = { "true", "false" }, + isProjectile = { "true", "false" }, + restoreTag = { "eat", "drink", "restore" }, +} + +-- OnLoad for Category Editor +function Guda_CategoryEditor_OnLoad(self) + Guda:ApplyBackdrop(self, "DEFAULT_FRAME") + + -- Set button texts + local addBtn = getglobal("Guda_CategoryEditor_AddRuleButton") + if addBtn then addBtn:SetText("+ Add Rule") end + + local saveBtn = getglobal("Guda_CategoryEditor_SaveButton") + if saveBtn then saveBtn:SetText("Save") end + + local cancelBtn = getglobal("Guda_CategoryEditor_CancelButton") + if cancelBtn then cancelBtn:SetText("Cancel") end + + -- Create radio button labels + local anyRadio = getglobal("Guda_CategoryEditor_MatchAny") + if anyRadio then + local label = anyRadio:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + label:SetPoint("LEFT", anyRadio, "RIGHT", 2, 0) + label:SetText("Any rule") + end + + local allRadio = getglobal("Guda_CategoryEditor_MatchAll") + if allRadio then + local label = allRadio:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + label:SetPoint("LEFT", allRadio, "RIGHT", 2, 0) + label:SetText("All rules") + end +end + +-- OnShow for Category Editor +function Guda_CategoryEditor_OnShow(self) + Guda_CategoryEditor_UpdateRulesDisplay() +end + +-- Open Category Editor for a specific category +function Guda_CategoryEditor_Open(categoryId) + if not Guda.Modules.CategoryManager then return end + + local categoryDef = Guda.Modules.CategoryManager:GetCategory(categoryId) + if not categoryDef then return end + + editorCategoryId = categoryId + editorMatchMode = categoryDef.matchMode or "any" + + -- Copy rules + editorRules = {} + if categoryDef.rules then + for i, rule in ipairs(categoryDef.rules) do + table.insert(editorRules, { type = rule.type, value = rule.value }) + end + end + + -- Set title + local title = getglobal("Guda_CategoryEditor_Title") + if title then + if categoryDef.isBuiltIn then + title:SetText("Edit Category (Built-in)") + else + title:SetText("Edit Category") + end + end + + -- Set name + local nameBox = getglobal("Guda_CategoryEditor_NameEditBox") + if nameBox then + nameBox:SetText(categoryDef.name or categoryId) + -- Disable name editing for built-in categories + if categoryDef.isBuiltIn then + nameBox:EnableMouse(false) + nameBox:EnableKeyboard(false) + nameBox:SetTextColor(0.5, 0.5, 0.5) + else + nameBox:EnableMouse(true) + nameBox:EnableKeyboard(true) + nameBox:SetTextColor(1, 1, 1) + end + end + + -- Set match mode + Guda_CategoryEditor_SetMatchMode(editorMatchMode) + + -- Show editor + local editor = getglobal("Guda_CategoryEditor") + if editor then + editor:Show() + end +end + +-- Set match mode (radio buttons) +function Guda_CategoryEditor_SetMatchMode(mode) + editorMatchMode = mode + + local anyRadio = getglobal("Guda_CategoryEditor_MatchAny") + local allRadio = getglobal("Guda_CategoryEditor_MatchAll") + + if anyRadio then anyRadio:SetChecked(mode == "any" and 1 or 0) end + if allRadio then allRadio:SetChecked(mode == "all" and 1 or 0) end +end + +-- Get or create a rule row frame +local function GetRuleRowFrame(index) + if editorRuleFrames[index] then + return editorRuleFrames[index] + end + + local container = getglobal("Guda_CategoryEditor_RulesContainer") + if not container then return nil end + + local rowName = "Guda_CategoryEditor_RuleRow" .. index + local row = CreateFrame("Frame", rowName, container) + row:SetHeight(RULE_ROW_HEIGHT) + row:SetWidth(360) + row:SetPoint("TOPLEFT", container, "TOPLEFT", 0, -((index - 1) * RULE_ROW_HEIGHT)) + + -- Rule type dropdown button + local typeBtn = CreateFrame("Button", rowName .. "_TypeBtn", row, "UIPanelButtonTemplate") + typeBtn:SetWidth(120) + typeBtn:SetHeight(22) + typeBtn:SetPoint("LEFT", row, "LEFT", 0, 0) + typeBtn:SetText("Select Type") + typeBtn.ruleIndex = index + typeBtn:SetScript("OnClick", function() + Guda_CategoryEditor_ShowTypeDropdown(this, this.ruleIndex) + end) + row.typeBtn = typeBtn + + -- Value input (editbox for text, button for dropdowns) + local valueBox = CreateFrame("EditBox", rowName .. "_ValueBox", row, "InputBoxTemplate") + valueBox:SetWidth(140) + valueBox:SetHeight(22) + valueBox:SetPoint("LEFT", typeBtn, "RIGHT", 5, 0) + valueBox:SetAutoFocus(false) + valueBox.ruleIndex = index + valueBox:SetScript("OnTextChanged", function() + local idx = this.ruleIndex + if editorRules[idx] then + editorRules[idx].value = this:GetText() + end + end) + valueBox:SetScript("OnEscapePressed", function() this:ClearFocus() end) + valueBox:SetScript("OnEnterPressed", function() this:ClearFocus() end) + row.valueBox = valueBox + + -- Value dropdown button (for predefined values) + local valueBtn = CreateFrame("Button", rowName .. "_ValueBtn", row, "UIPanelButtonTemplate") + valueBtn:SetWidth(140) + valueBtn:SetHeight(22) + valueBtn:SetPoint("LEFT", typeBtn, "RIGHT", 5, 0) + valueBtn:SetText("Select Value") + valueBtn.ruleIndex = index + valueBtn:SetScript("OnClick", function() + Guda_CategoryEditor_ShowValueDropdown(this, this.ruleIndex) + end) + valueBtn:Hide() + row.valueBtn = valueBtn + + -- Delete button + local deleteBtn = CreateFrame("Button", rowName .. "_DeleteBtn", row, "UIPanelCloseButton") + deleteBtn:SetWidth(22) + deleteBtn:SetHeight(22) + deleteBtn:SetPoint("LEFT", valueBox, "RIGHT", 5, 0) + deleteBtn.ruleIndex = index + deleteBtn:SetScript("OnClick", function() + Guda_CategoryEditor_RemoveRule(this.ruleIndex) + end) + row.deleteBtn = deleteBtn + + editorRuleFrames[index] = row + return row +end + +-- Update rules display +function Guda_CategoryEditor_UpdateRulesDisplay() + local numRules = table.getn(editorRules) + + for i = 1, MAX_RULES do + local row = GetRuleRowFrame(i) + if row then + if i <= numRules then + local rule = editorRules[i] + row.ruleIndex = i + row.typeBtn.ruleIndex = i + row.valueBox.ruleIndex = i + row.valueBtn.ruleIndex = i + row.deleteBtn.ruleIndex = i + + -- Set type button text + local typeName = "Select Type" + for _, opt in ipairs(RULE_TYPE_OPTIONS) do + if opt.id == rule.type then + typeName = opt.name + break + end + end + row.typeBtn:SetText(typeName) + + -- Show appropriate value input + if RULE_VALUE_OPTIONS[rule.type] then + -- Use dropdown for predefined values + row.valueBox:Hide() + row.valueBtn:Show() + local displayValue = tostring(rule.value or "Select") + -- Format quality display + if (rule.type == "quality" or rule.type == "qualityMin") and type(rule.value) == "number" then + local qualNames = { [0]="Poor", [1]="Common", [2]="Uncommon", [3]="Rare", [4]="Epic", [5]="Legendary" } + displayValue = rule.value .. " - " .. (qualNames[rule.value] or "") + end + row.valueBtn:SetText(displayValue) + else + -- Use editbox for text input + row.valueBtn:Hide() + row.valueBox:Show() + row.valueBox:SetText(tostring(rule.value or "")) + end + + row:Show() + else + row:Hide() + end + end + end + + -- Enable/disable Add Rule button + local addBtn = getglobal("Guda_CategoryEditor_AddRuleButton") + if addBtn then + if numRules >= MAX_RULES then + addBtn:Disable() + else + addBtn:Enable() + end + end +end + +-- Add a new rule +function Guda_CategoryEditor_AddRule() + if table.getn(editorRules) >= MAX_RULES then return end + + table.insert(editorRules, { type = "itemType", value = "Consumable" }) + Guda_CategoryEditor_UpdateRulesDisplay() +end + +-- Remove a rule +function Guda_CategoryEditor_RemoveRule(index) + if index > 0 and index <= table.getn(editorRules) then + table.remove(editorRules, index) + Guda_CategoryEditor_UpdateRulesDisplay() + end +end + +-- Helper to set rule type (called from dropdown) +function Guda_CategoryEditor_SetRuleType(ruleIndex, typeId) + if not editorRules[ruleIndex] then return end + + editorRules[ruleIndex].type = typeId + -- Reset value when type changes + if RULE_VALUE_OPTIONS[typeId] then + editorRules[ruleIndex].value = RULE_VALUE_OPTIONS[typeId][1] + -- Convert to proper type + if typeId == "quality" or typeId == "qualityMin" then + editorRules[ruleIndex].value = 0 + elseif typeId == "isBoE" or typeId == "isQuestItem" or typeId == "isSoulShard" or typeId == "isProjectile" then + editorRules[ruleIndex].value = true + end + else + editorRules[ruleIndex].value = "" + end + Guda_CategoryEditor_UpdateRulesDisplay() +end + +-- Helper to set rule value (called from dropdown) +function Guda_CategoryEditor_SetRuleValue(ruleIndex, val, ruleType) + if not editorRules[ruleIndex] then return end + + if ruleType == "quality" or ruleType == "qualityMin" then + local num = tonumber(string.sub(val, 1, 1)) + editorRules[ruleIndex].value = num or 0 + elseif ruleType == "isBoE" or ruleType == "isQuestItem" or ruleType == "isSoulShard" or ruleType == "isProjectile" then + editorRules[ruleIndex].value = (val == "true") + else + editorRules[ruleIndex].value = val + end + Guda_CategoryEditor_UpdateRulesDisplay() +end + +-- Show type dropdown menu +function Guda_CategoryEditor_ShowTypeDropdown(button, ruleIndex) + local menu = {} + for i = 1, table.getn(RULE_TYPE_OPTIONS) do + local opt = RULE_TYPE_OPTIONS[i] + table.insert(menu, { + text = opt.name, + ruleIndex = ruleIndex, + typeId = opt.id, + }) + end + + Guda_ShowSimpleDropdown(button, menu, "type") +end + +-- Show value dropdown menu +function Guda_CategoryEditor_ShowValueDropdown(button, ruleIndex) + local rule = editorRules[ruleIndex] + if not rule then return end + + local options = RULE_VALUE_OPTIONS[rule.type] + if not options then return end + + local menu = {} + for i = 1, table.getn(options) do + local val = options[i] + table.insert(menu, { + text = val, + ruleIndex = ruleIndex, + ruleType = rule.type, + value = val, + }) + end + + Guda_ShowSimpleDropdown(button, menu, "value") +end + +-- Simple dropdown menu helper +local dropdownFrame = nil +function Guda_ShowSimpleDropdown(anchor, menuItems, menuType) + if not dropdownFrame then + dropdownFrame = CreateFrame("Frame", "Guda_SimpleDropdown", UIParent) + dropdownFrame:SetFrameStrata("FULLSCREEN_DIALOG") + dropdownFrame:SetWidth(150) + dropdownFrame:SetBackdrop({ + bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", + edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", + tile = true, tileSize = 16, edgeSize = 16, + insets = { left = 4, right = 4, top = 4, bottom = 4 } + }) + dropdownFrame:SetBackdropColor(0, 0, 0, 1) + dropdownFrame:EnableMouse(true) + dropdownFrame:Hide() + + dropdownFrame:SetScript("OnLeave", function() + -- Hide after a short delay if mouse leaves + this.hideTimer = 0.5 + end) + dropdownFrame:SetScript("OnUpdate", function() + if this.hideTimer then + this.hideTimer = this.hideTimer - arg1 + if this.hideTimer <= 0 then + this.hideTimer = nil + -- Check if mouse is over any child + if not MouseIsOver(this) then + this:Hide() + end + end + end + end) + end + + -- Clear old buttons + local children = { dropdownFrame:GetChildren() } + for _, child in ipairs(children) do + child:Hide() + child:SetParent(nil) + end + + -- Create menu buttons + local btnHeight = 20 + local totalHeight = 10 + for i, item in ipairs(menuItems) do + local btn = CreateFrame("Button", nil, dropdownFrame) + btn:SetWidth(140) + btn:SetHeight(btnHeight) + btn:SetPoint("TOPLEFT", dropdownFrame, "TOPLEFT", 5, -(5 + (i-1) * btnHeight)) + + local text = btn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + text:SetPoint("LEFT", btn, "LEFT", 5, 0) + text:SetText(item.text) + btn.text = text + + local highlight = btn:CreateTexture(nil, "HIGHLIGHT") + highlight:SetAllPoints(btn) + highlight:SetTexture(1, 1, 1, 0.2) + + -- Store data on button for vanilla Lua closure compatibility + btn.menuType = menuType + btn.ruleIndex = item.ruleIndex + btn.typeId = item.typeId + btn.ruleType = item.ruleType + btn.value = item.value + + btn:SetScript("OnClick", function() + dropdownFrame:Hide() + if this.menuType == "type" then + Guda_CategoryEditor_SetRuleType(this.ruleIndex, this.typeId) + elseif this.menuType == "value" then + Guda_CategoryEditor_SetRuleValue(this.ruleIndex, this.value, this.ruleType) + end + end) + btn:SetScript("OnEnter", function() + dropdownFrame.hideTimer = nil + end) + + totalHeight = totalHeight + btnHeight + end + totalHeight = totalHeight + 5 + + dropdownFrame:SetHeight(totalHeight) + dropdownFrame:ClearAllPoints() + dropdownFrame:SetPoint("TOPLEFT", anchor, "BOTTOMLEFT", 0, 0) + dropdownFrame:Show() + dropdownFrame.hideTimer = nil +end + +-- Save category changes +function Guda_CategoryEditor_Save() + if not editorCategoryId or not Guda.Modules.CategoryManager then return end + + local categoryDef = Guda.Modules.CategoryManager:GetCategory(editorCategoryId) + if not categoryDef then return end + + -- Get name (only for custom categories) + local nameBox = getglobal("Guda_CategoryEditor_NameEditBox") + if nameBox and not categoryDef.isBuiltIn then + categoryDef.name = nameBox:GetText() + end + + -- Set match mode + categoryDef.matchMode = editorMatchMode + + -- Set rules + categoryDef.rules = {} + for _, rule in ipairs(editorRules) do + if rule.type and rule.type ~= "" then + table.insert(categoryDef.rules, { type = rule.type, value = rule.value }) + end + end + + -- Save to database + Guda.Modules.CategoryManager:UpdateCategory(editorCategoryId, categoryDef) + + -- Refresh displays + Guda_SettingsPopup_CategoriesTab_Update() + Guda_SettingsPopup_RefreshBagFrames() + + -- Close editor + local editor = getglobal("Guda_CategoryEditor") + if editor then editor:Hide() end + + Guda:Print("Category '" .. (categoryDef.name or editorCategoryId) .. "' saved.") +end + -- Initialize function SettingsPopup:Initialize() Guda:Debug("Settings popup initialized") diff --git a/UI/SettingsPopup.xml b/UI/SettingsPopup.xml index 20652b3..3dd7e30 100644 --- a/UI/SettingsPopup.xml +++ b/UI/SettingsPopup.xml @@ -769,4 +769,246 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + this:ClearFocus() + + + this:ClearFocus() + + + + + + + + + + + + + + + + + + + Guda_CategoryEditor_SetMatchMode("any") + + + + + + + + + + + + + + + + + + + Guda_CategoryEditor_SetMatchMode("all") + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Guda_CategoryEditor_OnLoad(this) + + + Guda_CategoryEditor_OnShow(this) + + + if (arg1 == "LeftButton") then + this:StartMoving() + end + + + this:StopMovingOrSizing() + + + +