From 57ce89cc827adb0c59ff5ebc596f841aa1f18037 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 4 Aug 2026 19:20:31 +1000 Subject: [PATCH 1/5] Improve disable mods and custom mods box When double clicking an item on the items tab it would open up the item on the side but then required 2 clicks if you wanted to disable a mod by clicking it as the first click was to deselect the item list and the second actually registers the click on the item --- spec/System/TestItemDBControl_spec.lua | 39 ++++++++++++++++++++++++ spec/System/TestItemListControl_spec.lua | 28 +++++++++++++++++ src/Classes/ItemDBControl.lua | 1 + src/Classes/ItemListControl.lua | 1 + src/Classes/ListControl.lua | 6 ++-- 5 files changed, 73 insertions(+), 2 deletions(-) diff --git a/spec/System/TestItemDBControl_spec.lua b/spec/System/TestItemDBControl_spec.lua index 85dc8efa4b8..3a77a067e92 100644 --- a/spec/System/TestItemDBControl_spec.lua +++ b/spec/System/TestItemDBControl_spec.lua @@ -1,4 +1,14 @@ describe("ItemDBControl", function() + local originalGetCursorPos + + before_each(function() + originalGetCursorPos = GetCursorPos + end) + + after_each(function() + GetCursorPos = originalGetCursorPos + end) + it("sorts lower-is-better stats below zero", function() local function makeItem(name) return { @@ -75,4 +85,33 @@ describe("ItemDBControl", function() assert.is_true(control:DoesItemMatchFilters(item)) end) + + it("releases focus after opening an item with a double click", function() + local item = { + raw = "Rarity: Unique\nTest Item\nLeather Belt", + } + local itemsTab + itemsTab = { + CreateDisplayItemFromRaw = function(_, raw, isUnique) + itemsTab.displayRaw = raw + itemsTab.displayIsUnique = isUnique + end, + } + local control = new("ItemDBControl", nil, { 0, 0, 100, 100 }, itemsTab, { + list = { item }, + }, "UNIQUE") + control.list = { item } + GetCursorPos = function() + return 3, 3 + end + control.GetRowRegion = function() + return { x = 0, y = 0, width = 100, height = 100 } + end + + local selectedControl = control:OnKeyDown("LEFTBUTTON", true) + + assert.is_nil(selectedControl) + assert.are.equal(item.raw, itemsTab.displayRaw) + assert.is_true(itemsTab.displayIsUnique) + end) end) diff --git a/spec/System/TestItemListControl_spec.lua b/spec/System/TestItemListControl_spec.lua index 17982f5a310..9b27e5ec0e0 100644 --- a/spec/System/TestItemListControl_spec.lua +++ b/spec/System/TestItemListControl_spec.lua @@ -1,5 +1,6 @@ describe("ItemListControl", function() local originalOpenConfirmPopup + local originalGetCursorPos local function newItemListControl() local activeItemSet = { @@ -57,10 +58,12 @@ describe("ItemListControl", function() before_each(function() originalOpenConfirmPopup = main.OpenConfirmPopup + originalGetCursorPos = GetCursorPos end) after_each(function() main.OpenConfirmPopup = originalOpenConfirmPopup + GetCursorPos = originalGetCursorPos end) it("only shows items from the active item set and passive tree", function() @@ -172,4 +175,29 @@ describe("ItemListControl", function() assert.are.same({ }, itemsTab.itemOrderList) assert.are.same({ }, itemsTab.items) end) + + it("releases focus after opening an item with a double click", function() + local control, itemsTab = newItemListControl() + local item = new("Item", [[ +Rarity: Rare +Test Belt +Leather Belt +]]) + item.id = 1 + itemsTab.items[1] = item + itemsTab.SetDisplayItem = function(_, displayItem) + itemsTab.displayItem = displayItem + end + GetCursorPos = function() + return 3, 3 + end + control.GetRowRegion = function() + return { x = 0, y = 0, width = 360, height = 308 } + end + + local selectedControl = control:OnKeyDown("LEFTBUTTON", true) + + assert.is_nil(selectedControl) + assert.are.equal(1, itemsTab.displayItem.id) + end) end) diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index f8a56af8282..0995654528e 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -360,6 +360,7 @@ function ItemDBClass:OnSelClick(index, item, doubleClick) self.itemsTab.build.buildFlag = true elseif doubleClick then self.itemsTab:CreateDisplayItemFromRaw(item.raw, true) + return false end end diff --git a/src/Classes/ItemListControl.lua b/src/Classes/ItemListControl.lua index 11ae71487b7..acb48e8e13d 100644 --- a/src/Classes/ItemListControl.lua +++ b/src/Classes/ItemListControl.lua @@ -316,6 +316,7 @@ function ItemListClass:OnSelClick(index, itemId, doubleClick) local newItem = new("Item", item:BuildRaw()) newItem.id = item.id self.itemsTab:SetDisplayItem(newItem) + return false end end diff --git a/src/Classes/ListControl.lua b/src/Classes/ListControl.lua index 34dd0e7f413..c37608e20f4 100644 --- a/src/Classes/ListControl.lua +++ b/src/Classes/ListControl.lua @@ -16,7 +16,7 @@ -- :OnDragSend(index, value, target) [Called after a drag event] -- :OnOrderChange() [Called after list order is changed through dragging] -- :OnSelect(index, value) [Called when a list value is selected] --- :OnSelClick(index, value, doubleClick) [Called when a list value is clicked] +-- :OnSelClick(index, value, doubleClick) [Called when a list value is clicked; return false to release focus] -- :OnSelCopy(index, value) [Called when Ctrl+C is pressed while a list value is selected] -- :OnSelDelete(index, value) [Called when backspace or delete is pressed while a list value is selected] -- :OnSelKeyDown(index, value) [Called when any other key is pressed while a list value is selected] @@ -366,7 +366,9 @@ function ListClass:OnKeyDown(key, doubleClick) self.selDragActive = false end if self.OnSelClick then - self:OnSelClick(self.selIndex, self.selValue, doubleClick) + if self:OnSelClick(self.selIndex, self.selValue, doubleClick) == false then + return + end end end elseif #self.list > 0 and not self.selDragActive then From 9704bf0b6c5d26db944cad5fe4db53a4555c840d Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 4 Aug 2026 19:20:46 +1000 Subject: [PATCH 2/5] Fix add mod issues The mod list has been improved so that it now no longer shows duplicates for the same mod but with different stat rolls It also sorts the list by stat order and has a x button in the search bar to clear it --- spec/System/TestCustomModControl_spec.lua | 92 +++++++++++++++++++++++ src/Classes/ConfigTab.lua | 61 ++++++++++++--- 2 files changed, 141 insertions(+), 12 deletions(-) create mode 100644 spec/System/TestCustomModControl_spec.lua diff --git a/spec/System/TestCustomModControl_spec.lua b/spec/System/TestCustomModControl_spec.lua new file mode 100644 index 00000000000..c81621918d0 --- /dev/null +++ b/spec/System/TestCustomModControl_spec.lua @@ -0,0 +1,92 @@ +describe("Custom modifier controls", function() + local initialPopupCount + + before_each(function() + newBuild() + main:SelectControl() + initialPopupCount = #main.popups + end) + + after_each(function() + main:SelectControl() + while #main.popups > initialPopupCount do + main:ClosePopup() + end + end) + + local function openModBrowser() + local configTab = build.configTab + local blockData = configTab.configSets[configTab.activeConfigSetId].customModsList[1] + configTab:OpenAddModPopup(blockData) + return main.popups[1], blockData + end + + it("does not retain the modifier list focus after adding a mod", function() + local popup, blockData = openModBrowser() + local listControl = popup.controls.listControl + local selectedMod = listControl.list[1] + + listControl:OnSelClick(1, selectedMod, false) + popup.controls.save.onClick() + + assert.is_nil(main.selControl) + assert.are_not.equal(popup, main.popups[1]) + assert.are.equal(itemLib.applyRange(selectedMod, 0.5), blockData.text) + end) + + it("collapses numeric tiers of the same modifier", function() + local popup = openModBrowser() + local minionDamageEntries = { } + for _, modText in ipairs(popup.controls.listControl.list) do + if modText:match("^Minions deal .-%% increased Damage$") then + table.insert(minionDamageEntries, modText) + end + end + + assert.are.same({ "Minions deal (10-11)% increased Damage" }, minionDamageEntries) + end) + + it("orders modifiers by their number-independent stat text", function() + local popup = openModBrowser() + local previousTemplate + for _, modText in ipairs(popup.controls.listControl.list) do + local modTemplate = modText + :gsub("([%+-]?)%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)", "%1#") + :gsub("%d+%.?%d*", "#") + :lower() + assert.is_true(not previousTemplate or previousTemplate < modTemplate, + tostring(previousTemplate) .. " should be ordered before " .. modTemplate) + previousTemplate = modTemplate + end + end) + + it("clears the modifier search with its clear button", function() + local popup = openModBrowser() + local controls = popup.controls + local unfilteredCount = #controls.listControl.list + assert.is_false(controls.search.controls.buttonClear:IsShown()) + controls.search:SetText("minion damage", true) + + assert.is_true(#controls.listControl.list < unfilteredCount) + assert.are.equal("minion damage", controls.search.buf) + assert.is_true(controls.search.controls.buttonClear:IsShown()) + + controls.search.controls.buttonClear.onClick() + + assert.are.equal("", controls.search.buf) + assert.are.equal(unfilteredCount, #controls.listControl.list) + assert.is_false(controls.search.controls.buttonClear:IsShown()) + end) + + it("uses the expanded browser dimensions", function() + local popup = openModBrowser() + local listWidth, listHeight = popup.controls.listControl:GetSize() + local searchWidth = popup.controls.search:GetSize() + + assert.are.equal(720, popup.width) + assert.are.equal(540, popup.height) + assert.are.equal(700, listWidth) + assert.are.equal(454, listHeight) + assert.are.equal(640, searchWidth) + end) +end) diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index 47fe38cb865..51be1ec5d7f 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -1352,7 +1352,44 @@ function ConfigTabClass:OpenAddModPopup(blockData) end end - table.sort(allModsList) + local modTemplateCache = { } + local function getModTemplate(modText) + if not modTemplateCache[modText] then + modTemplateCache[modText] = modText + :gsub("([%+-]?)%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)", "%1#") + :gsub("%d+%.?%d*", "#") + :lower() + end + return modTemplateCache[modText] + end + local function sortByModTemplate(a, b) + local aTemplate = getModTemplate(a) + local bTemplate = getModTemplate(b) + if aTemplate ~= bTemplate then + return aTemplate < bTemplate + end + local aLower = a:lower() + local bLower = b:lower() + if aLower ~= bLower then + return aLower < bLower + end + return a < b + end + + table.sort(allModsList, sortByModTemplate) + + -- Collapse affix tiers that only differ by their numeric values. The list is + -- sorted first so the retained representative is deterministic. + wipeTable(seen) + local deduplicatedModsList = { } + for _, modText in ipairs(allModsList) do + local modTemplate = getModTemplate(modText) + if not seen[modTemplate] then + seen[modTemplate] = true + t_insert(deduplicatedModsList, modText) + end + end + allModsList = deduplicatedModsList local displayList = { } local controls = { } @@ -1416,7 +1453,7 @@ function ConfigTabClass:OpenAddModPopup(blockData) if a.rank ~= b.rank then return a.rank < b.rank end - return a.text < b.text + return sortByModTemplate(a.text, b.text) end) for _, match in ipairs(matches) do t_insert(displayList, match.text) @@ -1433,7 +1470,7 @@ function ConfigTabClass:OpenAddModPopup(blockData) end end - controls.listControl = new("ListControl", {"TOPLEFT", nil, "TOPLEFT"}, {10, 20, 580, 184}, 16, "VERTICAL", false, displayList) + controls.listControl = new("ListControl", {"TOPLEFT", nil, "TOPLEFT"}, {10, 20, 700, 454}, 16, "VERTICAL", false, displayList) controls.listControl.font = "VAR" controls.listControl.hasFocus = true controls.listControl.GetRowValue = function(self, column, index, value) @@ -1452,23 +1489,23 @@ function ConfigTabClass:OpenAddModPopup(blockData) end end controls.listControl.OnSelClick = function(self, index, value, doubleClick) - if main.SelectControl then - main:SelectControl(self) - end self:SelectIndex(index) if doubleClick and controls.save:IsEnabled() then controls.save.onClick() end end - controls.searchLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {65, 212, 0, 16}, "^7Search:") - controls.search = new("EditControl", {"TOPLEFT", nil, "TOPLEFT"}, {70, 212, 520, 18}, "", nil, "%c", 100, function() + controls.searchLabel = new("LabelControl", {"TOPRIGHT", nil, "TOPLEFT"}, {65, 482, 0, 16}, "^7Search:") + controls.search = new("EditControl", {"TOPLEFT", nil, "TOPLEFT"}, {70, 482, 640, 18}, "", nil, "%c", 100, function() updateDisplayList() - end) + end, nil, nil, true) + controls.search.controls.buttonClear.shown = function() + return #controls.search.buf > 0 + end updateDisplayList() - controls.save = new("ButtonControl", nil, {-45, 242, 80, 20}, "Add", function() + controls.save = new("ButtonControl", nil, {-45, 512, 80, 20}, "Add", function() local selIndex = controls.listControl.selIndex or 1 local selected = displayList[selIndex] if selected and selected ~= "No matching modifiers found" then @@ -1490,9 +1527,9 @@ function ConfigTabClass:OpenAddModPopup(blockData) return selected ~= nil and selected ~= "No matching modifiers found" end - controls.close = new("ButtonControl", nil, {45, 242, 80, 20}, "Cancel", function() + controls.close = new("ButtonControl", nil, {45, 512, 80, 20}, "Cancel", function() main:ClosePopup() end) - main:OpenPopup(600, 270, "Mod Browser", controls, "save", nil, "close") + main:OpenPopup(720, 540, "Mod Browser", controls, "save", nil, "close") end From 1934249a5afb04d5b2fe4d8c5e59a3f948ba1a5e Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 4 Aug 2026 19:43:52 +1000 Subject: [PATCH 3/5] Change sort + remove mod ranges Change the sort to be alphabetical but without caring about the mod rolls Also removes the mod ranges from the display of mods as it makes it much harder to read the lines and the range gets stripped anyway on being added --- spec/System/TestCustomModControl_spec.lua | 30 ++++++++++++++--------- src/Classes/ConfigTab.lua | 30 +++++++++++++++++------ 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/spec/System/TestCustomModControl_spec.lua b/spec/System/TestCustomModControl_spec.lua index c81621918d0..9bc06637e51 100644 --- a/spec/System/TestCustomModControl_spec.lua +++ b/spec/System/TestCustomModControl_spec.lua @@ -31,32 +31,40 @@ describe("Custom modifier controls", function() assert.is_nil(main.selControl) assert.are_not.equal(popup, main.popups[1]) - assert.are.equal(itemLib.applyRange(selectedMod, 0.5), blockData.text) + assert.are.equal(selectedMod, blockData.text) end) - it("collapses numeric tiers of the same modifier", function() - local popup = openModBrowser() + it("collapses numeric tiers and imports the first range value", function() + local popup, blockData = openModBrowser() local minionDamageEntries = { } - for _, modText in ipairs(popup.controls.listControl.list) do + local minionDamageIndex + for index, modText in ipairs(popup.controls.listControl.list) do if modText:match("^Minions deal .-%% increased Damage$") then table.insert(minionDamageEntries, modText) + minionDamageIndex = index end end - assert.are.same({ "Minions deal (10-11)% increased Damage" }, minionDamageEntries) + assert.are.same({ "Minions deal 10% increased Damage" }, minionDamageEntries) + popup.controls.listControl.selIndex = minionDamageIndex + popup.controls.save.onClick() + assert.are.equal("Minions deal 10% increased Damage", blockData.text) end) - it("orders modifiers by their number-independent stat text", function() + it("orders modifiers alphabetically while ignoring numeric values", function() local popup = openModBrowser() - local previousTemplate + local previousSortKey for _, modText in ipairs(popup.controls.listControl.list) do - local modTemplate = modText + local sortKey = modText :gsub("([%+-]?)%((%-?%d+%.?%d*)%-(%-?%d+%.?%d*)%)", "%1#") :gsub("%d+%.?%d*", "#") :lower() - assert.is_true(not previousTemplate or previousTemplate < modTemplate, - tostring(previousTemplate) .. " should be ordered before " .. modTemplate) - previousTemplate = modTemplate + :gsub("#", " ") + :gsub("[^%a]+", " ") + :match("^%s*(.-)%s*$") + assert.is_true(not previousSortKey or previousSortKey <= sortKey, + tostring(previousSortKey) .. " should be ordered before " .. sortKey) + previousSortKey = sortKey end end) diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index 51be1ec5d7f..bac2400d3e4 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -1362,7 +1362,22 @@ function ConfigTabClass:OpenAddModPopup(blockData) end return modTemplateCache[modText] end - local function sortByModTemplate(a, b) + local alphabeticalSortKeyCache = { } + local function getAlphabeticalSortKey(modText) + if not alphabeticalSortKeyCache[modText] then + alphabeticalSortKeyCache[modText] = getModTemplate(modText) + :gsub("#", " ") + :gsub("[^%a]+", " ") + :match("^%s*(.-)%s*$") + end + return alphabeticalSortKeyCache[modText] + end + local function sortAlphabeticallyIgnoringValues(a, b) + local aSortKey = getAlphabeticalSortKey(a) + local bSortKey = getAlphabeticalSortKey(b) + if aSortKey ~= bSortKey then + return aSortKey < bSortKey + end local aTemplate = getModTemplate(a) local bTemplate = getModTemplate(b) if aTemplate ~= bTemplate then @@ -1376,7 +1391,7 @@ function ConfigTabClass:OpenAddModPopup(blockData) return a < b end - table.sort(allModsList, sortByModTemplate) + table.sort(allModsList, sortAlphabeticallyIgnoringValues) -- Collapse affix tiers that only differ by their numeric values. The list is -- sorted first so the retained representative is deterministic. @@ -1386,9 +1401,10 @@ function ConfigTabClass:OpenAddModPopup(blockData) local modTemplate = getModTemplate(modText) if not seen[modTemplate] then seen[modTemplate] = true - t_insert(deduplicatedModsList, modText) + t_insert(deduplicatedModsList, itemLib.applyRange(modText, 0)) end end + table.sort(deduplicatedModsList, sortAlphabeticallyIgnoringValues) allModsList = deduplicatedModsList local displayList = { } @@ -1453,7 +1469,7 @@ function ConfigTabClass:OpenAddModPopup(blockData) if a.rank ~= b.rank then return a.rank < b.rank end - return sortByModTemplate(a.text, b.text) + return sortAlphabeticallyIgnoringValues(a.text, b.text) end) for _, match in ipairs(matches) do t_insert(displayList, match.text) @@ -1479,8 +1495,7 @@ function ConfigTabClass:OpenAddModPopup(blockData) controls.listControl.AddValueTooltip = function(self, tooltip, index, value) tooltip:Clear(true) if value and #value > 0 and value ~= "No matching modifiers found" then - local cleanText = itemLib.applyRange(value, 0.5) - local mods, extra = modLib.parseMod(cleanText) + local mods, extra = modLib.parseMod(value) if mods and not extra then tooltip:AddLine(14, "^7Supported: ^2Yes") else @@ -1509,11 +1524,10 @@ function ConfigTabClass:OpenAddModPopup(blockData) local selIndex = controls.listControl.selIndex or 1 local selected = displayList[selIndex] if selected and selected ~= "No matching modifiers found" then - local textToAdd = itemLib.applyRange(selected, 0.5) if blockData.text and #blockData.text > 0 and not blockData.text:match("\n$") then blockData.text = blockData.text .. "\n" end - blockData.text = (blockData.text or "") .. textToAdd + blockData.text = (blockData.text or "") .. selected self:UpdateCustomModsControls() self:AddUndoState() self:BuildModList() From 76e1eb6efd18289fe5e22d4f5305cbfe25098e31 Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 4 Aug 2026 22:09:27 +1000 Subject: [PATCH 4/5] Rename add mod + add mod group + focus search box When opening the UI it selects the search box so you can type right away Changes the naming of + mod and + block as I don't feel like they were the best names --- spec/System/TestCustomModControl_spec.lua | 13 +++++++++++++ src/Classes/ConfigTab.lua | 15 +++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/spec/System/TestCustomModControl_spec.lua b/spec/System/TestCustomModControl_spec.lua index 9bc06637e51..c3e4ff1bcb1 100644 --- a/spec/System/TestCustomModControl_spec.lua +++ b/spec/System/TestCustomModControl_spec.lua @@ -97,4 +97,17 @@ describe("Custom modifier controls", function() assert.are.equal(454, listHeight) assert.are.equal(640, searchWidth) end) + + it("opens with the search field ready for typing", function() + local popup = openModBrowser() + local inputEvents = { { type = "Char", key = "m" } } + + assert.are.equal(popup.controls.search, popup.selControl) + assert.is_true(popup.controls.search.hasFocus) + assert.is_nil(popup.controls.listControl.hasFocus) + + popup:ProcessInput(inputEvents, { x = 0, y = 0, width = 1920, height = 1080 }) + + assert.are.equal("m", popup.controls.search.buf) + end) end) diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index bac2400d3e4..b0dfa10c0e5 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -32,12 +32,12 @@ local CustomModBlockClass = newClass("CustomModBlockControl", "Control", "Contro configTab.build.buildFlag = true end) - self.controls.titleEdit = new("EditControl", {"LEFT", self.controls.deleteBtn, "RIGHT"}, {6, 0, 232, 18}, blockData.title or "", nil, nil, nil, function(buf) + self.controls.titleEdit = new("EditControl", {"LEFT", self.controls.deleteBtn, "RIGHT"}, {6, 0, 222, 18}, blockData.title or "", nil, nil, nil, function(buf) blockData.title = buf configTab:AddUndoState() end) - self.controls.addModBtn = new("ButtonControl", {"LEFT", self.controls.titleEdit, "RIGHT"}, {6, 0, 48, 18}, "^7+ Mod", function() + self.controls.addModBtn = new("ButtonControl", {"LEFT", self.controls.titleEdit, "RIGHT"}, {6, 0, 58, 18}, "^7Add Mod", function() configTab:OpenAddModPopup(blockData) end) @@ -59,7 +59,7 @@ local CustomModBlockClass = newClass("CustomModBlockControl", "Control", "Contro local output = calcFunc() blockData.enabled = curState configTab:BuildModList() - configTab.build:AddStatComparesToTooltip(tooltip, calcBase, output, curState and "^7Disabling this block will give you:" or "^7Enabling this block will give you:") + configTab.build:AddStatComparesToTooltip(tooltip, calcBase, output, curState and "^7Disabling this group will give you:" or "^7Enabling this group will give you:") end end end @@ -770,9 +770,9 @@ local ConfigTabClass = newClass("ConfigTab", "UndoHandler", "ControlHost", "Cont end self.controls.scrollBar = new("ScrollBarControl", {"TOPRIGHT",self,"TOPRIGHT"}, {0, 0, 18, 0}, 50, "VERTICAL", true) if self.customSection then - self.controls.customModsAddBlock = new("ButtonControl", {"TOPLEFT", self.customSection, "TOPLEFT"}, {8, 0, 75, 20}, "^7+ Block", function() + self.controls.customModsAddBlock = new("ButtonControl", {"TOPLEFT", self.customSection, "TOPLEFT"}, {8, 0, 120, 20}, "^7Add Mod Group", function() local customModsList = self.configSets[self.activeConfigSetId].customModsList - t_insert(customModsList, { title = "Block " .. (#customModsList + 1), enabled = true, text = "" }) + t_insert(customModsList, { title = "Group " .. (#customModsList + 1), enabled = true, text = "" }) self:UpdateCustomModsControls() self:AddUndoState() self:BuildModList() @@ -1101,7 +1101,7 @@ function ConfigTabClass:BuildModList() end end - -- Apply Custom Modifier Blocks + -- Apply Custom Modifier groups local customModsList = self.configSets[self.activeConfigSetId].customModsList local hasBlockText = false if customModsList then @@ -1488,7 +1488,6 @@ function ConfigTabClass:OpenAddModPopup(blockData) controls.listControl = new("ListControl", {"TOPLEFT", nil, "TOPLEFT"}, {10, 20, 700, 454}, 16, "VERTICAL", false, displayList) controls.listControl.font = "VAR" - controls.listControl.hasFocus = true controls.listControl.GetRowValue = function(self, column, index, value) return value or "" end @@ -1545,5 +1544,5 @@ function ConfigTabClass:OpenAddModPopup(blockData) main:ClosePopup() end) - main:OpenPopup(720, 540, "Mod Browser", controls, "save", nil, "close") + main:OpenPopup(720, 540, "Mod Browser", controls, "save", "search", "close") end From 5c4c400c38ac92676c103d8062c2e8b0831b45cd Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Tue, 4 Aug 2026 22:09:45 +1000 Subject: [PATCH 5/5] Use group name in calc breakdown --- spec/System/TestCustomModControl_spec.lua | 30 +++++++++++++++++++++++ src/Classes/CalcBreakdownControl.lua | 2 ++ src/Classes/ConfigTab.lua | 4 ++- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/spec/System/TestCustomModControl_spec.lua b/spec/System/TestCustomModControl_spec.lua index c3e4ff1bcb1..0ec3f33c4e4 100644 --- a/spec/System/TestCustomModControl_spec.lua +++ b/spec/System/TestCustomModControl_spec.lua @@ -110,4 +110,34 @@ describe("Custom modifier controls", function() assert.are.equal("m", popup.controls.search.buf) end) + + it("uses the mod group title as the calculation source name", function() + local configTab = build.configTab + local blockData = configTab.configSets[configTab.activeConfigSetId].customModsList[1] + blockData.text = "+100 to maximum Life" + configTab:BuildModList() + + configTab.customModsBlockControls[1].controls.titleEdit:SetText("Bossing", true) + + local customMods = configTab.modList:Tabulate("BASE", nil, "Life") + assert.are.equal(1, #customMods) + assert.are.equal("Custom:Bossing", customMods[1].mod.source) + + build.buildFlag = true + runCallback("OnFrame") + local breakdownControl = build.calcsTab.controls.breakdown + breakdownControl.sectionList = { } + breakdownControl:AddModSection({ modName = "Life", modType = "BASE" }) + local customRow + for _, row in ipairs(breakdownControl.sectionList[1].rowList) do + if row.mod.source == "Custom:Bossing" then + customRow = row + break + end + end + + assert.is_not_nil(customRow) + assert.are.equal("Custom", customRow.source) + assert.are.equal("Bossing", customRow.sourceName) + end) end) diff --git a/src/Classes/CalcBreakdownControl.lua b/src/Classes/CalcBreakdownControl.lua index 1255ae9856c..6983b4b9418 100644 --- a/src/Classes/CalcBreakdownControl.lua +++ b/src/Classes/CalcBreakdownControl.lua @@ -421,6 +421,8 @@ function CalcBreakdownClass:AddModSection(sectionData, modList) row.sourceName = row.mod.source:match("Pantheon:(.+)") elseif sourceType == "Spectre" then row.sourceName = row.mod.source:match("Spectre:(.+)") + elseif sourceType == "Custom" then + row.sourceName = row.mod.source:match("Custom:(.+)") end if row.mod.flags ~= 0 or row.mod.keywordFlags ~= 0 then diff --git a/src/Classes/ConfigTab.lua b/src/Classes/ConfigTab.lua index b0dfa10c0e5..3085f50fe7d 100644 --- a/src/Classes/ConfigTab.lua +++ b/src/Classes/ConfigTab.lua @@ -35,6 +35,8 @@ local CustomModBlockClass = newClass("CustomModBlockControl", "Control", "Contro self.controls.titleEdit = new("EditControl", {"LEFT", self.controls.deleteBtn, "RIGHT"}, {6, 0, 222, 18}, blockData.title or "", nil, nil, nil, function(buf) blockData.title = buf configTab:AddUndoState() + configTab:BuildModList() + configTab.build.buildFlag = true end) self.controls.addModBtn = new("ButtonControl", {"LEFT", self.controls.titleEdit, "RIGHT"}, {6, 0, 58, 18}, "^7Add Mod", function() @@ -1112,7 +1114,7 @@ function ConfigTabClass:BuildModList() local strippedLine = StripEscapes(line):match("^%s*(.-)%s*$") local mods, extra = modLib.parseMod(strippedLine) if mods and not extra then - local source = "Custom" + local source = "Custom:" .. (block.title or "Default") for i = 1, #mods do local mod = mods[i] if mod then