From b2733802aeee8ead512ea9f0afbb81ab0c8efa80 Mon Sep 17 00:00:00 2001 From: xoxorwr Date: Sat, 29 Aug 2026 20:19:34 +0000 Subject: [PATCH 1/2] Apply changes from https://github.com/PathOfBuildingCommunity/PathOfBuilding/pull/10079 --- src/Classes/ItemListControl.lua | 6 +- src/Classes/ItemListControl.lua.rej | 194 ++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/Classes/ItemListControl.lua.rej diff --git a/src/Classes/ItemListControl.lua b/src/Classes/ItemListControl.lua index b1cef41e05..9d5c8e6c33 100644 --- a/src/Classes/ItemListControl.lua +++ b/src/Classes/ItemListControl.lua @@ -35,7 +35,7 @@ function ItemListClass:ItemListControl(anchor, rect, itemsTab, forceTooltip) spec.jewels[nodeId] = 0 end end - wipeTable(self.list) + wipeTable(itemsTab.itemOrderList) wipeTable(self.itemsTab.items) itemsTab:PopulateSlots() itemsTab:AddUndoState() @@ -159,6 +159,7 @@ function ItemListClass:ReceiveDrag(type, value, source) self.itemsTab:AddItem(newItem, true, self.selDragIndex) self.itemsTab:PopulateSlots() self.itemsTab:AddUndoState() + self:UpdateList() end end @@ -222,6 +223,7 @@ function ItemListClass:OnSelDelete(index, itemId) self.itemsTab:DeleteItem(item) self.selIndex = nil self.selValue = nil + self:UpdateList() end) else local equipTree = self:FindSocketedJewel(itemId, true) @@ -230,11 +232,13 @@ function ItemListClass:OnSelDelete(index, itemId) self.itemsTab:DeleteItem(item) self.selIndex = nil self.selValue = nil + self:UpdateList() end) else self.itemsTab:DeleteItem(item) self.selIndex = nil self.selValue = nil + self:UpdateList() end end end diff --git a/src/Classes/ItemListControl.lua.rej b/src/Classes/ItemListControl.lua.rej new file mode 100644 index 0000000000..88569d03b7 --- /dev/null +++ b/src/Classes/ItemListControl.lua.rej @@ -0,0 +1,194 @@ +diff a/src/Classes/ItemListControl.lua b/src/Classes/ItemListControl.lua (rejected hunks) +@@ -22,7 +22,7 @@ local ItemListClass = newClass("ItemListControl", "ListControl", function(self, + end) + self.controls.deleteUnused = new("ButtonControl", {"LEFT",self.controls.sort,"RIGHT"}, {4, 0, 84, 18}, "Del Unused", function() + local delList = {} +- for _, itemId in pairs(self.list) do ++ for _, itemId in pairs(itemsTab.itemOrderList) do + if not itemsTab:GetEquippedSlotForItem(itemsTab.items[itemId]) and not self:FindEquippedAbyssJewel(itemId, false) and not self:FindSocketedJewel(itemId, false) then + t_insert(delList, itemId) + end +@@ -76,12 +76,14 @@ end) + + function ItemListClass:UpdateLoadoutList() + local list = { "Any Loadout", "Current Loadout", "Unused Items" } ++ local listValues = { ["Any Loadout"] = true, ["Current Loadout"] = true, ["Unused Items"] = true } + local build = self.itemsTab.build + if build and build.controls and build.controls.buildLoadouts then + for _, val in ipairs(build.controls.buildLoadouts.list) do + if val ~= "^7^7Loadouts:" and val ~= "^7^7-----" and val ~= "^7^7New Loadout" and val ~= "^7^7Sync" and val ~= "^7^7Help >>" then +- if not isValueInArray(list, val) then ++ if not listValues[val] then + t_insert(list, val) ++ listValues[val] = true + end + end + end +@@ -90,93 +92,104 @@ function ItemListClass:UpdateLoadoutList() + for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do + local itemSet = self.itemsTab.itemSets[itemSetId] + local title = itemSet and (itemSet.title or "Default") +- if title and not isValueInArray(list, title) then ++ if title and not listValues[title] then + t_insert(list, title) ++ listValues[title] = true + end + end + end +- local selIndex = self.controls.loadoutFilter.selIndex or 1 +- self.controls.loadoutFilter:SetList(list) +- self.controls.loadoutFilter.selIndex = math.min(selIndex, #list) +-end +- +-function ItemListClass:IsItemInLoadout(itemId, filterVal) +- local item = self.itemsTab.items[itemId] +- if not item then ++ local listKey = table.concat(list, "\0") ++ if self.loadoutListKey == listKey then + return false + end +- +- -- Check item sets +- if self.itemsTab.itemSetOrderList then +- for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do +- local itemSet = self.itemsTab.itemSets[itemSetId] +- if itemSet then +- local title = itemSet.title or "Default" +- if title == filterVal or title:find(filterVal, 1, true) or filterVal:find(title, 1, true) or #self.itemsTab.itemSetOrderList == 1 then +- local slot, equipSet = self.itemsTab:GetEquippedSlotForItem(item) +- if (slot and (not equipSet or equipSet == itemSet)) or self:FindEquippedAbyssJewel(itemId, false) == title then +- return true +- end +- end +- end +- end +- end +- +- -- Check passive tree specs +- local treeTab = self.itemsTab.build.treeTab +- if treeTab and treeTab.specList then +- for _, spec in ipairs(treeTab.specList) do +- local title = spec.title or "Default" +- if title == filterVal or title:find(filterVal, 1, true) or filterVal:find(title, 1, true) or #treeTab.specList == 1 then +- if self:FindSocketedJewel(itemId, false) == title then +- return true +- end +- end +- end +- end +- +- return false ++ self.loadoutListKey = listKey ++ local selIndex = self.controls.loadoutFilter.selIndex or 1 ++ local selValue = self.controls.loadoutFilter.list and self.controls.loadoutFilter.list[selIndex] or "Any Loadout" ++ self.controls.loadoutFilter:SetList(list) ++ self.controls.loadoutFilter.selIndex = isValueInArray(list, selValue) or 1 ++ return true + end + + function ItemListClass:UpdateList() + self:UpdateLoadoutList() + local selFilter = self.controls.loadoutFilter.selIndex or 1 + local filterVal = self.controls.loadoutFilter.list[selFilter] or "Any Loadout" ++ local selectedItemId = self.selValue + + if selFilter == 1 or filterVal == "Any Loadout" then + self.list = self.itemsTab.itemOrderList +- return +- end +- +- local newList = {} +- for _, itemId in ipairs(self.itemsTab.itemOrderList) do +- local item = self.itemsTab.items[itemId] +- if item then +- if selFilter == 2 or filterVal == "Current Loadout" then +- if self.itemsTab:GetEquippedSlotForItem(item) or self:FindEquippedAbyssJewel(itemId, false) or self:FindSocketedJewel(itemId, false) then +- t_insert(newList, itemId) ++ self.isMutable = true ++ else ++ self.isMutable = false ++ local filterItemSet ++ local filterSpec ++ if selFilter == 2 or filterVal == "Current Loadout" then ++ filterItemSet = self.itemsTab.activeItemSet ++ filterSpec = self.itemsTab.build.treeTab.specList[self.itemsTab.build.treeTab.activeSpec] ++ elseif selFilter ~= 3 and filterVal ~= "Unused Items" then ++ local filterTitle = filterVal:gsub("^%[[^%]]+%]%s*", "") ++ for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do ++ local itemSet = self.itemsTab.itemSets[itemSetId] ++ if (itemSet.title or "Default") == filterTitle then ++ filterItemSet = itemSet ++ break + end +- elseif selFilter == 3 or filterVal == "Unused Items" then +- if not self.itemsTab:GetEquippedSlotForItem(item) and not self:FindEquippedAbyssJewel(itemId, false) and not self:FindSocketedJewel(itemId, false) then +- t_insert(newList, itemId) ++ end ++ local treeTab = self.itemsTab.build.treeTab ++ for _, spec in ipairs(treeTab.specList) do ++ if (spec.title or "Default") == filterTitle then ++ filterSpec = spec ++ break + end +- else +- if self:IsItemInLoadout(itemId, filterVal) then +- t_insert(newList, itemId) ++ end ++ local linkId = filterVal:match("%{(%w+)%}") ++ local itemLink = linkId and self.itemsTab.build.itemListSpecialLinks and self.itemsTab.build.itemListSpecialLinks[linkId] ++ local treeLink = linkId and self.itemsTab.build.treeListSpecialLinks and self.itemsTab.build.treeListSpecialLinks[linkId] ++ filterItemSet = filterItemSet or #self.itemsTab.itemSetOrderList == 1 and self.itemsTab.itemSets[self.itemsTab.itemSetOrderList[1]] or itemLink and self.itemsTab.itemSets[itemLink.setId] ++ filterSpec = filterSpec or #treeTab.specList == 1 and treeTab.specList[1] or treeLink and treeTab.specList[treeLink.setId] ++ end ++ filterItemSet = filterItemSet or { } ++ local newList = {} ++ for _, itemId in ipairs(self.itemsTab.itemOrderList) do ++ local item = self.itemsTab.items[itemId] ++ if item then ++ if selFilter == 3 or filterVal == "Unused Items" then ++ if not self.itemsTab:GetEquippedSlotForItem(item) and not self:FindEquippedAbyssJewel(itemId, false) and not self:FindSocketedJewel(itemId, false) then ++ t_insert(newList, itemId) ++ end ++ else ++ local inLoadout = false ++ for _, slot in pairs(filterItemSet) do ++ if type(slot) == "table" and slot.selItemId == itemId then ++ inLoadout = true ++ break ++ end ++ end ++ if not inLoadout and filterSpec then ++ for nodeId, jewelId in pairs(filterSpec.jewels) do ++ if jewelId == itemId and filterSpec.nodes[nodeId] and filterSpec.nodes[nodeId].alloc then ++ inLoadout = true ++ break ++ end ++ end ++ end ++ if inLoadout then ++ t_insert(newList, itemId) ++ end + end + end + end ++ self.list = newList + end +- self.list = newList +- if self.selIndex and self.selIndex > #self.list then +- self.selIndex = #self.list > 0 and #self.list or nil +- self.selValue = self.selIndex and self.list[self.selIndex] or nil +- end ++ self.selIndex = selectedItemId and isValueInArray(self.list, selectedItemId) or nil ++ self.selValue = self.selIndex and self.list[self.selIndex] or nil + end + + function ItemListClass:Draw(viewPort) +- if self.itemsTab.build and self.itemsTab.build.outputRevision ~= self.lastOutputRevision then +- self.lastOutputRevision = self.itemsTab.build.outputRevision ++ local loadoutListChanged = self:UpdateLoadoutList() ++ local outputRevision = self.itemsTab.build and self.itemsTab.build.outputRevision ++ if loadoutListChanged or outputRevision ~= self.lastOutputRevision then ++ self.lastOutputRevision = outputRevision + self:UpdateList() + end + self.ListControl.Draw(self, viewPort) From 1ab2c61cddc544ace11d4278e862238b1b6ff2ec Mon Sep 17 00:00:00 2001 From: LocalIdentity Date: Sun, 30 Aug 2026 09:23:51 +1000 Subject: [PATCH 2/2] Fix port --- spec/System/TestItemListControl_spec.lua | 203 ++++++++++++++++++++++- src/Classes/ItemListControl.lua | 185 +++++++++++++++++---- src/Classes/ItemListControl.lua.rej | 194 ---------------------- 3 files changed, 352 insertions(+), 230 deletions(-) delete mode 100644 src/Classes/ItemListControl.lua.rej diff --git a/spec/System/TestItemListControl_spec.lua b/spec/System/TestItemListControl_spec.lua index 16e50c6256..9456fad117 100644 --- a/spec/System/TestItemListControl_spec.lua +++ b/spec/System/TestItemListControl_spec.lua @@ -1,40 +1,229 @@ describe("ItemListControl", function() local originalGetCursorPos + local originalOpenConfirmPopup local function newItemListControl() local activeItemSet = { id = 1, title = "Boss", ["Body Armour"] = { selItemId = 1 }, + [1000] = { selItemId = 5 }, + } + local otherItemSet = { + id = 2, + title = "Mapping", + ["Body Armour"] = { selItemId = 2 }, + [2000] = { selItemId = 6 }, + } + local treeTab = { + activeSpec = 1, + specList = { + { + title = "Boss", + jewels = { [100] = 3 }, + nodes = { [100] = { alloc = true } }, + BuildClusterJewelGraphs = function() end, + }, + { + title = "Mapping", + jewels = { [200] = 4 }, + nodes = { [200] = { alloc = true } }, + BuildClusterJewelGraphs = function() end, + }, + }, } local itemsTab = { - itemOrderList = { 1, 2 }, + itemOrderList = { 1, 2, 3, 4, 5, 6, 7 }, items = { [1] = { id = 1, type = "Body Armour", base = { subType = "" } }, - [2] = { id = 2, type = "Jewel", base = { subType = "" } }, + [2] = { id = 2, type = "Body Armour", base = { subType = "" } }, + [3] = { id = 3, type = "Jewel", base = { subType = "" } }, + [4] = { id = 4, type = "Jewel", base = { subType = "" } }, + [5] = { id = 5, type = "Jewel", base = { subType = "" } }, + [6] = { id = 6, type = "Jewel", base = { subType = "" } }, + [7] = { id = 7, type = "Jewel", base = { subType = "" } }, }, - itemSets = { activeItemSet }, + itemSetOrderList = { 1, 2 }, + itemSets = { activeItemSet, otherItemSet }, + activeItemSetId = 1, activeItemSet = activeItemSet, slots = { }, build = { - treeTab = { - activeSpec = 1, - specList = { { title = "Boss", jewels = { }, nodes = { } } }, + itemListSpecialLinks = { }, + treeListSpecialLinks = { }, + controls = { + buildLoadouts = { list = { "Boss", "Mapping" } }, }, + treeTab = treeTab, }, PopulateSlots = function() end, AddUndoState = function() end, } + itemsTab.GetEquippedSlotForItem = function(_, item) + for _, itemSetId in ipairs(itemsTab.itemSetOrderList) do + local itemSet = itemsTab.itemSets[itemSetId] + if itemSet["Body Armour"].selItemId == item.id then + return { label = "Body Armour" }, itemSetId ~= itemsTab.activeItemSetId and itemSet or nil + end + end + end + itemsTab.DeleteItem = function(_, item) + itemsTab.items[item.id] = nil + local index = isValueInArray(itemsTab.itemOrderList, item.id) + if index then + table.remove(itemsTab.itemOrderList, index) + end + end local control = new("ItemListControl"):ItemListControl(nil, { 0, 0, 360, 308 }, itemsTab, true) - return control, itemsTab + return control, itemsTab, treeTab end before_each(function() originalGetCursorPos = GetCursorPos + originalOpenConfirmPopup = main.OpenConfirmPopup end) after_each(function() GetCursorPos = originalGetCursorPos + main.OpenConfirmPopup = originalOpenConfirmPopup + end) + + it("only shows items from the active item set and passive tree", function() + local control = newItemListControl() + control:UpdateLoadoutList() + control.controls.loadoutFilter.selIndex = 2 + + control:UpdateList() + + assert.are.same({ 1, 3, 5 }, control.list) + end) + + it("uses the selected item set and passive tree for named loadouts", function() + local control = newItemListControl() + control:UpdateLoadoutList() + control.controls.loadoutFilter.selIndex = isValueInArray(control.controls.loadoutFilter.list, "Mapping") + + control:UpdateList() + + assert.are.same({ 2, 4, 6 }, control.list) + end) + + it("matches linked sets and old passive tree display names", function() + local control, itemsTab, treeTab = newItemListControl() + itemsTab.itemSets[2].title = "Gear {mapping}" + treeTab.specList[2].title = "Tree {mapping}" + itemsTab.build.itemListSpecialLinks.mapping = { setId = 2 } + itemsTab.build.treeListSpecialLinks.mapping = { setId = 2 } + itemsTab.build.controls.buildLoadouts.list = { "Tree {mapping}", "[0.4] Boss" } + control:UpdateLoadoutList() + control.controls.loadoutFilter.selIndex = isValueInArray(control.controls.loadoutFilter.list, "Tree {mapping}") + + control:UpdateList() + + assert.are.same({ 2, 4, 6 }, control.list) + + control.controls.loadoutFilter.selIndex = isValueInArray(control.controls.loadoutFilter.list, "[0.4] Boss") + control:UpdateList() + + assert.are.same({ 1, 3, 5 }, control.list) + end) + + it("clears hidden selections and preserves visible selections by item ID", function() + local control = newItemListControl() + control:UpdateLoadoutList() + control.selIndex = 2 + control.selValue = 2 + control.controls.loadoutFilter.selIndex = 2 + + control:UpdateList() + + assert.is_nil(control.selIndex) + assert.is_nil(control.selValue) + + control.selIndex = 3 + control.selValue = 3 + control:UpdateList() + + assert.are.equal(2, control.selIndex) + assert.are.equal(3, control.selValue) + end) + + it("only allows internal reordering in the unfiltered item list", function() + local control = newItemListControl() + control:UpdateLoadoutList() + control:UpdateList() + + assert.is_true(control.isMutable) + + control.controls.loadoutFilter.selIndex = 2 + control:UpdateList() + + assert.is_false(control.isMutable) + + control.controls.loadoutFilter.selIndex = 1 + control:UpdateList() + + assert.is_true(control.isMutable) + end) + + it("refreshes filter options when loadouts are renamed without a new output revision", function() + local control, itemsTab, treeTab = newItemListControl() + itemsTab.build.outputRevision = 1 + control.lastOutputRevision = 1 + control:UpdateLoadoutList() + itemsTab.itemSets[2].title = "Renamed" + treeTab.specList[2].title = "Renamed" + itemsTab.build.controls.buildLoadouts.list = { "Boss", "Renamed" } + wipeTable(itemsTab.itemOrderList) + wipeTable(itemsTab.items) + + control:Draw({ x = 0, y = 0, width = 1920, height = 1080 }) + + assert.is_nil(isValueInArray(control.controls.loadoutFilter.list, "Mapping")) + assert.is_not_nil(isValueInArray(control.controls.loadoutFilter.list, "Renamed")) + end) + + it("clears the canonical item order when deleting all from a filtered list", function() + local control, itemsTab = newItemListControl() + control:UpdateLoadoutList() + control.controls.loadoutFilter.selIndex = isValueInArray(control.controls.loadoutFilter.list, "Mapping") + control:UpdateList() + main.OpenConfirmPopup = function(_, _, _, _, onConfirm) + onConfirm() + end + + control.controls.deleteAll.onClick() + + assert.are.same({ }, itemsTab.itemOrderList) + assert.are.same({ }, itemsTab.items) + end) + + it("deletes unused items across the entire build while filtered", function() + local control, itemsTab = newItemListControl() + control:UpdateLoadoutList() + control.controls.loadoutFilter.selIndex = isValueInArray(control.controls.loadoutFilter.list, "Mapping") + control:UpdateList() + + control.controls.deleteUnused.onClick() + + assert.is_nil(itemsTab.items[7]) + assert.is_nil(isValueInArray(itemsTab.itemOrderList, 7)) + assert.are.same({ 2, 4, 6 }, control.list) + end) + + it("keeps bulk actions enabled when the filtered list is empty", function() + local control, itemsTab, treeTab = newItemListControl() + itemsTab.activeItemSet["Body Armour"].selItemId = 0 + itemsTab.activeItemSet[1000].selItemId = 0 + treeTab.specList[1].jewels = { } + control:UpdateLoadoutList() + control.controls.loadoutFilter.selIndex = 2 + + control:UpdateList() + + assert.are.same({ }, control.list) + assert.is_true(control.controls.deleteUnused.enabled()) + assert.is_true(control.controls.deleteAll.enabled()) end) it("releases focus after opening an item with a double click", function() diff --git a/src/Classes/ItemListControl.lua b/src/Classes/ItemListControl.lua index 9d5c8e6c33..9cd48b1957 100644 --- a/src/Classes/ItemListControl.lua +++ b/src/Classes/ItemListControl.lua @@ -4,6 +4,7 @@ -- Build item list control. -- local pairs = pairs +local ipairs = ipairs local t_insert = table.insert ---@class ItemListControl: ListControl @@ -16,16 +17,40 @@ local ItemListClass = newClass("ItemListControl", "ListControl") function ItemListClass:ItemListControl(anchor, rect, itemsTab, forceTooltip) self:ListControl(anchor, rect, 16, "VERTICAL", true, itemsTab.itemOrderList, forceTooltip) self.itemsTab = itemsTab - self.label = "^7All items:" self.defaultText = "^x7F7F7FThis is the list of items that have been added to this build.\nYou can add items to this list by dragging them from\none of the other lists, or by clicking 'Add to build' when\nviewing an item." self.dragTargetList = { } - self.controls.delete = new("ButtonControl"):ButtonControl({ "BOTTOMRIGHT", self, "TOPRIGHT" }, { 0, -2, 60, 18 }, "Delete", function() - self:OnSelDelete(self.selIndex, self.selValue) + self.controls.loadoutFilter = new("DropDownControl"):DropDownControl({ "BOTTOMLEFT", self, "TOPLEFT" }, { 0, -2, 110, 18 }, nil, function() + self:UpdateList() end) - self.controls.delete.enabled = function() - return self.selValue ~= nil + self.controls.loadoutFilter.enableDroppedWidth = true + self.controls.sort = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.loadoutFilter, "RIGHT" }, { 4, 0, 42, 18 }, "Sort", function() + itemsTab:SortItemList() + self:UpdateList() + end) + self.controls.deleteUnused = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.sort, "RIGHT" }, { 4, 0, 84, 18 }, "Del Unused", function() + local delList = {} + for _, itemId in ipairs(itemsTab.itemOrderList) do + if not itemsTab:GetEquippedSlotForItem(itemsTab.items[itemId]) and not self:FindEquippedItemSocket(itemId, false) and not self:FindSocketedJewel(itemId, false) then + t_insert(delList, itemId) + end + end + -- Delete in reverse order so as to not delete the wrong item whilst deleting + for i = #delList, 1, -1 do + itemsTab:DeleteItem(itemsTab.items[delList[i]], true) + end + -- Rebuild cluster jewel graphs, populate slots, and create an undo state, as we deferred doing this during itemsTab:DeleteItem(...) + for _, spec in pairs(itemsTab.build.treeTab.specList) do + spec:BuildClusterJewelGraphs() + end + itemsTab:PopulateSlots() + itemsTab:AddUndoState() + itemsTab.build.buildFlag = true + self:UpdateList() + end) + self.controls.deleteUnused.enabled = function() + return #itemsTab.itemOrderList > 0 end - self.controls.deleteAll = new("ButtonControl"):ButtonControl({ "RIGHT", self.controls.delete, "LEFT" }, { -4, 0, 70, 18 }, "Delete All", function() + self.controls.deleteAll = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.deleteUnused, "RIGHT" }, { 4, 0, 58, 18 }, "Del All", function() main:OpenConfirmPopup("Delete All", "Are you sure you want to delete all items in this build?", "Delete", function() for _, slot in pairs(itemsTab.slots) do slot:SetSelItemId(0) @@ -42,37 +67,138 @@ function ItemListClass:ItemListControl(anchor, rect, itemsTab, forceTooltip) itemsTab.build.buildFlag = true self.selIndex = nil self.selValue = nil + self:UpdateList() end) end) self.controls.deleteAll.enabled = function() - return #self.list > 0 + return #itemsTab.itemOrderList > 0 end - self.controls.deleteUnused = new("ButtonControl"):ButtonControl({ "RIGHT", self.controls.deleteAll, "LEFT" }, { -4, 0, 100, 18 }, "Delete Unused", function() - local delList = {} - for _, itemId in pairs(self.list) do - if not itemsTab:GetEquippedSlotForItem(itemsTab.items[itemId]) and not self:FindEquippedItemSocket(itemId, false) and not self:FindSocketedJewel(itemId, false) then - t_insert(delList, itemId) + self.controls.delete = new("ButtonControl"):ButtonControl({ "LEFT", self.controls.deleteAll, "RIGHT" }, { 4, 0, 50, 18 }, "Delete", function() + self:OnSelDelete(self.selIndex, self.selValue) + end) + self.controls.delete.enabled = function() + return self.selValue ~= nil + end + return self +end + +function ItemListClass:UpdateLoadoutList() + local list = { "Any Loadout", "Current Loadout", "Unused Items" } + local listValues = { ["Any Loadout"] = true, ["Current Loadout"] = true, ["Unused Items"] = true } + local build = self.itemsTab.build + if build and build.controls and build.controls.buildLoadouts then + for _, val in ipairs(build.controls.buildLoadouts.list) do + if val ~= "No Loadouts" and val ~= "^7^7Loadouts:" and val ~= "^7^7-----" and val ~= "^7^7Manage" and val ~= "^7^7Sync" and val ~= "^7^7Help >>" and not listValues[val] then + t_insert(list, val) + listValues[val] = true end end - -- Delete in reverse order so as to not delete the wrong item whilst deleting - for i = #delList, 1, -1 do - itemsTab:DeleteItem(itemsTab.items[delList[i]], true) + end + if self.itemsTab.itemSetOrderList then + for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do + local itemSet = self.itemsTab.itemSets[itemSetId] + local title = itemSet and (itemSet.title or "Default") + if title and not listValues[title] then + t_insert(list, title) + listValues[title] = true + end end - -- Rebuild cluster jewel graphs, populate slots, and create an undo state, as we deferred doing this during itemsTab:DeleteItem(...) - for _, spec in pairs(itemsTab.build.treeTab.specList) do - spec:BuildClusterJewelGraphs() + end + local listKey = table.concat(list, "\0") + if self.loadoutListKey == listKey then + return false + end + self.loadoutListKey = listKey + local selIndex = self.controls.loadoutFilter.selIndex or 1 + local selValue = self.controls.loadoutFilter.list and self.controls.loadoutFilter.list[selIndex] or "Any Loadout" + self.controls.loadoutFilter:SetList(list) + self.controls.loadoutFilter.selIndex = isValueInArray(list, selValue) or 1 + return true +end + +function ItemListClass:UpdateList() + self:UpdateLoadoutList() + local selFilter = self.controls.loadoutFilter.selIndex or 1 + local filterVal = self.controls.loadoutFilter.list[selFilter] or "Any Loadout" + local selectedItemId = self.selValue + + if selFilter == 1 or filterVal == "Any Loadout" then + self.list = self.itemsTab.itemOrderList + self.isMutable = true + else + self.isMutable = false + local filterItemSet + local filterSpec + if selFilter == 2 or filterVal == "Current Loadout" then + filterItemSet = self.itemsTab.activeItemSet + filterSpec = self.itemsTab.build.treeTab.specList[self.itemsTab.build.treeTab.activeSpec] + elseif selFilter ~= 3 and filterVal ~= "Unused Items" then + local filterTitle = filterVal:gsub("^%[[^%]]+%]%s*", "") + for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do + local itemSet = self.itemsTab.itemSets[itemSetId] + if (itemSet.title or "Default") == filterTitle then + filterItemSet = itemSet + break + end + end + local treeTab = self.itemsTab.build.treeTab + for _, spec in ipairs(treeTab.specList) do + if (spec.title or "Default") == filterTitle then + filterSpec = spec + break + end + end + local linkId = filterVal:match("{(%w+)}") + local itemLink = linkId and self.itemsTab.build.itemListSpecialLinks and self.itemsTab.build.itemListSpecialLinks[linkId] + local treeLink = linkId and self.itemsTab.build.treeListSpecialLinks and self.itemsTab.build.treeListSpecialLinks[linkId] + filterItemSet = filterItemSet or #self.itemsTab.itemSetOrderList == 1 and self.itemsTab.itemSets[self.itemsTab.itemSetOrderList[1]] or itemLink and self.itemsTab.itemSets[itemLink.setId] + filterSpec = filterSpec or #treeTab.specList == 1 and treeTab.specList[1] or treeLink and treeTab.specList[treeLink.setId] end - itemsTab:PopulateSlots() - itemsTab:AddUndoState() - itemsTab.build.buildFlag = true - end) - self.controls.deleteUnused.enabled = function() - return #self.list > 0 + filterItemSet = filterItemSet or { } + local newList = { } + for _, itemId in ipairs(self.itemsTab.itemOrderList) do + local item = self.itemsTab.items[itemId] + if item then + if selFilter == 3 or filterVal == "Unused Items" then + if not self.itemsTab:GetEquippedSlotForItem(item) and not self:FindEquippedItemSocket(itemId, false) and not self:FindSocketedJewel(itemId, false) then + t_insert(newList, itemId) + end + else + local inLoadout = false + for _, slot in pairs(filterItemSet) do + if type(slot) == "table" and slot.selItemId == itemId then + inLoadout = true + break + end + end + if not inLoadout and filterSpec then + for nodeId, jewelId in pairs(filterSpec.jewels) do + if jewelId == itemId and filterSpec.nodes[nodeId] and filterSpec.nodes[nodeId].alloc then + inLoadout = true + break + end + end + end + if inLoadout then + t_insert(newList, itemId) + end + end + end + end + self.list = newList end - self.controls.sort = new("ButtonControl"):ButtonControl({ "RIGHT", self.controls.deleteUnused, "LEFT" }, { -4, 0, 60, 18 }, "Sort", function() - itemsTab:SortItemList() - end) - return self + self.selIndex = selectedItemId and isValueInArray(self.list, selectedItemId) or nil + self.selValue = self.selIndex and self.list[self.selIndex] or nil +end + +function ItemListClass:Draw(viewPort) + local loadoutListChanged = self:UpdateLoadoutList() + local outputRevision = self.itemsTab.build and self.itemsTab.build.outputRevision + if loadoutListChanged or outputRevision ~= self.lastOutputRevision then + self.lastOutputRevision = outputRevision + self:UpdateList() + end + self.ListControl.Draw(self, viewPort) end function ItemListClass:FindSocketedJewel(jewelId, excludeActiveSpec) @@ -214,6 +340,7 @@ function ItemListClass:OnSelDelete(index, itemId) self.itemsTab:DeleteItem(item) self.selIndex = nil self.selValue = nil + self:UpdateList() end) else local equipSet = self:FindEquippedItemSocket(itemId, true) @@ -252,4 +379,4 @@ function ItemListClass:OnHoverKeyUp(key) itemLib.wiki.openItem(item) end end -end \ No newline at end of file +end diff --git a/src/Classes/ItemListControl.lua.rej b/src/Classes/ItemListControl.lua.rej deleted file mode 100644 index 88569d03b7..0000000000 --- a/src/Classes/ItemListControl.lua.rej +++ /dev/null @@ -1,194 +0,0 @@ -diff a/src/Classes/ItemListControl.lua b/src/Classes/ItemListControl.lua (rejected hunks) -@@ -22,7 +22,7 @@ local ItemListClass = newClass("ItemListControl", "ListControl", function(self, - end) - self.controls.deleteUnused = new("ButtonControl", {"LEFT",self.controls.sort,"RIGHT"}, {4, 0, 84, 18}, "Del Unused", function() - local delList = {} -- for _, itemId in pairs(self.list) do -+ for _, itemId in pairs(itemsTab.itemOrderList) do - if not itemsTab:GetEquippedSlotForItem(itemsTab.items[itemId]) and not self:FindEquippedAbyssJewel(itemId, false) and not self:FindSocketedJewel(itemId, false) then - t_insert(delList, itemId) - end -@@ -76,12 +76,14 @@ end) - - function ItemListClass:UpdateLoadoutList() - local list = { "Any Loadout", "Current Loadout", "Unused Items" } -+ local listValues = { ["Any Loadout"] = true, ["Current Loadout"] = true, ["Unused Items"] = true } - local build = self.itemsTab.build - if build and build.controls and build.controls.buildLoadouts then - for _, val in ipairs(build.controls.buildLoadouts.list) do - if val ~= "^7^7Loadouts:" and val ~= "^7^7-----" and val ~= "^7^7New Loadout" and val ~= "^7^7Sync" and val ~= "^7^7Help >>" then -- if not isValueInArray(list, val) then -+ if not listValues[val] then - t_insert(list, val) -+ listValues[val] = true - end - end - end -@@ -90,93 +92,104 @@ function ItemListClass:UpdateLoadoutList() - for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do - local itemSet = self.itemsTab.itemSets[itemSetId] - local title = itemSet and (itemSet.title or "Default") -- if title and not isValueInArray(list, title) then -+ if title and not listValues[title] then - t_insert(list, title) -+ listValues[title] = true - end - end - end -- local selIndex = self.controls.loadoutFilter.selIndex or 1 -- self.controls.loadoutFilter:SetList(list) -- self.controls.loadoutFilter.selIndex = math.min(selIndex, #list) --end -- --function ItemListClass:IsItemInLoadout(itemId, filterVal) -- local item = self.itemsTab.items[itemId] -- if not item then -+ local listKey = table.concat(list, "\0") -+ if self.loadoutListKey == listKey then - return false - end -- -- -- Check item sets -- if self.itemsTab.itemSetOrderList then -- for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do -- local itemSet = self.itemsTab.itemSets[itemSetId] -- if itemSet then -- local title = itemSet.title or "Default" -- if title == filterVal or title:find(filterVal, 1, true) or filterVal:find(title, 1, true) or #self.itemsTab.itemSetOrderList == 1 then -- local slot, equipSet = self.itemsTab:GetEquippedSlotForItem(item) -- if (slot and (not equipSet or equipSet == itemSet)) or self:FindEquippedAbyssJewel(itemId, false) == title then -- return true -- end -- end -- end -- end -- end -- -- -- Check passive tree specs -- local treeTab = self.itemsTab.build.treeTab -- if treeTab and treeTab.specList then -- for _, spec in ipairs(treeTab.specList) do -- local title = spec.title or "Default" -- if title == filterVal or title:find(filterVal, 1, true) or filterVal:find(title, 1, true) or #treeTab.specList == 1 then -- if self:FindSocketedJewel(itemId, false) == title then -- return true -- end -- end -- end -- end -- -- return false -+ self.loadoutListKey = listKey -+ local selIndex = self.controls.loadoutFilter.selIndex or 1 -+ local selValue = self.controls.loadoutFilter.list and self.controls.loadoutFilter.list[selIndex] or "Any Loadout" -+ self.controls.loadoutFilter:SetList(list) -+ self.controls.loadoutFilter.selIndex = isValueInArray(list, selValue) or 1 -+ return true - end - - function ItemListClass:UpdateList() - self:UpdateLoadoutList() - local selFilter = self.controls.loadoutFilter.selIndex or 1 - local filterVal = self.controls.loadoutFilter.list[selFilter] or "Any Loadout" -+ local selectedItemId = self.selValue - - if selFilter == 1 or filterVal == "Any Loadout" then - self.list = self.itemsTab.itemOrderList -- return -- end -- -- local newList = {} -- for _, itemId in ipairs(self.itemsTab.itemOrderList) do -- local item = self.itemsTab.items[itemId] -- if item then -- if selFilter == 2 or filterVal == "Current Loadout" then -- if self.itemsTab:GetEquippedSlotForItem(item) or self:FindEquippedAbyssJewel(itemId, false) or self:FindSocketedJewel(itemId, false) then -- t_insert(newList, itemId) -+ self.isMutable = true -+ else -+ self.isMutable = false -+ local filterItemSet -+ local filterSpec -+ if selFilter == 2 or filterVal == "Current Loadout" then -+ filterItemSet = self.itemsTab.activeItemSet -+ filterSpec = self.itemsTab.build.treeTab.specList[self.itemsTab.build.treeTab.activeSpec] -+ elseif selFilter ~= 3 and filterVal ~= "Unused Items" then -+ local filterTitle = filterVal:gsub("^%[[^%]]+%]%s*", "") -+ for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do -+ local itemSet = self.itemsTab.itemSets[itemSetId] -+ if (itemSet.title or "Default") == filterTitle then -+ filterItemSet = itemSet -+ break - end -- elseif selFilter == 3 or filterVal == "Unused Items" then -- if not self.itemsTab:GetEquippedSlotForItem(item) and not self:FindEquippedAbyssJewel(itemId, false) and not self:FindSocketedJewel(itemId, false) then -- t_insert(newList, itemId) -+ end -+ local treeTab = self.itemsTab.build.treeTab -+ for _, spec in ipairs(treeTab.specList) do -+ if (spec.title or "Default") == filterTitle then -+ filterSpec = spec -+ break - end -- else -- if self:IsItemInLoadout(itemId, filterVal) then -- t_insert(newList, itemId) -+ end -+ local linkId = filterVal:match("%{(%w+)%}") -+ local itemLink = linkId and self.itemsTab.build.itemListSpecialLinks and self.itemsTab.build.itemListSpecialLinks[linkId] -+ local treeLink = linkId and self.itemsTab.build.treeListSpecialLinks and self.itemsTab.build.treeListSpecialLinks[linkId] -+ filterItemSet = filterItemSet or #self.itemsTab.itemSetOrderList == 1 and self.itemsTab.itemSets[self.itemsTab.itemSetOrderList[1]] or itemLink and self.itemsTab.itemSets[itemLink.setId] -+ filterSpec = filterSpec or #treeTab.specList == 1 and treeTab.specList[1] or treeLink and treeTab.specList[treeLink.setId] -+ end -+ filterItemSet = filterItemSet or { } -+ local newList = {} -+ for _, itemId in ipairs(self.itemsTab.itemOrderList) do -+ local item = self.itemsTab.items[itemId] -+ if item then -+ if selFilter == 3 or filterVal == "Unused Items" then -+ if not self.itemsTab:GetEquippedSlotForItem(item) and not self:FindEquippedAbyssJewel(itemId, false) and not self:FindSocketedJewel(itemId, false) then -+ t_insert(newList, itemId) -+ end -+ else -+ local inLoadout = false -+ for _, slot in pairs(filterItemSet) do -+ if type(slot) == "table" and slot.selItemId == itemId then -+ inLoadout = true -+ break -+ end -+ end -+ if not inLoadout and filterSpec then -+ for nodeId, jewelId in pairs(filterSpec.jewels) do -+ if jewelId == itemId and filterSpec.nodes[nodeId] and filterSpec.nodes[nodeId].alloc then -+ inLoadout = true -+ break -+ end -+ end -+ end -+ if inLoadout then -+ t_insert(newList, itemId) -+ end - end - end - end -+ self.list = newList - end -- self.list = newList -- if self.selIndex and self.selIndex > #self.list then -- self.selIndex = #self.list > 0 and #self.list or nil -- self.selValue = self.selIndex and self.list[self.selIndex] or nil -- end -+ self.selIndex = selectedItemId and isValueInArray(self.list, selectedItemId) or nil -+ self.selValue = self.selIndex and self.list[self.selIndex] or nil - end - - function ItemListClass:Draw(viewPort) -- if self.itemsTab.build and self.itemsTab.build.outputRevision ~= self.lastOutputRevision then -- self.lastOutputRevision = self.itemsTab.build.outputRevision -+ local loadoutListChanged = self:UpdateLoadoutList() -+ local outputRevision = self.itemsTab.build and self.itemsTab.build.outputRevision -+ if loadoutListChanged or outputRevision ~= self.lastOutputRevision then -+ self.lastOutputRevision = outputRevision - self:UpdateList() - end - self.ListControl.Draw(self, viewPort)