diff --git a/spec/System/TestItemDBControl_spec.lua b/spec/System/TestItemDBControl_spec.lua index 98d4605c4a..9e7efd1861 100644 --- a/spec/System/TestItemDBControl_spec.lua +++ b/spec/System/TestItemDBControl_spec.lua @@ -1,12 +1,15 @@ describe("ItemDBControl", function() local originalGetCursorPos + local originalIsKeyDown before_each(function() originalGetCursorPos = GetCursorPos + originalIsKeyDown = _G.IsKeyDown end) after_each(function() GetCursorPos = originalGetCursorPos + _G.IsKeyDown = originalIsKeyDown end) it("sorts lower-is-better stats below zero", function() @@ -114,4 +117,63 @@ describe("ItemDBControl", function() assert.are.equal(item.raw, itemsTab.displayRaw) assert.is_true(itemsTab.displayIsUnique) end) + + it("resizes its filter controls with the item list", function() + local width = 360 + local control = new("ItemDBControl"):ItemDBControl(nil, { 0, 0, function() return width end, 100 }, { + build = { outputRevision = 1 }, + }, { + list = { }, + }, "RARE") + local viewPort = { x = 0, y = 0, width = 1920, height = 1080 } + + control:Draw(viewPort) + assert.equal(179, control.controls.slot.width) + assert.equal(258, control.controls.search.width) + + width = 450 + control:Draw(viewPort) + assert.equal(224, control.controls.slot.width) + assert.equal(348, control.controls.search.width) + end) + + it("selects a newly added item after equipping it", function() + local equipped + local counterpartAdded + local slotsPopulated + local selectedItemId + local item = { raw = "Rarity: Normal\nPlate Vest" } + local itemsTab = { + activeItemSet = { useSecondWeaponSet = false }, + slots = { + ["Body Armour"] = { + weaponSet = 0, + SetSelItemId = function() equipped = true end, + }, + }, + controls = { + itemList = { + SelectItem = function(_, itemId) + assert.is_true(equipped) + assert.is_true(counterpartAdded) + assert.is_true(slotsPopulated) + selectedItemId = itemId + end, + }, + }, + build = { }, + AddItem = function(_, newItem) + newItem.id = 1 + end, + AddForbiddenJewelCounterpart = function() counterpartAdded = true end, + PopulateSlots = function() slotsPopulated = true end, + AddUndoState = function() end, + } + local control = new("ItemDBControl"):ItemDBControl(nil, { 0, 0, 100, 100 }, itemsTab, { list = { item } }, "RARE") + _G.IsKeyDown = function(key) return key == "CTRL" end + + control:OnSelClick(1, item, false) + + assert.equal(1, selectedItemId) + end) end) diff --git a/spec/System/TestItemListControl_spec.lua b/spec/System/TestItemListControl_spec.lua index 2d690a6eb8..5f2315ee15 100644 --- a/spec/System/TestItemListControl_spec.lua +++ b/spec/System/TestItemListControl_spec.lua @@ -1,8 +1,21 @@ describe("ItemListControl", function() local originalOpenConfirmPopup local originalGetCursorPos + local originalPortraitMode local function newItemListControl() + local function newItem(id, name, type, primarySlot, rarity) + return { + id = id, + name = name, + type = type, + rarity = rarity or "NORMAL", + base = { subType = "" }, + GetPrimarySlot = function() + return primarySlot + end, + } + end local activeItemSet = { id = 1, title = "Boss", @@ -30,11 +43,12 @@ describe("ItemListControl", function() } local itemsTab = { itemOrderList = { 1, 2, 3, 4 }, + slotOrder = { ["Body Armour"] = 1, Jewel = 2 }, items = { - [1] = { id = 1, type = "Body Armour", base = { subType = "" } }, - [2] = { id = 2, type = "Body Armour", base = { subType = "" } }, - [3] = { id = 3, type = "Jewel", base = { subType = "" } }, - [4] = { id = 4, type = "Jewel", base = { subType = "" } }, + [1] = newItem(1, "Armour One", "Body Armour", "Body Armour"), + [2] = newItem(2, "Armour Two", "Body Armour", "Body Armour"), + [3] = newItem(3, "Jewel One", "Jewel", "Jewel"), + [4] = newItem(4, "Jewel Two", "Jewel", "Jewel"), }, itemSetOrderList = { 1, 2 }, itemSets = { activeItemSet, otherItemSet }, @@ -51,6 +65,13 @@ describe("ItemListControl", function() }, PopulateSlots = function() end, AddUndoState = function() end, + GetEquippedSlotForItem = function(_, item) + if item.id == 1 then + return { slotName = "Body Armour" } + elseif item.id == 2 then + return { slotName = "Body Armour" }, otherItemSet + end + end, } local control = new("ItemListControl"):ItemListControl(nil, { 0, 0, 360, 308 }, itemsTab, true) return control, itemsTab, treeTab @@ -59,11 +80,13 @@ describe("ItemListControl", function() before_each(function() originalOpenConfirmPopup = main.OpenConfirmPopup originalGetCursorPos = GetCursorPos + originalPortraitMode = main.portraitMode end) after_each(function() main.OpenConfirmPopup = originalOpenConfirmPopup GetCursorPos = originalGetCursorPos + main.portraitMode = originalPortraitMode end) it("only shows items from the active item set and passive tree", function() @@ -126,12 +149,15 @@ describe("ItemListControl", function() assert.are.equal(3, control.selValue) end) - it("only allows internal reordering in the unfiltered item list", function() + it("uses the mutable custom order by default", function() local control = newItemListControl() control:UpdateLoadoutList() control:UpdateList() + assert.equal("Sort by Custom Order", control.controls.sortMode:GetSelValue()) assert.is_true(control.isMutable) + assert.is_true(rawequal(control.list, control.itemsTab.itemOrderList)) + assert.are.same({ 1, 2, 3, 4 }, control.itemsTab.itemOrderList) control.controls.loadoutFilter.selIndex = 2 control:UpdateList() @@ -142,6 +168,204 @@ describe("ItemListControl", function() control:UpdateList() assert.is_true(control.isMutable) + assert.is_true(rawequal(control.list, control.itemsTab.itemOrderList)) + assert.are.same({ 1, 2, 3, 4 }, control.itemsTab.itemOrderList) + end) + + it("keeps sorted views separate from the custom order", function() + local control, itemsTab = newItemListControl() + itemsTab.items[1].name = "D Item" + itemsTab.items[2].name = "A Item" + itemsTab.items[3].name = "B Item" + itemsTab.items[4].name = "C Item" + control.controls.sortMode:SelByValue("Sort by Name") + + control:UpdateList() + + assert.is_false(control.isMutable) + assert.are.same({ 2, 3, 4, 1 }, control.list) + assert.are.same({ 1, 2, 3, 4 }, control.itemsTab.itemOrderList) + end) + + it("appends drops from sorted views to the custom order", function() + local control, itemsTab = newItemListControl() + local insertionIndex + itemsTab.AddItem = function(self, item, _, index) + insertionIndex = index + item.id = 5 + self.items[item.id] = item + table.insert(self.itemOrderList, index or #self.itemOrderList + 1, item.id) + end + itemsTab.AddForbiddenJewelCounterpart = function() end + control.controls.sortMode:SelByValue("Sort by Name") + control:UpdateList() + control.selDragIndex = 2 + + control:ReceiveDrag("Item", { raw = "Rarity: Normal\nPlate Vest" }) + + assert.is_nil(insertionIndex) + assert.are.same({ 1, 2, 3, 4, 5 }, itemsTab.itemOrderList) + end) + + it("filters by primary item slot and item name", function() + local control, itemsTab = newItemListControl() + itemsTab.items[1].name = "Matching Body Armour" + itemsTab.items[2].name = "Other Body Armour" + itemsTab.items[3].name = "Matching Jewel" + control.controls.slotFilter.selIndex = isValueInArray(control.controls.slotFilter.list, "Body Armour") + control.controls.search.buf = "matching" + + control:UpdateList() + + assert.are.same({ 1 }, control.list) + end) + + it("sorts by item-slot order and then by name", function() + local control, itemsTab = newItemListControl() + itemsTab.items[1].name = "Later Body Armour" + itemsTab.items[2].name = "Earlier Body Armour" + itemsTab.items[3].name = "Later Jewel" + itemsTab.items[4].name = "Earlier Jewel" + control.controls.sortMode:SelByValue("Sort by Item Slot") + + control:UpdateList() + + assert.are.same({ 2, 1, 4, 3 }, control.list) + end) + + it("sorts unique, rare, magic, and normal items in rarity order", function() + local control, itemsTab = newItemListControl() + itemsTab.items[1].rarity = "MAGIC" + itemsTab.items[2].rarity = "NORMAL" + itemsTab.items[3].rarity = "UNIQUE" + itemsTab.items[4].rarity = "RARE" + control.controls.sortMode:SelByValue("Sort by Rarity") + + control:UpdateList() + + assert.are.same({ 3, 4, 1, 2 }, control.list) + end) + + it("sorts loadout groups in menu order while preferring the current loadout", function() + local control, itemsTab = newItemListControl() + itemsTab.items[1].name = "Later Boss Armour" + itemsTab.items[2].name = "Mapping Armour" + itemsTab.items[3].name = "Boss Jewel" + itemsTab.items[4].name = "Mapping Jewel" + itemsTab.itemSets[2].Gloves = { selItemId = 1 } + itemsTab.build.controls.buildLoadouts.list = { "Mapping", "Boss" } + control.controls.sortMode:SelByValue("Sort by Loadout") + + control:UpdateList() + + assert.are.same({ + { groupHeader = "Mapping" }, 2, 4, + { groupHeader = "Boss" }, 3, 1, + }, control.list) + end) + + it("does not label items used outside complete loadouts as unused", function() + local control, itemsTab, treeTab = newItemListControl() + itemsTab.items[5] = { + id = 5, + name = "Experimental Jewel", + type = "Jewel", + rarity = "RARE", + base = { subType = "" }, + GetPrimarySlot = function() return "Jewel" end, + } + itemsTab.items[6] = { + id = 6, + name = "Unused Jewel", + type = "Jewel", + rarity = "RARE", + base = { subType = "" }, + GetPrimarySlot = function() return "Jewel" end, + } + table.insert(itemsTab.itemOrderList, 5) + table.insert(itemsTab.itemOrderList, 6) + table.insert(treeTab.specList, { + title = "Experimental", + jewels = { [300] = 5 }, + nodes = { [300] = { alloc = true } }, + }) + control.controls.sortMode:SelByValue("Sort by Loadout") + + control:UpdateList() + + assert.are.same({ + { groupHeader = "Boss" }, 1, 3, + { groupHeader = "Mapping" }, 2, 4, + { groupHeader = "Other Used Items" }, 5, + { groupHeader = "Unused Items" }, 6, + }, control.list) + end) + + it("does not allow group headers to become selected", function() + local control = newItemListControl() + control.controls.sortMode:SelByValue("Sort by Loadout") + control:UpdateList() + + assert.is_false(control:SelectIndex(1)) + assert.is_nil(control.selIndex) + assert.is_nil(control.selValue) + end) + + it("shows slot icons for items but not loadout group headers", function() + local control = newItemListControl() + + assert.is_not_nil(control:GetRowIcon(1, 1, 1)) + assert.is_nil(control:GetRowIcon(1, 1, { groupHeader = "Boss" })) + end) + + it("clears filters that hide a selected item", function() + local control = newItemListControl() + control.controls.slotFilter.selIndex = 4 + control.controls.loadoutFilter.selIndex = 2 + control.controls.search.buf = "missing" + control.controls.sortMode:SelByValue("Sort by Item Slot") + + control:SelectItem(4) + + assert.equal(1, control.controls.slotFilter.selIndex) + assert.equal(1, control.controls.loadoutFilter.selIndex) + assert.equal("", control.controls.search.buf) + assert.equal(4, control.selValue) + assert.equal(4, control.selIndex) + end) + + it("skips group headers during keyboard navigation", function() + local control = newItemListControl() + control.controls.sortMode:SelByValue("Sort by Loadout") + control:UpdateList() + + control:OnKeyDown("HOME") + assert.equal(1, control.selValue) + control:OnKeyDown("DOWN") + assert.equal(3, control.selValue) + control:OnKeyDown("DOWN") + assert.equal(2, control.selValue) + control:OnKeyDown("UP") + assert.equal(3, control.selValue) + control:OnKeyDown("END") + assert.equal(4, control.selValue) + end) + + it("resizes list controls when the orientation changes", function() + local control = newItemListControl() + control.width = function() return main.portraitMode and 360 or 450 end + local viewPort = { x = 0, y = 0, width = 1920, height = 1080 } + + main.portraitMode = false + control:Draw(viewPort) + assert.equal((450 - 8) / 3, control.controls.slotFilter.width) + assert.equal(450, control.controls.search.width) + + main.portraitMode = true + control:Draw(viewPort) + assert.equal((360 - 8) / 3, control.controls.slotFilter.width) + assert.equal(360, control.controls.search.width) + assert.equal(-44, control.controls.deleteUnused.y) end) it("refreshes filter options when loadouts are renamed without a new output revision", function() diff --git a/src/Assets/icon_flask.png b/src/Assets/icon_flask.png new file mode 100644 index 0000000000..18668370ca Binary files /dev/null and b/src/Assets/icon_flask.png differ diff --git a/src/Assets/icon_jewel.png b/src/Assets/icon_jewel.png new file mode 100644 index 0000000000..9cbe7a35b5 Binary files /dev/null and b/src/Assets/icon_jewel.png differ diff --git a/src/Classes/ItemDBControl.lua b/src/Classes/ItemDBControl.lua index 37b3ecee3d..742094755f 100644 --- a/src/Classes/ItemDBControl.lua +++ b/src/Classes/ItemDBControl.lua @@ -8,6 +8,7 @@ local ipairs = ipairs local t_insert = table.insert local m_max = math.max local m_floor = math.floor +local itemSlotIcons = require("Modules.ItemSlotIcons") ---@class ItemDBControl: ListControl @@ -31,27 +32,29 @@ function ItemDBClass:ItemDBControl(anchor, rect, itemsTab, db, dbType) self.typeList = { "Any type", "Armour", "Jewellery", "One Handed Melee", "Two Handed Melee" } self.slotList = { "Any slot", "Weapon 1", "Weapon 2", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring", "Belt", "Jewel", "Flask", "Graft 1", "Graft 2" } local baseY = dbType == "RARE" and -22 or -62 - self.controls.slot = new("DropDownControl"):DropDownControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, baseY, 179, 18}, self.slotList, function(index, value) + local width = self:GetProperty("width") + local filterWidth = (width - 2) / 2 + self.controls.slot = new("DropDownControl"):DropDownControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, baseY, filterWidth, 18}, self.slotList, function(index, value) self.listBuildFlag = true end) - self.controls.type = new("DropDownControl"):DropDownControl({"LEFT",self.controls.slot,"RIGHT"}, {2, 0, 179, 18}, self.typeList, function(index, value) + self.controls.type = new("DropDownControl"):DropDownControl({"LEFT",self.controls.slot,"RIGHT"}, {2, 0, filterWidth, 18}, self.typeList, function(index, value) self.listBuildFlag = true end) if dbType == "UNIQUE" then - self.controls.sort = new("DropDownControl"):DropDownControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, baseY + 20, 179, 18}, self.sortDropList, function(index, value) + self.controls.sort = new("DropDownControl"):DropDownControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, baseY + 20, filterWidth, 18}, self.sortDropList, function(index, value) self:SetSortMode(value.sortMode) end) - self.controls.league = new("DropDownControl"):DropDownControl({"LEFT",self.controls.sort,"RIGHT"}, {2, 0, 179, 18}, self.leagueList, function(index, value) + self.controls.league = new("DropDownControl"):DropDownControl({"LEFT",self.controls.sort,"RIGHT"}, {2, 0, filterWidth, 18}, self.leagueList, function(index, value) self.listBuildFlag = true end) - self.controls.requirement = new("DropDownControl"):DropDownControl({"LEFT",self.controls.sort,"BOTTOMLEFT"}, {0, 11, 179, 18}, { "Any requirements", "Current level", "Current attributes", "Current useable" }, function(index, value) + self.controls.requirement = new("DropDownControl"):DropDownControl({"LEFT",self.controls.sort,"BOTTOMLEFT"}, {0, 11, filterWidth, 18}, { "Any requirements", "Current level", "Current attributes", "Current useable" }, function(index, value) self.listBuildFlag = true end) - self.controls.obtainable = new("DropDownControl"):DropDownControl({"LEFT",self.controls.requirement,"RIGHT"}, {2, 0, 179, 18}, { "Obtainable", "Any source", "Unobtainable", "Vendor Recipe", "Upgraded", "Boss Item", "Corruption", "Core Drop Pool"}, function(index, value) + self.controls.obtainable = new("DropDownControl"):DropDownControl({"LEFT",self.controls.requirement,"RIGHT"}, {2, 0, filterWidth, 18}, { "Obtainable", "Any source", "Unobtainable", "Vendor Recipe", "Upgraded", "Boss Item", "Corruption", "Core Drop Pool"}, function(index, value) self.listBuildFlag = true end) end - self.controls.search = new("EditControl"):EditControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, -2, 258, 18}, "", "Search", "%c", 100, function() + self.controls.search = new("EditControl"):EditControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, -2, m_max(width - 102, 0), 18}, "", "Search", "%c", 100, function() self.listBuildFlag = true end, nil, nil, true) self.controls.searchMode = new("DropDownControl"):DropDownControl({"LEFT",self.controls.search,"RIGHT"}, {2, 0, 100, 18}, { "Anywhere", "Names", "Modifiers" }, function(index, value) @@ -291,6 +294,28 @@ function ItemDBClass:ListBuilder() end function ItemDBClass:Draw(viewPort) + local width = self:GetProperty("width") + local filterWidth = (width - 2) / 2 + local widthChanged = self.controls.slot.width ~= filterWidth + self.controls.slot.width = filterWidth + self.controls.type.width = filterWidth + if self.dbType == "UNIQUE" then + self.controls.sort.width = filterWidth + self.controls.league.width = filterWidth + self.controls.requirement.width = filterWidth + self.controls.obtainable.width = filterWidth + end + self.controls.search.width = m_max(width - 102, 0) + if widthChanged then + self.controls.slot:CheckDroppedWidth(false) + self.controls.type:CheckDroppedWidth(false) + if self.dbType == "UNIQUE" then + self.controls.sort:CheckDroppedWidth(false) + self.controls.league:CheckDroppedWidth(false) + self.controls.requirement:CheckDroppedWidth(false) + self.controls.obtainable:CheckDroppedWidth(false) + end + end if self.itemsTab.build.outputRevision ~= self.listOutputRevision then self.listBuildFlag = true end @@ -323,6 +348,10 @@ function ItemDBClass:GetRowValue(column, index, item) end end +function ItemDBClass:GetRowIcon(column, index, item) + return itemSlotIcons.Get(item:GetPrimarySlot()) +end + function ItemDBClass:AddValueTooltip(tooltip, index, item) if main.popups[1] then tooltip:Clear() @@ -364,6 +393,7 @@ function ItemDBClass:OnSelClick(index, item, doubleClick) self.itemsTab:AddForbiddenJewelCounterpart(newItem) self.itemsTab:PopulateSlots() + self.itemsTab.controls.itemList:SelectItem(newItem.id) self.itemsTab:AddUndoState() self.itemsTab.build.buildFlag = true elseif doubleClick then diff --git a/src/Classes/ItemListControl.lua b/src/Classes/ItemListControl.lua index ee50d6371b..dfd25088ad 100644 --- a/src/Classes/ItemListControl.lua +++ b/src/Classes/ItemListControl.lua @@ -6,6 +6,19 @@ local pairs = pairs local ipairs = ipairs local t_insert = table.insert +local t_sort = table.sort +local itemSlotIcons = require("Modules.ItemSlotIcons") + +local slotFilterList = { "Any Slot", "Weapon 1", "Weapon 2", "Helmet", "Body Armour", "Gloves", "Boots", "Amulet", "Ring", "Belt", "Graft", "Flask", "Jewel" } +local raritySortOrder = { UNIQUE = 1, RELIC = 1, RARE = 2, MAGIC = 3, NORMAL = 4 } + +local function isGroupHeader(value) + return type(value) == "table" and value.groupHeader +end + +local function getItemName(item) + return (item.name or item.title or ""):lower() +end ---@class ItemListControl: ListControl local ItemListClass = newClass("ItemListControl", "ListControl") @@ -15,15 +28,22 @@ function ItemListClass:ItemListControl(anchor, rect, itemsTab, forceTooltip) self.itemsTab = itemsTab 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.loadoutFilter = new("DropDownControl"):DropDownControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, -2, 110, 18}, nil, function() + local width = self:GetProperty("width") + local rowControlWidth = (width - 8) / 3 + self.controls.slotFilter = new("DropDownControl"):DropDownControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, -22, rowControlWidth, 18}, slotFilterList, function() self:UpdateList() end) - 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.controls.sortMode = new("DropDownControl"):DropDownControl({"LEFT",self.controls.slotFilter,"RIGHT"}, {4, 0, rowControlWidth, 18}, { "Sort by Custom Order", "Sort by Item Slot", "Sort by Name", "Sort by Rarity", "Sort by Loadout" }, function() self:UpdateList() end) - self.controls.deleteUnused = new("ButtonControl"):ButtonControl({"LEFT",self.controls.sort,"RIGHT"}, {4, 0, 84, 18}, "Del Unused", function() + self.controls.loadoutFilter = new("DropDownControl"):DropDownControl({"LEFT",self.controls.sortMode,"RIGHT"}, {4, 0, rowControlWidth, 18}, nil, function() + self:UpdateList() + end) + self.controls.loadoutFilter.enableDroppedWidth = true + self.controls.search = new("EditControl"):EditControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, -2, width, 18}, "", "Search", "%c", 100, function() + self:UpdateList() + end, nil, nil, true) + self.controls.deleteUnused = new("ButtonControl"):ButtonControl({"BOTTOMLEFT",self,"TOPLEFT"}, {0, -50, rowControlWidth, 20}, "Delete Unused", function() local delList = {} 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 @@ -44,9 +64,9 @@ function ItemListClass:ItemListControl(anchor, rect, itemsTab, forceTooltip) self:UpdateList() end) self.controls.deleteUnused.enabled = function() - return #self.list > 0 + return #itemsTab.itemOrderList > 0 end - self.controls.deleteAll = new("ButtonControl"):ButtonControl({"LEFT",self.controls.deleteUnused,"RIGHT"}, {4, 0, 58, 18}, "Del All", function() + self.controls.deleteAll = new("ButtonControl"):ButtonControl({"LEFT",self.controls.deleteUnused,"RIGHT"}, {4, 0, rowControlWidth, 20}, "Delete 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) @@ -67,13 +87,13 @@ function ItemListClass:ItemListControl(anchor, rect, itemsTab, forceTooltip) end) end) self.controls.deleteAll.enabled = function() - return #self.list > 0 + return #itemsTab.itemOrderList > 0 end - self.controls.delete = new("ButtonControl"):ButtonControl({"LEFT",self.controls.deleteAll,"RIGHT"}, {4, 0, 50, 18}, "Delete", function() + self.controls.delete = new("ButtonControl"):ButtonControl({"LEFT",self.controls.deleteAll,"RIGHT"}, {4, 0, rowControlWidth, 20}, "Delete", function() self:OnSelDelete(self.selIndex, self.selValue) end) self.controls.delete.enabled = function() - return self.selValue ~= nil + return type(self.selValue) == "number" end return self end @@ -84,7 +104,7 @@ function ItemListClass:UpdateLoadoutList() 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 val ~= "No Loadouts" and val ~= "^7^7Loadouts:" and val ~= "^7^7-----" and val ~= "^7^7New Loadout" and val ~= "^7^7Sync" and val ~= "^7^7Help >>" then if not listValues[val] then t_insert(list, val) listValues[val] = true @@ -114,82 +134,208 @@ function ItemListClass:UpdateLoadoutList() 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 +function ItemListClass:GetLoadoutSetAndSpec(loadoutName) + local itemSet + local spec + local filterTitle = loadoutName:gsub("^%[[^%]]+%]%s*", "") + for _, itemSetId in ipairs(self.itemsTab.itemSetOrderList) do + local candidate = self.itemsTab.itemSets[itemSetId] + if (candidate.title or "Default") == filterTitle then + itemSet = candidate + break + end + end + local treeTab = self.itemsTab.build.treeTab + for _, candidate in ipairs(treeTab.specList) do + if (candidate.title or "Default") == filterTitle then + spec = candidate + break + end + end + local linkId = loadoutName: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] + itemSet = itemSet or #self.itemsTab.itemSetOrderList == 1 and self.itemsTab.itemSets[self.itemsTab.itemSetOrderList[1]] or itemLink and self.itemsTab.itemSets[itemLink.setId] + spec = spec or #treeTab.specList == 1 and treeTab.specList[1] or treeLink and treeTab.specList[treeLink.setId] + return itemSet or { }, spec +end - 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 +function ItemListClass:IsItemInLoadout(itemId, itemSet, spec) + for _, slot in pairs(itemSet) do + if type(slot) == "table" and slot.selItemId == itemId then + return true + end + end + if spec and spec.jewels then + for nodeId, jewelId in pairs(spec.jewels) do + if jewelId == itemId and spec.nodes[nodeId] and spec.nodes[nodeId].alloc then + return true end - local treeTab = self.itemsTab.build.treeTab - for _, spec in ipairs(treeTab.specList) do - if (spec.title or "Default") == filterTitle then - filterSpec = spec + end + end + return false +end + +function ItemListClass:SortItems(itemList, canonicalOrder, sortMode) + t_sort(itemList, function(a, b) + local itemA = self.itemsTab.items[a] + local itemB = self.itemsTab.items[b] + if sortMode == "Sort by Item Slot" then + local orderA = self.itemsTab.slotOrder[itemA:GetPrimarySlot()] or math.huge + local orderB = self.itemsTab.slotOrder[itemB:GetPrimarySlot()] or math.huge + if orderA ~= orderB then + return orderA < orderB + end + elseif sortMode == "Sort by Rarity" then + local orderA = raritySortOrder[itemA.rarity] or math.huge + local orderB = raritySortOrder[itemB.rarity] or math.huge + if orderA ~= orderB then + return orderA < orderB + end + end + local nameA = getItemName(itemA) + local nameB = getItemName(itemB) + return nameA == nameB and canonicalOrder[a] < canonicalOrder[b] or nameA < nameB + end) +end + +function ItemListClass:BuildLoadoutSort(itemList, canonicalOrder) + local groups = { } + local currentGroup + local currentSpec = self.itemsTab.build.treeTab.specList[self.itemsTab.build.treeTab.activeSpec] + for index = 4, #self.controls.loadoutFilter.list do + local loadoutName = self.controls.loadoutFilter.list[index] + local itemSet, spec = self:GetLoadoutSetAndSpec(loadoutName) + local group = { label = loadoutName, itemSet = itemSet, spec = spec, items = { } } + t_insert(groups, group) + if itemSet == self.itemsTab.activeItemSet and spec == currentSpec then + currentGroup = group + end + end + local otherUsed = { } + local unused = { } + for _, itemId in ipairs(itemList) do + local assigned + if currentGroup and self:IsItemInLoadout(itemId, currentGroup.itemSet, currentGroup.spec) then + t_insert(currentGroup.items, itemId) + assigned = true + else + for _, group in ipairs(groups) do + if self:IsItemInLoadout(itemId, group.itemSet, group.spec) then + t_insert(group.items, itemId) + assigned = true 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 - filterItemSet = filterItemSet or { } - local newList = {} - for _, itemId in ipairs(self.itemsTab.itemOrderList) do + if not assigned then 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 + if not self.itemsTab:GetEquippedSlotForItem(item) and not self:FindEquippedAbyssJewel(itemId, false) and not self:FindSocketedJewel(itemId, false) then + t_insert(unused, itemId) + else + t_insert(otherUsed, itemId) end end - self.list = newList + end + local list = { } + local function addGroup(label, items) + if #items > 0 then + self:SortItems(items, canonicalOrder, "Sort by Name") + t_insert(list, { groupHeader = label }) + for _, itemId in ipairs(items) do + t_insert(list, itemId) + end + end + end + for _, group in ipairs(groups) do + addGroup(group.label, group.items) + end + addGroup("Other Used Items", otherUsed) + addGroup("Unused Items", unused) + return list +end + +function ItemListClass:UpdateList() + self:UpdateLoadoutList() + local loadoutFilterIndex = self.controls.loadoutFilter.selIndex or 1 + local loadoutFilter = self.controls.loadoutFilter.list[loadoutFilterIndex] or "Any Loadout" + local slotFilter = self.controls.slotFilter.list[self.controls.slotFilter.selIndex or 1] or "Any Slot" + local searchText = self.controls.search.buf:lower() + local selectedItemId = type(self.selValue) == "number" and self.selValue + local filterItemSet + local filterSpec + if loadoutFilterIndex == 2 then + filterItemSet = self.itemsTab.activeItemSet + filterSpec = self.itemsTab.build.treeTab.specList[self.itemsTab.build.treeTab.activeSpec] + elseif loadoutFilterIndex > 3 then + filterItemSet, filterSpec = self:GetLoadoutSetAndSpec(loadoutFilter) + end + local itemList = { } + local canonicalOrder = { } + for index, itemId in ipairs(self.itemsTab.itemOrderList) do + canonicalOrder[itemId] = index + local item = self.itemsTab.items[itemId] + if item then + local matchesLoadout = loadoutFilterIndex == 1 + or loadoutFilterIndex == 3 and not self.itemsTab:GetEquippedSlotForItem(item) and not self:FindEquippedAbyssJewel(itemId, false) and not self:FindSocketedJewel(itemId, false) + or filterItemSet and self:IsItemInLoadout(itemId, filterItemSet, filterSpec) + local primarySlot = item:GetPrimarySlot() + local matchesSlot = slotFilter == "Any Slot" or primarySlot == slotFilter or primarySlot:gsub(" %d$", "") == slotFilter + local matchesSearch = searchText == "" or getItemName(item):find(searchText, 1, true) + if matchesLoadout and matchesSlot and matchesSearch then + t_insert(itemList, itemId) + end + end + end + local sortMode = self.controls.sortMode.list[self.controls.sortMode.selIndex or 1] or "Sort by Custom Order" + if sortMode == "Sort by Custom Order" then + local unfiltered = loadoutFilterIndex == 1 and slotFilter == "Any Slot" and searchText == "" + self.list = unfiltered and self.itemsTab.itemOrderList or itemList + self.isMutable = unfiltered + elseif sortMode == "Sort by Loadout" then + self.list = self:BuildLoadoutSort(itemList, canonicalOrder) + self.isMutable = false + else + self:SortItems(itemList, canonicalOrder, sortMode) + self.list = itemList + self.isMutable = false 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:SelectItem(itemId) + self:UpdateList() + local index = isValueInArray(self.list, itemId) + if not index then + self.controls.slotFilter.selIndex = 1 + self.controls.loadoutFilter.selIndex = 1 + self.controls.search.buf = "" + self:UpdateList() + index = isValueInArray(self.list, itemId) + end + if index then + self:SelectIndex(index) + end +end + function ItemListClass:Draw(viewPort) + local width = self:GetProperty("width") + local rowControlWidth = (width - 8) / 3 + local widthChanged = self.controls.slotFilter.width ~= rowControlWidth + self.controls.slotFilter.width = rowControlWidth + self.controls.sortMode.width = rowControlWidth + self.controls.loadoutFilter.width = rowControlWidth + self.controls.search.width = width + self.controls.deleteUnused.y = main.portraitMode and -44 or -50 + self.controls.deleteUnused.width = rowControlWidth + self.controls.deleteAll.width = rowControlWidth + self.controls.delete.width = rowControlWidth + if widthChanged then + self.controls.slotFilter:CheckDroppedWidth(false) + self.controls.sortMode:CheckDroppedWidth(false) + self.controls.loadoutFilter:CheckDroppedWidth(true) + end local loadoutListChanged = self:UpdateLoadoutList() local outputRevision = self.itemsTab.build and self.itemsTab.build.outputRevision if loadoutListChanged or outputRevision ~= self.lastOutputRevision then @@ -243,9 +389,57 @@ function ItemListClass:FindEquippedAbyssJewel(jewelId, excludeActiveSet) return equipSet end +function ItemListClass:OverrideSelectIndex(index) + if isGroupHeader(self.list[index]) then + self.selIndex = nil + self.selValue = nil + return true + end + return false +end + +function ItemListClass:OnKeyDown(key, doubleClick) + if not self:IsShown() or not self:IsEnabled() then + return + end + local mouseOverControl = self:GetMouseOverControl() + if mouseOverControl and mouseOverControl.OnKeyDown then + return mouseOverControl:OnKeyDown(key) + end + if not self.selDragActive and #self.list > 0 and (key == "UP" or key == "DOWN" or key == "HOME" or key == "END") then + local step = (key == "UP" or key == "END") and -1 or 1 + local index + if key == "HOME" then + index = 1 + elseif key == "END" then + index = #self.list + elseif key == "UP" then + index = (self.selIndex or #self.list + 1) - 1 + else + index = (self.selIndex or 0) + 1 + end + for _ = 1, #self.list do + if index < 1 then + index = #self.list + elseif index > #self.list then + index = 1 + end + if not isGroupHeader(self.list[index]) then + self:SelectIndex(index) + return self + end + index = index + step + end + end + return self.ListControl.OnKeyDown(self, key, doubleClick) +end + function ItemListClass:GetRowValue(column, index, itemId) - local item = self.itemsTab.items[itemId] if column == 1 then + if isGroupHeader(itemId) then + return "^7" .. itemId.groupHeader + end + local item = self.itemsTab.items[itemId] local used = self:FindEquippedAbyssJewel(itemId, true) or self:FindSocketedJewel(itemId, true) or "" if used == "" then local slot, itemSet = self.itemsTab:GetEquippedSlotForItem(item) @@ -261,8 +455,14 @@ function ItemListClass:GetRowValue(column, index, itemId) end end +function ItemListClass:GetRowIcon(column, index, itemId) + if not isGroupHeader(itemId) then + return itemSlotIcons.Get(self.itemsTab.items[itemId]:GetPrimarySlot()) + end +end + function ItemListClass:AddValueTooltip(tooltip, index, itemId) - if main.popups[1] then + if main.popups[1] or isGroupHeader(itemId) then tooltip:Clear() return end @@ -280,11 +480,11 @@ function ItemListClass:ReceiveDrag(type, value, source) if type == "Item" then local newItem = new("Item"):Item(value.raw) newItem:NormaliseQuality() - self.itemsTab:AddItem(newItem, true, self.selDragIndex) + self.itemsTab:AddItem(newItem, true, self.isMutable and self.selDragIndex or nil) self.itemsTab:AddForbiddenJewelCounterpart(newItem) self.itemsTab:PopulateSlots() self.itemsTab:AddUndoState() - self:UpdateList() + self:SelectItem(newItem.id) end end @@ -376,9 +576,9 @@ end function ItemListClass:OnHoverKeyUp(key) if itemLib.wiki.matchesKey(key) then local itemId = self.ListControl:GetHoverValue() - if itemId then + if itemId and not isGroupHeader(itemId) then local item = self.itemsTab.items[itemId] itemLib.wiki.openItem(item) end end -end \ No newline at end of file +end diff --git a/src/Classes/ItemSlotControl.lua b/src/Classes/ItemSlotControl.lua index bdf73ad750..912dd4e213 100644 --- a/src/Classes/ItemSlotControl.lua +++ b/src/Classes/ItemSlotControl.lua @@ -12,7 +12,7 @@ local itemSlotHelper = require("Modules.ItemSlotHelper") local ItemSlotClass = newClass("ItemSlotControl", "DropDownControl") function ItemSlotClass:ItemSlotControl(anchor, x, y, itemsTab, slotName, slotLabel, nodeId) - self:DropDownControl(anchor, { x, y, 310, 20 }, {}, function(index, value) + self:DropDownControl(anchor, { x, y, 329, 20 }, {}, function(index, value) if self.items[index] ~= self.selItemId then self:SetSelItemId(self.items[index]) itemsTab:PopulateSlots() @@ -120,16 +120,20 @@ function ItemSlotClass:CanReceiveDrag(type, value) end function ItemSlotClass:ReceiveDrag(type, value, source) + local newItem if value.id and self.itemsTab.items[value.id] then self:SetSelItemId(value.id) else - local newItem = new("Item"):Item(value.raw) + newItem = new("Item"):Item(value.raw) newItem:NormaliseQuality() self.itemsTab:AddItem(newItem, true) self:SetSelItemId(newItem.id) self.itemsTab:AddForbiddenJewelCounterpart(newItem) end self.itemsTab:PopulateSlots() + if newItem then + self.itemsTab.controls.itemList:SelectItem(newItem.id) + end self.itemsTab:AddUndoState() self.itemsTab.build.buildFlag = true end @@ -176,4 +180,4 @@ function ItemSlotClass:OnHoverKeyUp(key) end end end -end \ No newline at end of file +end diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index e650022020..eb7bd61189 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -108,7 +108,7 @@ function ItemsTabClass:ItemsTab(build) self.tradeQuery = new("TradeQuery"):TradeQuery(self) -- Set selector - self.controls.setSelect = new("DropDownControl"):DropDownControl({"TOPLEFT",self,"TOPLEFT"}, {96, 8, 216, 20}, nil, function(index, value) + self.controls.setSelect = new("DropDownControl"):DropDownControl({"TOPLEFT",self,"TOPLEFT"}, {96, 8, 235, 20}, nil, function(index, value) self:SetActiveItemSet(self.itemSetOrderList[index]) self:AddUndoState() end) @@ -128,7 +128,7 @@ function ItemsTabClass:ItemsTab(build) end) -- Price Items - self.controls.priceDisplayItem = new("ButtonControl"):ButtonControl({"TOPLEFT",self,"TOPLEFT"}, {96, 32, 310, 20}, "Trade for these items", function() + self.controls.priceDisplayItem = new("ButtonControl"):ButtonControl({"TOPLEFT",self,"TOPLEFT"}, {96, 32, 329, 20}, "Trade for these items", function() self.tradeQuery:PriceItem() end) self.controls.priceDisplayItem.tooltipFunc = function(tooltip) @@ -141,7 +141,7 @@ function ItemsTabClass:ItemsTab(build) self.slots = { } self.orderedSlots = { } self.slotOrder = { } - self.slotAnchor = new("Control"):Control({"TOPLEFT",self,"TOPLEFT"}, {96, 76, 310, 0}) + self.slotAnchor = new("Control"):Control({"TOPLEFT",self,"TOPLEFT"}, {96, 76, 329, 0}) local prevSlot = self.slotAnchor local function addSlot(slot) prevSlot = slot @@ -202,7 +202,7 @@ function ItemsTabClass:ItemsTab(build) end -- Passive tree dropdown controls - self.controls.specSelect = new("DropDownControl"):DropDownControl({"TOPLEFT",prevSlot,"BOTTOMLEFT"}, {0, 8, 216, 20}, nil, function(index, value) + self.controls.specSelect = new("DropDownControl"):DropDownControl({"TOPLEFT",prevSlot,"BOTTOMLEFT"}, {0, 8, 235, 20}, nil, function(index, value) if self.build.treeTab.specList[index] then self.build.modFlag = true self.build.treeTab:SetActiveSpec(index) @@ -276,10 +276,12 @@ function ItemsTabClass:ItemsTab(build) self.controls.weaponSwapLabel = new("LabelControl"):LabelControl({"RIGHT",self.controls.weaponSwap1,"LEFT"}, {-4, 0, 0, 14}, "^7Weapon Set:") -- All items list + local function itemListWidth() return main.portraitMode and 360 or 450 end + local function itemListHeight() return main.portraitMode and 244 or 324 end if main.portraitMode then - self.controls.itemList = new("ItemListControl"):ItemListControl({"TOPRIGHT",self.lastSlot,"BOTTOMRIGHT"}, {0, 0, 360, 308}, self, true) + self.controls.itemList = new("ItemListControl"):ItemListControl({"TOPRIGHT",self.lastSlot,"BOTTOMRIGHT"}, {0, 0, itemListWidth, itemListHeight}, self, true) else - self.controls.itemList = new("ItemListControl"):ItemListControl({"TOPLEFT",self.controls.setManage,"TOPRIGHT"}, {20, 20, 360, 308}, self, true) + self.controls.itemList = new("ItemListControl"):ItemListControl({"TOPLEFT",self.controls.setManage,"TOPRIGHT"}, {20, 70, itemListWidth, itemListHeight}, self, true) end -- Database selector @@ -290,24 +292,24 @@ function ItemsTabClass:ItemsTab(build) self.controls.selectDB = new("DropDownControl"):DropDownControl({"LEFT",self.controls.selectDBLabel,"RIGHT"}, {4, 0, 150, 18}, { "Uniques", "Rare Templates" }) -- Unique database - self.controls.uniqueDB = new("ItemDBControl"):ItemDBControl({"TOPLEFT",self.controls.itemList,"BOTTOMLEFT"}, {0, 76, 360, function(c) return m_min(244, self.maxY - select(2, c:GetPos())) end}, self, main.uniqueDB, "UNIQUE") + self.controls.uniqueDB = new("ItemDBControl"):ItemDBControl({"TOPLEFT",self.controls.itemList,"BOTTOMLEFT"}, {0, 76, itemListWidth, function(c) return m_min(196, self.maxY - select(2, c:GetPos())) end}, self, main.uniqueDB, "UNIQUE") self.controls.uniqueDB.y = function() - return self.controls.selectDBLabel:IsShown() and 118 or 96 + return self.controls.selectDBLabel:IsShown() and 122 or 100 end self.controls.uniqueDB.shown = function() return not self.controls.selectDBLabel:IsShown() or self.controls.selectDB.selIndex == 1 end -- Rare template database - self.controls.rareDB = new("ItemDBControl"):ItemDBControl({"TOPLEFT",self.controls.itemList,"BOTTOMLEFT"}, {0, 76, 360, function(c) return m_min(260, self.maxY - select(2, c:GetPos())) end}, self, main.rareDB, "RARE") + self.controls.rareDB = new("ItemDBControl"):ItemDBControl({"TOPLEFT",self.controls.itemList,"BOTTOMLEFT"}, {0, 76, itemListWidth, function(c) return m_min(196, self.maxY - select(2, c:GetPos())) end}, self, main.rareDB, "RARE") self.controls.rareDB.y = function() - return self.controls.selectDBLabel:IsShown() and 78 or 396 + return self.controls.selectDBLabel:IsShown() and 82 or 356 end self.controls.rareDB.shown = function() return not self.controls.selectDBLabel:IsShown() or self.controls.selectDB.selIndex == 2 end -- Create/import item - self.controls.craftDisplayItem = new("ButtonControl"):ButtonControl({"TOPLEFT",main.portraitMode and self.controls.setManage or self.controls.itemList,"TOPRIGHT"}, {20, main.portraitMode and 0 or -20, 120, 20}, "Craft item...", function() + self.controls.craftDisplayItem = new("ButtonControl"):ButtonControl({"TOPLEFT",main.portraitMode and self.controls.setManage or self.controls.itemList,"TOPRIGHT"}, {20, main.portraitMode and 0 or -70, 120, 20}, "Craft item...", function() self:CraftItem() end) self.controls.craftDisplayItem.shown = function() @@ -317,24 +319,21 @@ function ItemsTabClass:ItemsTab(build) self:EditDisplayItemText() end) self.controls.displayItemTip = new("LabelControl"):LabelControl({"TOPLEFT",self.controls.craftDisplayItem,"BOTTOMLEFT"}, {0, 8, 100, 16}, -[[^7Double-click an item from one of the lists, -or copy and paste an item from in game -(hover over the item and Ctrl+C) to view or edit -the item and add it to your build. You can -also clone an item within Path of Building by -copying and pasting it with Ctrl+C and Ctrl+V. - -You can Control + Click an item to equip it, or -drag it onto the slot. This will also add it to -your build if it's from the unique/template list. -If there's 2 slots an item can go in, -holding Shift will put it in the second.]]) - self.controls.sharedItemList = new("SharedItemListControl"):SharedItemListControl({"TOPLEFT",self.controls.craftDisplayItem, "BOTTOMLEFT"}, {0, 232, 340, 308}, self, true) +[[^7Double-click an item from one of the lists, or copy and paste an +item from in game (hover over the item and Ctrl+C) to view or edit +the item and add it to your build. You can also clone an item within +Path of Building by copying and pasting it with Ctrl+C and Ctrl+V. + +You can Control + Click an item to equip it, or drag it onto the slot. +This will also add it to your build if it's from the unique/template +list. If there are 2 slots an item can go in, holding Shift will +put it in the second.]]) + self.controls.sharedItemList = new("SharedItemListControl"):SharedItemListControl({"TOPLEFT",self.controls.craftDisplayItem, "BOTTOMLEFT"}, {0, 232, 425, 308}, self, true) -- Display item self.displayItemTooltip = new("Tooltip"):Tooltip() self.displayItemTooltip.maxWidth = 458 - self.anchorDisplayItem = new("Control"):Control({"TOPLEFT",main.portraitMode and self.controls.setManage or self.controls.itemList,"TOPRIGHT"}, {20, main.portraitMode and 0 or -20, 0, 0}) + self.anchorDisplayItem = new("Control"):Control({"TOPLEFT",main.portraitMode and self.controls.setManage or self.controls.itemList,"TOPRIGHT"}, {20, main.portraitMode and 0 or -70, 0, 0}) self.anchorDisplayItem.shown = function() return self.displayItem ~= nil end @@ -1551,12 +1550,12 @@ function ItemsTabClass:Draw(viewPort, inputEvents) self:UpdateSockets() if main.portraitMode then - self.controls.itemList:SetAnchor("TOPRIGHT", self.lastSlot, "BOTTOMRIGHT", 0, 40) + self.controls.itemList:SetAnchor("TOPRIGHT", self.lastSlot, "BOTTOMRIGHT", 0, 84) else - self.controls.itemList:SetAnchor("TOPLEFT", self.controls.setManage, "TOPRIGHT", 20, 20) + self.controls.itemList:SetAnchor("TOPLEFT", self.controls.setManage, "TOPRIGHT", 20, 70) end - self.controls.craftDisplayItem:SetAnchor("TOPLEFT", main.portraitMode and self.controls.setManage or self.controls.itemList, "TOPRIGHT", 20, main.portraitMode and 0 or -20) - self.anchorDisplayItem:SetAnchor("TOPLEFT", main.portraitMode and self.controls.setManage or self.controls.itemList, "TOPRIGHT", 20, main.portraitMode and 0) + self.controls.craftDisplayItem:SetAnchor("TOPLEFT", main.portraitMode and self.controls.setManage or self.controls.itemList, "TOPRIGHT", 20, main.portraitMode and 0 or -70) + self.anchorDisplayItem:SetAnchor("TOPLEFT", main.portraitMode and self.controls.setManage or self.controls.itemList, "TOPRIGHT", 20, main.portraitMode and 0 or -70) self:DrawControls(viewPort) if self.controls.scrollBarH:IsShown() then @@ -1620,6 +1619,7 @@ end function ItemsTabClass:EquipItemInSet(item, itemSetId) local itemSet = self.itemSets[itemSetId] local slotName = item:GetPrimarySlot() + local itemAdded if self.slots[slotName].weaponSet == 1 and itemSet.useSecondWeaponSet then -- Redirect to second weapon set slotName = slotName .. " Swap" @@ -1627,6 +1627,7 @@ function ItemsTabClass:EquipItemInSet(item, itemSetId) if not item.id or not self.items[item.id] then item = new("Item"):Item(item.raw) self:AddItem(item, true) + itemAdded = true end local altSlot = slotName:gsub("1","2") if IsKeyDown("SHIFT") then @@ -1644,6 +1645,9 @@ function ItemsTabClass:EquipItemInSet(item, itemSetId) end end self:PopulateSlots() + if itemAdded then + self.controls.itemList:SelectItem(item.id) + end self:AddUndoState() self.build.buildFlag = true end @@ -1803,6 +1807,7 @@ end -- Adds the current display item to the build's item list function ItemsTabClass:AddDisplayItem(noAutoEquip) local item = self.displayItem + local itemAdded = item and not item.id local oldItem = item and item.id and self.items[item.id] -- Add it to the list and clear the current display item self:AddItem(item, noAutoEquip) @@ -1812,47 +1817,13 @@ function ItemsTabClass:AddDisplayItem(noAutoEquip) self:AddForbiddenJewelCounterpart(item) self:PopulateSlots() + if itemAdded then + self.controls.itemList:SelectItem(item.id) + end self:AddUndoState() self.build.buildFlag = true end --- Sorts the build's item list -function ItemsTabClass:SortItemList() - table.sort(self.itemOrderList, function(a, b) - local itemA = self.items[a] - local itemB = self.items[b] - local primSlotA = itemA:GetPrimarySlot() - local primSlotB = itemB:GetPrimarySlot() - if primSlotA ~= primSlotB then - if not self.slotOrder[primSlotA] then - return false - elseif not self.slotOrder[primSlotB] then - return true - end - return self.slotOrder[primSlotA] < self.slotOrder[primSlotB] - end - local equipSlotA, equipSetA = self:GetEquippedSlotForItem(itemA) - local equipSlotB, equipSetB = self:GetEquippedSlotForItem(itemB) - if equipSlotA and equipSlotB then - if equipSlotA ~= equipSlotB then - return self.slotOrder[equipSlotA.slotName] < self.slotOrder[equipSlotB.slotName] - elseif equipSetA and not equipSetB then - return false - elseif not equipSetA and equipSetB then - return true - elseif equipSetA and equipSetB then - return isValueInArray(self.itemSetOrderList, equipSetA.id) < isValueInArray(self.itemSetOrderList, equipSetB.id) - end - elseif equipSlotA then - return true - elseif equipSlotB then - return false - end - return itemA.name < itemB.name - end) - self:AddUndoState() -end - -- Deletes an item function ItemsTabClass:DeleteItem(item, deferUndoState) for slotName, slot in pairs(self.slots) do diff --git a/src/Classes/SharedItemListControl.lua b/src/Classes/SharedItemListControl.lua index a4891b368f..6e41fac75c 100644 --- a/src/Classes/SharedItemListControl.lua +++ b/src/Classes/SharedItemListControl.lua @@ -14,9 +14,9 @@ function SharedItemListClass:SharedItemListControl(anchor, rect, itemsTab, force self:ListControl(anchor, rect, 16, "VERTICAL", true, main.sharedItemList, forceTooltip) self.itemsTab = itemsTab self.label = "^7Shared items:" - self.defaultText = "^x7F7F7FThis is a list of items that will be shared between all of\nyour builds.\nYou can add items to this list by dragging them from\none of the other lists." + self.defaultText = "^x7F7F7FThis is a list of items that will be shared between all of your builds.\n\nYou can add items to this list by dragging them from another list." self.dragTargetList = { } - self.controls.delete = new("ButtonControl"):ButtonControl({"BOTTOMRIGHT",self,"TOPRIGHT"}, {0, -2, 60, 18}, "Delete", function() + self.controls.delete = new("ButtonControl"):ButtonControl({"BOTTOMRIGHT",self,"TOPRIGHT"}, {0, -2, 60, 20}, "Delete", function() self:OnSelDelete(self.selIndex, self.selValue) end) self.controls.delete.enabled = function() diff --git a/src/Classes/SkillListControl.lua b/src/Classes/SkillListControl.lua index 13237af9bd..75d019d71a 100644 --- a/src/Classes/SkillListControl.lua +++ b/src/Classes/SkillListControl.lua @@ -6,25 +6,7 @@ local ipairs = ipairs local t_insert = table.insert local t_remove = table.remove -local slot_map = { - ["Weapon 1"] = { icon = NewImageHandle(), path = "Assets/icon_weapon.png" }, - ["Weapon 2"] = { icon = NewImageHandle(), path = "Assets/icon_weapon_2.png" }, - ["Weapon 1 Swap"] = { icon = NewImageHandle(), path = "Assets/icon_weapon_swap.png" }, - ["Weapon 2 Swap"] = { icon = NewImageHandle(), path = "Assets/icon_weapon_2_swap.png" }, - ["Bow"] = { icon = NewImageHandle(), path = "Assets/icon_bow.png" }, - ["Quiver"] = { icon = NewImageHandle(), path = "Assets/icon_quiver.png" }, - ["Shield"] = { icon = NewImageHandle(), path = "Assets/icon_shield.png" }, - ["Shield Swap"] = { icon = NewImageHandle(), path = "Assets/icon_shield_swap.png" }, - ["Helmet"] = { icon = NewImageHandle(), path = "Assets/icon_helmet.png" }, - ["Body Armour"] = { icon = NewImageHandle(), path = "Assets/icon_body_armour.png" }, - ["Gloves"] = { icon = NewImageHandle(), path = "Assets/icon_gloves.png" }, - ["Boots"] = { icon = NewImageHandle(), path = "Assets/icon_boots.png" }, - ["Amulet"] = { icon = NewImageHandle(), path = "Assets/icon_amulet.png" }, - ["Ring 1"] = { icon = NewImageHandle(), path = "Assets/icon_ring_left.png" }, - ["Ring 2"] = { icon = NewImageHandle(), path = "Assets/icon_ring_right.png" }, - ["Ring 3"] = { icon = NewImageHandle(), path = "Assets/icon_ring_right.png" }, - ["Belt"] = { icon = NewImageHandle(), path = "Assets/icon_belt.png" }, -} +local itemSlotIcons = require("Modules.ItemSlotIcons") ---@class SkillListControl: ListControl local SkillListClass = newClass("SkillListControl", "ListControl") @@ -67,9 +49,6 @@ function SkillListClass:SkillListControl(anchor, rect, skillsTab) skillsTab.build.buildFlag = true return skillsTab.gemSlots[1].nameSpec end) - for k, x in pairs(slot_map) do - x.icon:Load(x.path) - end return self end @@ -259,6 +238,6 @@ function SkillListClass:GetRowIcon(column, index, socketGroup) if slot == "Weapon 2 Swap" and (weapon2SwapType == "Quiver" or weapon2SwapType == "Shield") then slot = weapon2SwapType.." Swap" end - return slot_map[slot] and slot_map[slot].icon + return itemSlotIcons.Get(slot) end end diff --git a/src/Modules/ItemSlotIcons.lua b/src/Modules/ItemSlotIcons.lua new file mode 100644 index 0000000000..2c3286c9dc --- /dev/null +++ b/src/Modules/ItemSlotIcons.lua @@ -0,0 +1,40 @@ +-- Path of Building +-- +-- Module: Item Slot Icons +-- Provides the icon used for each item slot. +-- +local M = { } + +local slotMap = { + ["Weapon 1"] = "Assets/icon_weapon.png", + ["Weapon 2"] = "Assets/icon_weapon_2.png", + ["Weapon 1 Swap"] = "Assets/icon_weapon_swap.png", + ["Weapon 2 Swap"] = "Assets/icon_weapon_2_swap.png", + ["Bow"] = "Assets/icon_bow.png", + ["Quiver"] = "Assets/icon_quiver.png", + ["Shield"] = "Assets/icon_shield.png", + ["Shield Swap"] = "Assets/icon_shield_swap.png", + ["Helmet"] = "Assets/icon_helmet.png", + ["Body Armour"] = "Assets/icon_body_armour.png", + ["Gloves"] = "Assets/icon_gloves.png", + ["Boots"] = "Assets/icon_boots.png", + ["Amulet"] = "Assets/icon_amulet.png", + ["Ring 1"] = "Assets/icon_ring_left.png", + ["Ring 2"] = "Assets/icon_ring_right.png", + ["Ring 3"] = "Assets/icon_ring_right.png", + ["Belt"] = "Assets/icon_belt.png", + ["Jewel"] = "Assets/icon_jewel.png", + ["Flask 1"] = "Assets/icon_flask.png", +} + +for slot, path in pairs(slotMap) do + local icon = NewImageHandle() + icon:Load(path) + slotMap[slot] = icon +end + +function M.Get(slot) + return slotMap[slot] +end + +return M