diff --git a/src/Classes/ComparePowerReportListControl.lua b/src/Classes/ComparePowerReportListControl.lua index 1df2699180..00e21a2711 100644 --- a/src/Classes/ComparePowerReportListControl.lua +++ b/src/Classes/ComparePowerReportListControl.lua @@ -16,8 +16,8 @@ local ComparePowerReportListClass = newClass("ComparePowerReportListControl", "L { width = width * 0.10, label = "Category", sortable = true }, { width = width * 0.44, label = "Name" }, self.impactColumn, - { width = width * 0.08, label = "Points", sortable = true }, - { width = width * 0.16, label = "Per Point", sortable = true }, + { width = width * 0.06, label = "Points", sortable = true }, + { width = width * 0.14, label = "Per Point", sortable = true }, } self.colLabels = true self.showRowSeparators = true diff --git a/src/Classes/CompareTab.lua b/src/Classes/CompareTab.lua index 2e4fba9776..0e2869f0fb 100644 --- a/src/Classes/CompareTab.lua +++ b/src/Classes/CompareTab.lua @@ -52,7 +52,7 @@ local LAYOUT = { controlBarHeight = 126, -- Tree view header/footer - treeHeaderHeight = 58, + treeHeaderHeight = 74, treeFooterHeight = 30, treeOverlayCheckX = 155, @@ -61,23 +61,25 @@ local LAYOUT = { summaryCol2Right = 440, summaryCol3Right = 580, summaryCol4 = 600, + summaryHeaderHeight = 38, -- Items view - itemsCheckboxOffset = 60, + itemsCheckboxOffset = 78, itemsCopyBtnW = 60, - itemsCopyUseBtnW = 78, + itemsEquipBtnW = 60, itemsCopyBtnH = 18, itemsBuyBtnW = 60, itemsMinColWidth = 700, itemsHScrollBarHeight = 16, compareColGap = 48, + skillsHeaderHeight = 24, skillsHScrollBarHeight = 16, -- Calcs view calcsMaxCardWidth = 400, calcsLabelWidth = 132, calcsSepW = 2, - calcsHeaderBarHeight = 24, + calcsHeaderBarHeight = 12, -- Power report section (inside Summary view) powerReportLeft = 10, @@ -160,6 +162,7 @@ local CompareTabClass = newClass("CompareTab", "ControlHost", "Control", functio -- Tooltip for calcs hover breakdown self.calcsTooltip = new("Tooltip") + self.calcsShowOnlyDifferences = true -- Interactive config controls state self.configControls = {} -- { var -> { control, varData } } @@ -343,7 +346,7 @@ function CompareTabClass:InitControls() self.controls.cmpSkillLabel.shown = setsEnabled -- Socket group dropdown - self.controls.cmpSocketGroup = new("DropDownControl", {"LEFT", self.controls.cmpSkillLabel, "RIGHT"}, {2, 0, 200, 20}, {}, function(index, value) + self.controls.cmpSocketGroup = new("DropDownControl", {"LEFT", self.controls.cmpSkillLabel, "RIGHT"}, {4, 0, 200, 20}, {}, function(index, value) local entry = self:GetActiveCompare() if entry then entry:SetMainSocketGroup(index) @@ -354,7 +357,7 @@ function CompareTabClass:InitControls() self.controls.cmpSocketGroup.enableDroppedWidth = true -- Active skill within group - self.controls.cmpMainSkill = new("DropDownControl", {"LEFT", self.controls.cmpSocketGroup, "RIGHT"}, {2, 0, 150, 20}, {}, function(index, value) + self.controls.cmpMainSkill = new("DropDownControl", {"LEFT", self.controls.cmpSocketGroup, "RIGHT"}, {4, 0, 225, 20}, {}, function(index, value) local entry = self:GetActiveCompare() if entry then local mainSocketGroup = entry.skillsTab.socketGroupList[entry.mainSocketGroup] @@ -367,7 +370,7 @@ function CompareTabClass:InitControls() self.controls.cmpMainSkill.shown = false -- Skill part (multi-part skills) - self.controls.cmpSkillPart = new("DropDownControl", {"LEFT", self.controls.cmpMainSkill, "RIGHT"}, {2, 0, 100, 20}, {}, function(index, value) + self.controls.cmpSkillPart = new("DropDownControl", {"LEFT", self.controls.cmpMainSkill, "RIGHT"}, {4, 0, 200, 20}, {}, function(index, value) local entry = self:GetActiveCompare() if entry then local mainSocketGroup = entry.skillsTab.socketGroupList[entry.mainSocketGroup] @@ -384,9 +387,9 @@ function CompareTabClass:InitControls() self.controls.cmpSkillPart.shown = false -- Stage count - self.controls.cmpStageCountLabel = new("LabelControl", {"LEFT", self.controls.cmpSkillPart, "RIGHT"}, {4, 0, 0, 16}, "^7Stages:") + self.controls.cmpStageCountLabel = new("LabelControl", {"LEFT", self.controls.cmpSkillPart, "RIGHT"}, {6, 0, 0, 16}, "^7Stages:") self.controls.cmpStageCountLabel.shown = function() return self.controls.cmpStageCount.shown end - self.controls.cmpStageCount = new("EditControl", {"LEFT", self.controls.cmpStageCountLabel, "RIGHT"}, {2, 0, 52, 20}, "", nil, "%D", 5, function(buf) + self.controls.cmpStageCount = new("EditControl", {"LEFT", self.controls.cmpStageCountLabel, "RIGHT"}, {4, 0, 52, 20}, "", nil, "%D", 5, function(buf) local entry = self:GetActiveCompare() if entry then local mainSocketGroup = entry.skillsTab.socketGroupList[entry.mainSocketGroup] @@ -403,9 +406,9 @@ function CompareTabClass:InitControls() self.controls.cmpStageCount.shown = false -- Mine count - self.controls.cmpMineCountLabel = new("LabelControl", {"LEFT", self.controls.cmpStageCount, "RIGHT"}, {4, 0, 0, 16}, "^7Mines:") + self.controls.cmpMineCountLabel = new("LabelControl", {"LEFT", self.controls.cmpStageCount, "RIGHT"}, {6, 0, 0, 16}, "^7Mines:") self.controls.cmpMineCountLabel.shown = function() return self.controls.cmpMineCount.shown end - self.controls.cmpMineCount = new("EditControl", {"LEFT", self.controls.cmpMineCountLabel, "RIGHT"}, {2, 0, 52, 20}, "", nil, "%D", 5, function(buf) + self.controls.cmpMineCount = new("EditControl", {"LEFT", self.controls.cmpMineCountLabel, "RIGHT"}, {4, 0, 52, 20}, "", nil, "%D", 5, function(buf) local entry = self:GetActiveCompare() if entry then local mainSocketGroup = entry.skillsTab.socketGroupList[entry.mainSocketGroup] @@ -422,7 +425,7 @@ function CompareTabClass:InitControls() self.controls.cmpMineCount.shown = false -- Minion selector - self.controls.cmpMinion = new("DropDownControl", {"LEFT", self.controls.cmpMineCount, "RIGHT"}, {4, 0, 140, 20}, {}, function(index, value) + self.controls.cmpMinion = new("DropDownControl", {"LEFT", self.controls.cmpMineCount, "RIGHT"}, {6, 0, 140, 20}, {}, function(index, value) local entry = self:GetActiveCompare() if entry then local mainSocketGroup = entry.skillsTab.socketGroupList[entry.mainSocketGroup] @@ -446,7 +449,7 @@ function CompareTabClass:InitControls() self.controls.cmpMinion.shown = false -- Minion skill selector - self.controls.cmpMinionSkill = new("DropDownControl", {"LEFT", self.controls.cmpMinion, "RIGHT"}, {2, 0, 140, 20}, {}, function(index, value) + self.controls.cmpMinionSkill = new("DropDownControl", {"LEFT", self.controls.cmpMinion, "RIGHT"}, {4, 0, 140, 20}, {}, function(index, value) local entry = self:GetActiveCompare() if entry then local mainSocketGroup = entry.skillsTab.socketGroupList[entry.mainSocketGroup] @@ -702,6 +705,13 @@ function CompareTabClass:InitControls() end) self.controls.cmpCalcsMode.shown = false + self.controls.calcsShowOnlyDifferencesCheck = new("CheckBoxControl", nil, {0, 0, 18}, "Show only differences", function(state) + self.calcsShowOnlyDifferences = state + end, "Show only rows that differ between both builds. Disable to include unchanged rows.") + self.controls.calcsShowOnlyDifferencesCheck.shown = function() + return self.compareViewMode == "CALCS" and self:GetActiveCompare() ~= nil + end + -- ============================================================ -- Tree footer controls (visible only in TREE view mode with a comparison loaded) -- ============================================================ @@ -911,7 +921,7 @@ function CompareTabClass:InitControls() end -- Config view: search bar - self.controls.configSearchEdit = new("EditControl", nil, {0, 0, 200, 20}, "", "Search", "%c", 100, nil, nil, nil, true) + self.controls.configSearchEdit = new("EditControl", nil, {0, 0, 240, 20}, "", "Search", "%c", 100, nil, nil, nil, true) self.controls.configSearchEdit.shown = function() return self.compareViewMode == "CONFIG" and self:GetActiveCompare() ~= nil end @@ -1018,6 +1028,16 @@ function CompareTabClass:InitControls() return self.compareViewMode == "CALCS" and self:GetActiveCompare() ~= nil and calcsScrollBar.enabled end + -- Shared vertical scrollbar for Summary/Items/Skills/Config sub-tabs + self.controls.viewScrollBar = new("ScrollBarControl", nil, {0, 0, 18, 0}, 50, "VERTICAL", true) + local viewScrollBar = self.controls.viewScrollBar + self.controls.viewScrollBar.shown = function() + return self:GetActiveCompare() ~= nil + and self.compareViewMode ~= "TREE" + and self.compareViewMode ~= "CALCS" + and viewScrollBar.enabled + end + -- Horizontal scrollbar for Items sub-tab self.controls.itemsHScrollBar = new("ScrollBarControl", nil, {0, 0, 0, LAYOUT.itemsHScrollBarHeight}, 60, "HORIZONTAL", true) local itemsHScrollBar = self.controls.itemsHScrollBar @@ -1093,7 +1113,7 @@ function CompareTabClass:NormalizeConfigVals(varData, pVal, cVal) end -- Create a single config control for a given varData, writing to the specified input/configTab/build -local function makeConfigControl(varData, inputTable, configTab, buildObj) +local function makeConfigControl(varData, inputTable, configTab, buildObj, sourceControl) local control local pVal = inputTable[varData.var] if varData.type == "check" then @@ -1128,6 +1148,12 @@ local function makeConfigControl(varData, inputTable, configTab, buildObj) end if control then control.shown = function() return false end + -- Reuse tooltip behavior from the source ConfigTab control so compare view + -- matches normal Config tab hover help (including dynamic tooltip funcs). + if sourceControl then + control.tooltipText = sourceControl.tooltipText + control.tooltipFunc = sourceControl.tooltipFunc + end end return control end @@ -1161,8 +1187,10 @@ function CompareTabClass:RebuildConfigControls(compareEntry) currentSection = nil end elseif currentSection and varData.var and varData.type ~= "text" then - local pCtrl = makeConfigControl(varData, pInput, self.primaryBuild.configTab, primaryBuild) - local cCtrl = makeConfigControl(varData, cInput, compareEntry.configTab, compareEntry) + local pSource = self.primaryBuild.configTab.varControls and self.primaryBuild.configTab.varControls[varData.var] + local cSource = compareEntry.configTab.varControls and compareEntry.configTab.varControls[varData.var] + local pCtrl = makeConfigControl(varData, pInput, self.primaryBuild.configTab, primaryBuild, pSource) + local cCtrl = makeConfigControl(varData, cInput, compareEntry.configTab, compareEntry, cSource) if pCtrl and cCtrl then self.controls["cfg_p_" .. varData.var] = pCtrl @@ -1539,6 +1567,11 @@ function CompareTabClass:OpenImportFolderPopup() self:LoadBuild(build) end end + function controls.buildList:OnHoverKeyUp(key) + if self.controls.scrollBarV:IsScrollDownKey(key) or self.controls.scrollBarV:IsScrollUpKey(key) then + self:OnKeyUp(key) + end + end function controls.buildList:CanReceiveDrag() return false end function controls.buildList:OnSelCopy() end function controls.buildList:OnSelCut() end @@ -1566,6 +1599,7 @@ end -- DRAW - Main render method -- ============================================================ function CompareTabClass:Draw(viewPort, inputEvents) + main:DrawBackground(viewPort) -- Position top-bar controls self.controls.subTabAnchor.x = viewPort.x + 4 self.controls.subTabAnchor.y = viewPort.y + 96 @@ -1604,12 +1638,18 @@ function CompareTabClass:Draw(viewPort, inputEvents) if compareEntry then self:UpdateSetSelectors(compareEntry) end + for _, event in ipairs(inputEvents) do + if event.type == "KeyDown" and event.key == "f" and IsKeyDown("CTRL") then + if self.compareViewMode == "CONFIG" and compareEntry then + self:SelectControl(self.controls.configSearchEdit) + end + end + end -- Layout and refresh calcs skill detail controls self.calcsSkillHeaderHeight = 0 if self.compareViewMode == "CALCS" and compareEntry then self.calcsSkillHeaderHeight = self:LayoutCalcsSkillControls(contentVP, compareEntry) end - self:HandleScrollInput(contentVP, inputEvents) -- Draw calcs skill header background if self.compareViewMode == "CALCS" and self.calcsSkillHeaderHeight > 0 then @@ -1617,11 +1657,51 @@ function CompareTabClass:Draw(viewPort, inputEvents) DrawImage(nil, contentVP.x, contentVP.y, contentVP.width, self.calcsSkillHeaderHeight) end + -- Layout shared vertical scrollbar for Summary/Items/Skills/Config. + local mode = self.compareViewMode + local viewScrollBar = self.controls.viewScrollBar + if compareEntry and mode ~= "TREE" and mode ~= "CALCS" then + local topReserve = 0 + local bottomReserve = 0 + local contentHeight = 0 + if mode == "SUMMARY" then + topReserve = LAYOUT.summaryHeaderHeight + contentHeight = self.summaryTotalContentHeight or 0 + elseif mode == "CONFIG" then + topReserve = LAYOUT.configFixedHeaderHeight + contentHeight = self.configTotalContentHeight or 0 + elseif mode == "ITEMS" then + topReserve = LAYOUT.itemsCheckboxOffset + bottomReserve = self.controls.itemsHScrollBar.enabled and LAYOUT.itemsHScrollBarHeight or 0 + contentHeight = self.itemsTotalContentHeight or 0 + elseif mode == "SKILLS" then + topReserve = LAYOUT.skillsHeaderHeight + bottomReserve = self.controls.skillsHScrollBar.enabled and LAYOUT.skillsHScrollBarHeight or 0 + contentHeight = self.skillsTotalContentHeight or 0 + end + local viewHeight = m_max(contentVP.height - topReserve - bottomReserve, 0) + viewScrollBar.x = contentVP.x + contentVP.width - 18 + viewScrollBar.y = contentVP.y + topReserve + viewScrollBar.height = viewHeight + viewScrollBar:SetContentDimension(contentHeight, viewHeight) + viewScrollBar:SetOffset(self.scrollY) + self.scrollY = viewScrollBar.offset + else + viewScrollBar:SetContentDimension(0, contentVP.height) + viewScrollBar:SetOffset(0) + end + -- Process input events for our controls (including footer controls) self:ProcessControlsInput(inputEvents, viewPort) + self:HandleScrollInput(contentVP, inputEvents) + if self.controls.viewScrollBar:IsShown() then + self.scrollY = self.controls.viewScrollBar.offset + end + + local drawingTree = self.compareViewMode == "TREE" and compareEntry ~= nil -- Draw TREE view BEFORE controls so header dropdowns render on top of the tree - if self.compareViewMode == "TREE" and compareEntry then + if drawingTree then self:DrawTree(contentVP, inputEvents, compareEntry) -- Elevate to main draw layer 1 (matching TreeTab pattern) so controls @@ -1641,14 +1721,11 @@ function CompareTabClass:Draw(viewPort, inputEvents) SetDrawColor(0.85, 0.85, 0.85) DrawImage(nil, contentVP.x, layout.footerY, contentVP.width, 2) end - end - - -- Draw controls (at main layer 1 when in TREE mode, above all tree content) - self:DrawControls(viewPort) - - -- Reset to default draw layer after controls - if self.compareViewMode == "TREE" and compareEntry then - SetDrawLayer(0) + SetDrawColor(1, 1, 1) + DrawString(self.controls.leftSpecSelect.x, contentVP.y + 4, "LEFT", 18, "VAR", + colorCodes.POSITIVE .. self:GetShortBuildName(self.primaryBuild.buildName)) + DrawString(self.controls.rightSpecSelect.x, contentVP.y + 4, "LEFT", 18, "VAR", + colorCodes.WARNING .. (compareEntry.label or "Compare Build")) end if not compareEntry then @@ -1660,57 +1737,72 @@ function CompareTabClass:Draw(viewPort, inputEvents) DrawString(0, 70, "CENTER", 16, "VAR", "^7Click " .. colorCodes.POSITIVE .. "Import..." .. "^7 above to import a build to compare against.") SetViewport() - return - end - - -- Position items expanded mode checkbox and item set dropdowns (inside content area, top-left) - -- Label draws to the left of the checkbox, so offset x by labelWidth to keep it visible - if self.compareViewMode == "ITEMS" then - self.controls.itemsExpandedCheck.x = contentVP.x + 10 + self.controls.itemsExpandedCheck.labelWidth - self.controls.itemsExpandedCheck.y = contentVP.y + 8 - - local colWidth = self.itemsColWidth or m_max(m_floor(contentVP.width / 2), LAYOUT.itemsMinColWidth) - local itemSetLabelW = DrawStringWidth(16, "VAR", "^7Item set:") + 4 - local scrollOffsetX = -((self.controls.itemsHScrollBar and self.controls.itemsHScrollBar.offset) or 0) - - -- Item set dropdowns - local row1Y = contentVP.y + 34 - - -- Primary build item set dropdown - self.controls.primaryItemSetLabel.x = contentVP.x + scrollOffsetX + 10 - self.controls.primaryItemSetLabel.y = row1Y + 2 - self.controls.primaryItemSetSelect.x = contentVP.x + scrollOffsetX + 10 + itemSetLabelW - self.controls.primaryItemSetSelect.y = row1Y + else + -- Position items expanded mode checkbox and item set dropdowns (inside content area, top-left) + -- Label draws to the left of the checkbox, so offset x by labelWidth to keep it visible + if self.compareViewMode == "ITEMS" then + self.controls.itemsExpandedCheck.x = contentVP.x + 10 + self.controls.itemsExpandedCheck.labelWidth + self.controls.itemsExpandedCheck.y = contentVP.y + 8 + + local colWidth = self.itemsColWidth or m_max(m_floor(contentVP.width / 2), LAYOUT.itemsMinColWidth) + local itemSetLabelW = DrawStringWidth(16, "VAR", "^7Item set:") + 4 + local scrollOffsetX = -((self.controls.itemsHScrollBar and self.controls.itemsHScrollBar.offset) or 0) + + -- Item set dropdowns + local buildLabelY = contentVP.y + 30 + local row1Y = contentVP.y + 52 + + -- Primary build item set dropdown + self.controls.primaryItemSetLabel.x = contentVP.x + scrollOffsetX + 10 + self.controls.primaryItemSetLabel.y = row1Y + 2 + self.controls.primaryItemSetSelect.x = contentVP.x + scrollOffsetX + 10 + itemSetLabelW + self.controls.primaryItemSetSelect.y = row1Y + + -- Compare build item set dropdown + self.controls.compareItemSetLabel2.x = contentVP.x + scrollOffsetX + colWidth + 10 + self.controls.compareItemSetLabel2.y = row1Y + 2 + self.controls.compareItemSetSelect2.x = contentVP.x + scrollOffsetX + colWidth + 10 + itemSetLabelW + self.controls.compareItemSetSelect2.y = row1Y - -- Compare build item set dropdown - self.controls.compareItemSetLabel2.x = contentVP.x + scrollOffsetX + colWidth + 10 - self.controls.compareItemSetLabel2.y = row1Y + 2 - self.controls.compareItemSetSelect2.x = contentVP.x + scrollOffsetX + colWidth + 10 + itemSetLabelW - self.controls.compareItemSetSelect2.y = row1Y + SetDrawColor(1, 1, 1) + DrawString(self.controls.primaryItemSetLabel.x, buildLabelY, "LEFT", 18, "VAR", + colorCodes.POSITIVE .. self:GetShortBuildName(self.primaryBuild.buildName)) + DrawString(self.controls.compareItemSetLabel2.x, buildLabelY, "LEFT", 18, "VAR", + colorCodes.WARNING .. (compareEntry.label or "Compare Build")) + SetDrawColor(0.5, 0.5, 0.5) + DrawImage(nil, contentVP.x + 4, contentVP.y + LAYOUT.itemsCheckboxOffset - 2, contentVP.width - 8, 2) + + -- Populate primary build item set list + if self.primaryBuild.itemsTab and self.primaryBuild.itemsTab.itemSetOrderList then + self:PopulateSetDropdown(self.primaryBuild.itemsTab, "itemSetOrderList", "itemSets", "activeItemSetId", self.controls.primaryItemSetSelect) + end - -- Populate primary build item set list - if self.primaryBuild.itemsTab and self.primaryBuild.itemsTab.itemSetOrderList then - self:PopulateSetDropdown(self.primaryBuild.itemsTab, "itemSetOrderList", "itemSets", "activeItemSetId", self.controls.primaryItemSetSelect) + -- Populate compare build item set list + if compareEntry and compareEntry.itemsTab and compareEntry.itemsTab.itemSetOrderList then + self:PopulateSetDropdown(compareEntry.itemsTab, "itemSetOrderList", "itemSets", "activeItemSetId", self.controls.compareItemSetSelect2) + end end - -- Populate compare build item set list - if compareEntry and compareEntry.itemsTab and compareEntry.itemsTab.itemSetOrderList then - self:PopulateSetDropdown(compareEntry.itemsTab, "itemSetOrderList", "itemSets", "activeItemSetId", self.controls.compareItemSetSelect2) + -- Dispatch to sub-view (TREE already drawn above) + if self.compareViewMode == "SUMMARY" then + self:DrawSummary(contentVP, compareEntry) + elseif self.compareViewMode == "ITEMS" then + self:DrawItems(contentVP, compareEntry, inputEvents) + elseif self.compareViewMode == "SKILLS" then + self:DrawSkills(contentVP, compareEntry) + elseif self.compareViewMode == "CALCS" then + self:DrawCalcs(contentVP, compareEntry) + elseif self.compareViewMode == "CONFIG" then + self:DrawConfig(contentVP, compareEntry) end - end - -- Dispatch to sub-view (TREE already drawn above) - if self.compareViewMode == "SUMMARY" then - self:DrawSummary(contentVP, compareEntry) - elseif self.compareViewMode == "ITEMS" then - self:DrawItems(contentVP, compareEntry, inputEvents) - elseif self.compareViewMode == "SKILLS" then - self:DrawSkills(contentVP, compareEntry) - elseif self.compareViewMode == "CALCS" then - self:DrawCalcs(contentVP, compareEntry) - elseif self.compareViewMode == "CONFIG" then - self:DrawConfig(contentVP, compareEntry) + self:DrawControls(viewPort) + if drawingTree then + SetDrawLayer(0) + end + if self.controls.viewScrollBar:IsShown() then + self.scrollY = self.controls.viewScrollBar.offset end end @@ -1820,6 +1912,9 @@ function CompareTabClass:LayoutTreeView(contentVP, compareEntry) self.controls.rightFooterAnchor.y = footerY + 4 self.controls.rightTreeSearch.width = halfWidth - 8 end + self.controls.leftSpecSelect.y = contentVP.y + 22 + self.controls.rightSpecSelect.y = contentVP.y + 22 + self.controls.treeOverlayCheck.y = contentVP.y + 48 -- (Common) Update spec dropdown lists if self.primaryBuild.treeTab then @@ -1897,12 +1992,19 @@ function CompareTabClass:LayoutConfigView(contentVP, compareEntry) -- Position header controls local row1Y = contentVP.y + 4 local row2Y = contentVP.y + 28 - self.controls.copyConfigBtn.x = contentVP.x + 10 + local configHeaderLeftX = contentVP.x + 10 + local configSetSelectX = contentVP.x + 80 + local configSetSelectW = 225 + local inputEndX = configSetSelectX + configSetSelectW + local actionX = inputEndX + 10 + + self.controls.copyConfigBtn.x = actionX self.controls.copyConfigBtn.y = row1Y - self.controls.configToggleBtn.x = contentVP.x + 260 - self.controls.configToggleBtn.y = row1Y + self.controls.configToggleBtn.x = actionX + self.controls.configToggleBtn.y = row2Y - self.controls.configSearchEdit.x = contentVP.x + 10 + self.controls.configSearchEdit.x = configHeaderLeftX + self.controls.configSearchEdit.width = inputEndX - configHeaderLeftX self.controls.configSearchEdit.y = row2Y -- Update primary config set dropdown list @@ -1916,10 +2018,11 @@ function CompareTabClass:LayoutConfigView(contentVP, compareEntry) end end self.controls.configPrimarySetSelect:SetList(pSetList) - self.controls.configPrimarySetLabel.x = contentVP.x + 220 - self.controls.configPrimarySetLabel.y = row2Y + 2 - self.controls.configPrimarySetSelect.x = contentVP.x + 290 - self.controls.configPrimarySetSelect.y = row2Y + self.controls.configPrimarySetLabel.x = configHeaderLeftX + self.controls.configPrimarySetLabel.y = row1Y + 2 + self.controls.configPrimarySetSelect.x = configSetSelectX + self.controls.configPrimarySetSelect.width = configSetSelectW + self.controls.configPrimarySetSelect.y = row1Y -- Build section layout: multi-column grid, mirroring regular ConfigTab local rowHeight = LAYOUT.configRowHeight @@ -2146,7 +2249,7 @@ function CompareTabClass:LayoutCalcsSkillControls(vp, compareEntry) local colWidth = m_floor((vp.width - 20) / 2) local leftX = vp.x + 4 local rightX = leftX + colWidth + 12 - local labelW = 140 + local labelW = 120 local controlW = colWidth - labelW - 8 local rowH = 22 local y = vp.y + 4 @@ -2197,9 +2300,9 @@ function CompareTabClass:LayoutCalcsSkillControls(vp, compareEntry) local textLinesHeight = 2 -- padding before text local primaryEnv = self.primaryBuild.calcsTab and self.primaryBuild.calcsTab.calcsEnv local compareEnv = compareEntry.calcsTab and compareEntry.calcsTab.calcsEnv - local pOutput = primaryEnv and primaryEnv.player and primaryEnv.player.output - local cOutput = compareEnv and compareEnv.player and compareEnv.player.output - if pOutput or cOutput then + local primaryOutput = primaryEnv and primaryEnv.player and primaryEnv.player.output + local compareOutput = compareEnv and compareEnv.player and compareEnv.player.output + if primaryOutput or compareOutput then local wrapWidth = colWidth - 8 local infoLabels = { BuffList = "Aura/Buff Skills", @@ -2208,18 +2311,23 @@ function CompareTabClass:LayoutCalcsSkillControls(vp, compareEntry) } local infoKeys = { "BuffList", "CombatList", "CurseList" } for _, key in ipairs(infoKeys) do - local pVal = pOutput and pOutput[key] - local cVal = cOutput and cOutput[key] - if (pVal and pVal ~= "") or (cVal and cVal ~= "") then + local primaryValue = primaryOutput and primaryOutput[key] + local compareValue = compareOutput and compareOutput[key] + if (primaryValue and primaryValue ~= "") or (compareValue and compareValue ~= "") then local label = infoLabels[key] - local pLines = (pVal and pVal ~= "") and #wrapInfoLine(label .. ": " .. pVal, wrapWidth) or 0 - local cLines = (cVal and cVal ~= "") and #wrapInfoLine(label .. ": " .. cVal, wrapWidth) or 0 - textLinesHeight = textLinesHeight + m_max(pLines, cLines, 1) * 18 + local primaryLineCount = (primaryValue and primaryValue ~= "") and #wrapInfoLine(label .. ": " .. primaryValue, wrapWidth) or 0 + local compareLineCount = (compareValue and compareValue ~= "") and #wrapInfoLine(label .. ": " .. compareValue, wrapWidth) or 0 + textLinesHeight = textLinesHeight + m_max(primaryLineCount, compareLineCount, 1) * 18 end end end - local headerHeight = m_max(leftY, rightY) - vp.y + textLinesHeight + 4 -- +4 for separator padding + local textBaseY = m_max(leftY, rightY) + local headerHeight = textBaseY - vp.y + textLinesHeight + 4 -- + separator padding + local showOnlyDiffCheck = self.controls.calcsShowOnlyDifferencesCheck + showOnlyDiffCheck.state = self.calcsShowOnlyDifferences and true or false + showOnlyDiffCheck.x = leftX + showOnlyDiffCheck.labelWidth + 2 + showOnlyDiffCheck.y = vp.y + headerHeight + 4 return headerHeight end @@ -2231,9 +2339,10 @@ function CompareTabClass:HandleScrollInput(contentVP, inputEvents) local listControl = self.controls.comparePowerReportList local mouseOverList = listControl:IsShown() and listControl:IsMouseOver() + local mouseOverViewScrollBar = self.controls.viewScrollBar:IsShown() and self.controls.viewScrollBar:IsMouseOver() for id, event in ipairs(inputEvents) do - if event.type == "KeyDown" and mouseInContent and not mouseOverList then + if event.type == "KeyUp" and mouseInContent and not mouseOverList then if self.compareViewMode == "CALCS" then if event.key == "WHEELUP" then self.controls.calcsScrollBar:Scroll(-1) @@ -2242,26 +2351,32 @@ function CompareTabClass:HandleScrollInput(contentVP, inputEvents) self.controls.calcsScrollBar:Scroll(1) inputEvents[id] = nil end - elseif event.key == "WHEELUP" and self.compareViewMode ~= "TREE" then + elseif event.key == "WHEELUP" and self.compareViewMode ~= "TREE" and not mouseOverViewScrollBar then self.scrollY = m_max(self.scrollY - 40, 0) + self.controls.viewScrollBar:SetOffset(self.scrollY) + self.scrollY = self.controls.viewScrollBar.offset inputEvents[id] = nil - elseif event.key == "WHEELDOWN" and self.compareViewMode ~= "TREE" then + elseif event.key == "WHEELDOWN" and self.compareViewMode ~= "TREE" and not mouseOverViewScrollBar then local maxScroll = 0 local viewportH = contentVP.height if self.compareViewMode == "CONFIG" and self.configTotalContentHeight then local scrollViewH = viewportH - LAYOUT.configFixedHeaderHeight maxScroll = m_max(self.configTotalContentHeight - scrollViewH, 0) elseif self.compareViewMode == "SUMMARY" and self.summaryTotalContentHeight > 0 then - maxScroll = m_max(self.summaryTotalContentHeight - viewportH, 0) + local scrollViewH = viewportH - LAYOUT.summaryHeaderHeight + maxScroll = m_max(self.summaryTotalContentHeight - scrollViewH, 0) elseif self.compareViewMode == "ITEMS" and self.itemsTotalContentHeight > 0 then local hBarReserve = self.controls.itemsHScrollBar.enabled and LAYOUT.itemsHScrollBarHeight or 0 local scrollViewH = viewportH - LAYOUT.itemsCheckboxOffset - hBarReserve maxScroll = m_max(self.itemsTotalContentHeight - scrollViewH, 0) elseif self.compareViewMode == "SKILLS" and self.skillsTotalContentHeight > 0 then local hBarReserve = self.controls.skillsHScrollBar.enabled and LAYOUT.skillsHScrollBarHeight or 0 - maxScroll = m_max(self.skillsTotalContentHeight - (viewportH - hBarReserve), 0) + local scrollViewH = viewportH - LAYOUT.skillsHeaderHeight - hBarReserve + maxScroll = m_max(self.skillsTotalContentHeight - scrollViewH, 0) end self.scrollY = m_min(self.scrollY + 40, maxScroll) + self.controls.viewScrollBar:SetOffset(self.scrollY) + self.scrollY = self.controls.viewScrollBar.offset inputEvents[id] = nil end end @@ -2937,6 +3052,8 @@ function CompareTabClass:DrawSummary(vp, compareEntry) local lineHeight = 18 local headerHeight = 22 + local headerReserve = LAYOUT.summaryHeaderHeight + local scrollViewH = vp.height - headerReserve -- Column positions (col3R and col4 shift right dynamically to avoid name overlap) local col1 = LAYOUT.summaryCol1 @@ -2952,22 +3069,18 @@ function CompareTabClass:DrawSummary(vp, compareEntry) local col3R = m_min(m_max(LAYOUT.summaryCol3Right, minCol3R), maxCol3R) local col4 = col3R + 20 - SetViewport(vp.x, vp.y, vp.width, vp.height) - local drawY = 4 - self.scrollY - - -- Headers + SetViewport(vp.x, vp.y, vp.width, headerReserve) SetDrawColor(1, 1, 1) - DrawString(col1, drawY, "LEFT", headerHeight, "VAR", "^7Stat") - DrawString(col2R, drawY, "RIGHT_X", headerHeight, "VAR", colorCodes.POSITIVE .. primaryName) - DrawString(col3R, drawY, "RIGHT_X", headerHeight, "VAR", + DrawString(col1, 4, "LEFT", headerHeight, "VAR", "^7Stat") + DrawString(col2R, 4, "RIGHT_X", headerHeight, "VAR", colorCodes.POSITIVE .. primaryName) + DrawString(col3R, 4, "RIGHT_X", headerHeight, "VAR", colorCodes.WARNING .. compareName) - DrawString(col4, drawY, "LEFT", headerHeight, "VAR", "^7Difference") - drawY = drawY + headerHeight + 4 - - -- Separator + DrawString(col4, 4, "LEFT", headerHeight, "VAR", "^7Difference") SetDrawColor(0.5, 0.5, 0.5) - DrawImage(nil, 4, drawY, vp.width - 8, 2) - drawY = drawY + 6 + DrawImage(nil, 4, headerHeight + 8, vp.width - 8, 2) + + SetViewport(vp.x, vp.y + headerReserve, vp.width, scrollViewH) + local drawY = -self.scrollY -- Stat comparison local displayStats = summaryUseMinion and self.primaryBuild.minionDisplayStats or self.primaryBuild.displayStats @@ -2996,8 +3109,7 @@ function CompareTabClass:DrawSummary(vp, compareEntry) -- Position controls dynamically based on drawY -- The controls need absolute screen positions (vp.x/vp.y offset + viewport-local drawY) - -- drawY already includes the scroll offset (starts at 4 - self.scrollY) - local controlY = vp.y + drawY + local controlY = vp.y + headerReserve + drawY local ctrlBaseX = vp.x + LAYOUT.powerReportLeft -- Metric dropdown @@ -3050,11 +3162,11 @@ function CompareTabClass:DrawSummary(vp, compareEntry) -- Position the list control (absolute screen coordinates). -- The list has a fixed height and its own internal scrollbar for rows. - -- Width matches the table columns (750) plus scrollbar (20px border/scroll area). + -- Width matches the table columns (720) plus scrollbar (20px border/scroll area). local listHeight = 250 - local listWidth = 770 + local listWidth = 740 listControl.x = vp.x + LAYOUT.powerReportLeft - listControl.y = vp.y + drawY + listControl.y = vp.y + headerReserve + drawY listControl.width = listWidth listControl.height = listHeight @@ -3541,30 +3653,24 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) -- Track item copy button clicks local clickedCopySlot = nil - local clickedCopyUseSlot = nil + local clickedEquipSlot = nil local clickedBuySlot = nil local clickedBuyItem = nil - -- Track Copy+Use button hover for stat comparison tooltip - local hoverCopyUseItem = nil - local hoverCopyUseSlotName = nil - local hoverCopyUseBtnX, hoverCopyUseBtnY = 0, 0 - local hoverCopyUseBtnW, hoverCopyUseBtnH = 0, 0 - - -- Headers - SetDrawColor(1, 1, 1) - DrawString(scrollOffsetX + 10, drawY, "LEFT", 18, "VAR", colorCodes.POSITIVE .. self:GetShortBuildName(self.primaryBuild.buildName)) - DrawString(scrollOffsetX + colWidth + 10, drawY, "LEFT", 18, "VAR", colorCodes.WARNING .. (compareEntry.label or "Compare Build")) - drawY = drawY + 24 + -- Track Equip button hover for stat comparison tooltip + local hoverEquipItem = nil + local hoverEquipSlotName = nil + local hoverEquipBtnX, hoverEquipBtnY = 0, 0 + local hoverEquipBtnW, hoverEquipBtnH = 0, 0 -- Helper: process copy/buy button hover state and click events for a slot. - -- Closes over hoverCopyUse*/clicked* locals above. - local function processSlotButtons(b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H, cItem, copySlotName, copyUseSlotName) + -- Closes over hoverEquip*/clicked* locals above. + local function processSlotButtons(b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H, cItem, copySlotName, equipSlotName) if b2Hover and cItem then - hoverCopyUseItem = cItem - hoverCopyUseSlotName = copyUseSlotName - hoverCopyUseBtnX, hoverCopyUseBtnY = b2X, b2Y - hoverCopyUseBtnW, hoverCopyUseBtnH = b2W, b2H + hoverEquipItem = cItem + hoverEquipSlotName = equipSlotName + hoverEquipBtnX, hoverEquipBtnY = b2X, b2Y + hoverEquipBtnW, hoverEquipBtnH = b2W, b2H end if cItem and inputEvents then for id, event in ipairs(inputEvents) do @@ -3573,10 +3679,10 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) clickedCopySlot = copySlotName inputEvents[id] = nil elseif b2Hover then - clickedCopyUseSlot = copyUseSlotName + clickedEquipSlot = equipSlotName inputEvents[id] = nil elseif b3Hover then - clickedBuySlot = copyUseSlotName + clickedBuySlot = equipSlotName clickedBuyItem = cItem inputEvents[id] = nil end @@ -3587,7 +3693,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) -- Helper: draw a single slot entry (expanded or compact mode). -- Closes over drawY, colWidth, cursorX/Y, vp, self, compareEntry, hoverItem/hoverX/Y/W/H/hoverItemsTab. - local function drawSlotEntry(label, pItem, cItem, copySlotName, copyUseSlotName, labelW, pWarn, cWarn, slotMissing) + local function drawSlotEntry(label, pItem, cItem, copySlotName, equipSlotName, labelW, pWarn, cWarn, slotMissing) if self.itemsExpandedMode then -- === EXPANDED MODE === SetDrawColor(1, 1, 1) @@ -3596,8 +3702,8 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) DrawString(scrollOffsetX + 10 + labelEndW + 8, drawY + 2, "LEFT", 14, "VAR", tradeHelpers.getSlotDiffLabel(pItem, cItem)) if cItem then - local b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H = tradeHelpers.drawCopyButtons(cursorX, cursorY, scrollOffsetX + contentWidth - 214, drawY + 21, slotMissing, LAYOUT.itemsCopyBtnW, LAYOUT.itemsCopyBtnH, LAYOUT.itemsBuyBtnW, LAYOUT.itemsCopyUseBtnW) - processSlotButtons(b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H, cItem, copySlotName, copyUseSlotName) + local b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H = tradeHelpers.drawCopyButtons(cursorX, cursorY, scrollOffsetX + contentWidth - 214, drawY + 21, slotMissing, LAYOUT.itemsCopyBtnW, LAYOUT.itemsCopyBtnH, LAYOUT.itemsBuyBtnW, LAYOUT.itemsEquipBtnW) + processSlotButtons(b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H, cItem, copySlotName, equipSlotName) end drawY = drawY + 20 @@ -3620,7 +3726,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) tradeHelpers.drawCompactSlotRow(drawY, label, pItem, cItem, colWidth, cursorX, cursorY, labelW, self.primaryBuild.itemsTab, compareEntry.itemsTab, pWarn, cWarn, slotMissing, - LAYOUT.itemsCopyBtnW, LAYOUT.itemsCopyBtnH, LAYOUT.itemsBuyBtnW, LAYOUT.itemsCopyUseBtnW, scrollOffsetX) + LAYOUT.itemsCopyBtnW, LAYOUT.itemsCopyBtnH, LAYOUT.itemsBuyBtnW, LAYOUT.itemsEquipBtnW, scrollOffsetX) if rowHoverItem then hoverItem = rowHoverItem @@ -3629,7 +3735,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) hoverW, hoverH = rowHoverW, rowHoverH end - processSlotButtons(b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H, cItem, copySlotName, copyUseSlotName) + processSlotButtons(b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H, cItem, copySlotName, equipSlotName) drawY = drawY + 20 end @@ -3694,8 +3800,8 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) -- Process item copy button clicks if clickedCopySlot then self:CopyCompareItemToPrimary(clickedCopySlot, compareEntry, false) - elseif clickedCopyUseSlot then - self:CopyCompareItemToPrimary(clickedCopyUseSlot, compareEntry, true) + elseif clickedEquipSlot then + self:CopyCompareItemToPrimary(clickedEquipSlot, compareEntry, true) end -- Process buy button click @@ -3704,29 +3810,32 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) end -- Draw item tooltip on hover (compact mode only, on top of everything) + SetViewport() + local maxTooltipWidth = m_min(600, m_max(260, vp.width - 24)) if hoverItem and hoverItemsTab then self.itemTooltip:Clear() - hoverItemsTab:AddItemTooltip(self.itemTooltip, hoverItem, nil) + hoverItemsTab:AddItemTooltip(self.itemTooltip, hoverItem, nil, nil, maxTooltipWidth) SetDrawLayer(nil, 100) - self.itemTooltip:Draw(hoverX, hoverY, hoverW, hoverH, vp) + self.itemTooltip:Draw(vp.x + hoverX, vp.y + checkboxOffset + hoverY, hoverW, hoverH, vp) SetDrawLayer(nil, 0) end - -- Draw stat comparison tooltip when hovering Copy+Use button - if hoverCopyUseItem and hoverCopyUseSlotName and not hoverItem then + -- Draw stat comparison tooltip when hovering Equip button + if hoverEquipItem and hoverEquipSlotName and not hoverItem then self.itemTooltip:Clear() + self.itemTooltip.maxWidth = maxTooltipWidth local calcFunc, calcBase = self.calcs.getMiscCalculator(self.primaryBuild) if calcFunc then -- Create a fresh item to evaluate - local newItem = new("Item", hoverCopyUseItem.raw) + local newItem = new("Item", hoverEquipItem.raw) newItem:NormaliseQuality() -- Determine what's currently in the target slot - local pSlot = self.primaryBuild.itemsTab.slots[hoverCopyUseSlotName] + local pSlot = self.primaryBuild.itemsTab.slots[hoverEquipSlotName] local selItem = pSlot and self.primaryBuild.itemsTab.items[pSlot.selItemId] -- For jewel sockets that aren't allocated, temporarily allocate the node - local override = { repSlotName = hoverCopyUseSlotName, repItem = newItem } + local override = { repSlotName = hoverEquipSlotName, repItem = newItem } if pSlot and pSlot.nodeId then local pSpec = self.primaryBuild.spec if pSpec and pSpec.allocNodes and not pSpec.allocNodes[pSlot.nodeId] then @@ -3738,7 +3847,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) end local output = calcFunc(override) - local slotLabel = pSlot and pSlot.label or hoverCopyUseSlotName + local slotLabel = pSlot and pSlot.label or hoverEquipSlotName local header if selItem then header = string.format("^7Equipping this item in %s will give you:\n(replacing %s%s^7)", slotLabel, colorCodes[selItem.rarity] or "^7", selItem.name) @@ -3754,7 +3863,7 @@ function CompareTabClass:DrawItems(vp, compareEntry, inputEvents) SetDrawLayer(nil, 100) -- Force tooltip to the left of the button by passing a large width -- so the right-side placement overflows and the Draw logic flips to left - self.itemTooltip:Draw(hoverCopyUseBtnX, hoverCopyUseBtnY, vp.width, hoverCopyUseBtnH, vp) + self.itemTooltip:Draw(vp.x + hoverEquipBtnX, vp.y + checkboxOffset + hoverEquipBtnY, vp.width, hoverEquipBtnH, vp) SetDrawLayer(nil, 0) end @@ -3915,8 +4024,8 @@ function CompareTabClass:DrawSkills(vp, compareEntry) elseif entry.gem then local gemName = entry.gem.grantedEffect and entry.gem.grantedEffect.name or entry.gem.nameSpec or "?" local gemColor = entry.gem.color or colorCodes.GEM - local levelStr = entry.gem.level and (" Lv" .. entry.gem.level) or "" - local qualStr = entry.gem.quality and entry.gem.quality > 0 and ("/" .. entry.gem.quality .. "q") or "" + local levelStr = entry.gem.level and (" " .. entry.gem.level) or "" + local qualStr = entry.gem.quality and ("/" .. entry.gem.quality .. "") or "" local prefix = "" if entry.status == "additional" then prefix = colorCodes.POSITIVE .. "+ " @@ -4047,6 +4156,30 @@ function CompareTabClass:DrawSkills(vp, compareEntry) end return groupLabel end + local function getGroupSlotIcon(skillsTab, idx, group) + local groupList = skillsTab and skillsTab.controls and skillsTab.controls.groupList + if not groupList or not groupList.GetRowIcon or not group then + return nil + end + return groupList:GetRowIcon(1, idx, group) + end + local groupHeaderX = 10 + local groupHeaderIconSize = 16 + local groupHeaderIconGap = 2 + local groupHeaderTextIndent = groupHeaderIconSize + groupHeaderIconGap + local groupHeaderTextX = groupHeaderX + groupHeaderTextIndent + local function getGroupHeaderWidth(group, idx) + return groupHeaderTextX + DrawStringWidth(18, "VAR", "^7" .. getGroupLabel(group, idx)) + end + local function drawGroupHeader(skillsTab, group, idx, x, y) + local icon = getGroupSlotIcon(skillsTab, idx, group) + local textX = x + groupHeaderTextIndent + if icon then + SetDrawColor(1, 1, 1) + DrawImage(icon, x, y, groupHeaderIconSize, groupHeaderIconSize) + end + DrawString(textX, y, "LEFT", 18, "VAR", "^7" .. getGroupLabel(group, idx)) + end local displayListsByPair = {} local maxPrimaryW = 0 @@ -4059,12 +4192,12 @@ function CompareTabClass:DrawSkills(vp, compareEntry) displayListsByPair[idx] = { p = pDisplay, c = cDisplay } if pGroup then - local w = 10 + DrawStringWidth(16, "VAR", "^7" .. getGroupLabel(pGroup, pair.pIdx)) + local w = getGroupHeaderWidth(pGroup, pair.pIdx) if w > maxPrimaryW then maxPrimaryW = w end end for _, entry in ipairs(pDisplay) do local line = buildGemDisplayString(entry) - local w = 20 + DrawStringWidth(gemFontSize, "VAR", line) + local w = groupHeaderTextX + DrawStringWidth(gemFontSize, "VAR", line) if w > maxPrimaryW then maxPrimaryW = w end end end @@ -4076,7 +4209,8 @@ function CompareTabClass:DrawSkills(vp, compareEntry) local colWidth = maxPrimaryW + LAYOUT.compareColGap local contentWidth = colWidth * 2 local needsHScroll = contentWidth > vp.width - local gemTextWidth = colWidth - 30 + local gemTextWidth = colWidth - (groupHeaderTextX + 10) + local headerReserve = LAYOUT.skillsHeaderHeight -- Configure horizontal scrollbar local hBar = self.controls.skillsHScrollBar @@ -4087,29 +4221,32 @@ function CompareTabClass:DrawSkills(vp, compareEntry) self.skillsScrollX = hBar.offset local bottomReserve = needsHScroll and LAYOUT.skillsHScrollBarHeight or 0 - local scrollViewH = vp.height - bottomReserve - SetViewport(vp.x, vp.y, vp.width, scrollViewH) - local drawY = 4 - self.scrollY + local scrollViewH = vp.height - headerReserve - bottomReserve local scrollOffsetX = -self.skillsScrollX - -- Headers SetDrawColor(1, 1, 1) - DrawString(scrollOffsetX + 10, drawY, "LEFT", 18, "VAR", colorCodes.POSITIVE .. self:GetShortBuildName(self.primaryBuild.buildName)) - DrawString(scrollOffsetX + colWidth + 10, drawY, "LEFT", 18, "VAR", colorCodes.WARNING .. (compareEntry.label or "Compare Build")) - drawY = drawY + 24 + DrawString(vp.x + scrollOffsetX + 10, vp.y + 4, "LEFT", 18, "VAR", colorCodes.POSITIVE .. self:GetShortBuildName(self.primaryBuild.buildName)) + DrawString(vp.x + scrollOffsetX + colWidth + 10, vp.y + 4, "LEFT", 18, "VAR", colorCodes.WARNING .. (compareEntry.label or "Compare Build")) + SetDrawColor(0.5, 0.5, 0.5) + DrawImage(nil, vp.x + 4, vp.y + headerReserve, vp.width - 8, 2) + + SetViewport(vp.x, vp.y + headerReserve, vp.width, scrollViewH) + local drawY = 4 - self.scrollY -- Position pre-pass: compute gem positions for hover hit-testing local gemEntries = {} -- { gem, x, y, group } - local preY = 4 - self.scrollY + 24 -- after headers + local preY = 4 - self.scrollY for idx, pair in ipairs(renderPairs) do - preY = preY + 2 -- separator + if idx > 1 then + preY = preY + 2 + end local pDisplayList = displayListsByPair[idx].p local cDisplayList = displayListsByPair[idx].c local pGroup = pair.pIdx and pGroups[pair.pIdx] local cGroup = pair.cIdx and cGroups[pair.cIdx] - local pGemY = collectGemEntries(gemEntries, pDisplayList, scrollOffsetX + 20, preY + lineHeight, pGroup) - local cGemY = collectGemEntries(gemEntries, cDisplayList, scrollOffsetX + colWidth + 20, preY + lineHeight, cGroup) + local pGemY = collectGemEntries(gemEntries, pDisplayList, scrollOffsetX + groupHeaderTextX, preY + lineHeight, pGroup) + local cGemY = collectGemEntries(gemEntries, cDisplayList, scrollOffsetX + colWidth + groupHeaderTextX, preY + lineHeight, cGroup) preY = preY + m_max(pGemY - preY, cGemY - preY) + 6 end @@ -4117,9 +4254,9 @@ function CompareTabClass:DrawSkills(vp, compareEntry) -- Hit-test: find hovered gem local cursorX, cursorY = GetCursorPos() local localCursorX = cursorX - vp.x - local localCursorY = cursorY - vp.y + local localCursorY = cursorY - (vp.y + headerReserve) local hoveredEntry = nil - if localCursorX >= 0 and localCursorX < vp.width and localCursorY >= 0 and localCursorY < vp.height then + if localCursorX >= 0 and localCursorX < vp.width and localCursorY >= 0 and localCursorY < scrollViewH then for _, entry in ipairs(gemEntries) do if localCursorX >= entry.x and localCursorX < entry.x + gemTextWidth and localCursorY >= entry.y and localCursorY < entry.y + gemLineHeight then @@ -4149,10 +4286,12 @@ function CompareTabClass:DrawSkills(vp, compareEntry) -- Draw pass for idx, pair in ipairs(renderPairs) do - SetDrawColor(0.3, 0.3, 0.3) - -- Divider spans the viewport width, independent of horizontal scroll - DrawImage(nil, 0, drawY, vp.width, 1) - drawY = drawY + 2 + if idx > 1 then + SetDrawColor(0.3, 0.3, 0.3) + -- Divider spans the viewport width, independent of horizontal scroll + DrawImage(nil, 0, drawY, vp.width, 1) + drawY = drawY + 2 + end local pDisplayList = displayListsByPair[idx].p local cDisplayList = displayListsByPair[idx].c @@ -4163,14 +4302,14 @@ function CompareTabClass:DrawSkills(vp, compareEntry) local cGroup = pair.cIdx and cGroups[pair.cIdx] if pGroup then - DrawString(scrollOffsetX + 10, drawY, "LEFT", 16, "VAR", "^7" .. getGroupLabel(pGroup, pair.pIdx)) + drawGroupHeader(pSkillsTab, pGroup, pair.pIdx, scrollOffsetX + groupHeaderX, drawY) end if cGroup then - DrawString(scrollOffsetX + colWidth + 10, drawY, "LEFT", 16, "VAR", "^7" .. getGroupLabel(cGroup, pair.cIdx)) + drawGroupHeader(cSkillsTab, cGroup, pair.cIdx, scrollOffsetX + colWidth + groupHeaderX, drawY) end - pFinalGemY = drawGemList(pDisplayList, scrollOffsetX + 20, drawY + lineHeight, highlightSet, gemTextWidth) - cFinalGemY = drawGemList(cDisplayList, scrollOffsetX + colWidth + 20, drawY + lineHeight, highlightSet, gemTextWidth) + pFinalGemY = drawGemList(pDisplayList, scrollOffsetX + groupHeaderTextX, drawY + lineHeight, highlightSet, gemTextWidth) + cFinalGemY = drawGemList(cDisplayList, scrollOffsetX + colWidth + groupHeaderTextX, drawY + lineHeight, highlightSet, gemTextWidth) drawY = drawY + m_max(pFinalGemY - drawY, cFinalGemY - drawY) + 6 end @@ -4266,10 +4405,10 @@ function CompareTabClass:DrawCalcsSkillHeader(vp, compareEntry, headerHeight, pr -- Text info lines (Aura/Buffs, Combat Buffs, Curses) local textY = m_max(leftY, rightY) + 2 - local pOutput = primaryEnv.player and primaryEnv.player.output - local cOutput = compareEnv.player and compareEnv.player.output + local primaryOutput = primaryEnv.player and primaryEnv.player.output + local compareOutput = compareEnv.player and compareEnv.player.output self.calcsSkillHeaderHover = nil -- Reset hover state - if pOutput or cOutput then + if primaryOutput or compareOutput then local cursorX, cursorY = GetCursorPos() local wrapWidth = colWidth - 8 local infoLines = { @@ -4278,42 +4417,42 @@ function CompareTabClass:DrawCalcsSkillHeader(vp, compareEntry, headerHeight, pr { label = "Curses/Debuffs", key = "CurseList", breakdown = "SkillDebuffs" }, } for _, info in ipairs(infoLines) do - local pVal = pOutput and pOutput[info.key] - local cVal = cOutput and cOutput[info.key] - if (pVal and pVal ~= "") or (cVal and cVal ~= "") then - local pLines = (pVal and pVal ~= "") and wrapInfoLine(info.label .. ": " .. pVal, wrapWidth) or {} - local cLines = (cVal and cVal ~= "") and wrapInfoLine(info.label .. ": " .. cVal, wrapWidth) or {} - local pH = #pLines * 18 - local cH = #cLines * 18 - local rowH = m_max(pH, cH, 18) + local primaryValue = primaryOutput and primaryOutput[info.key] + local compareValue = compareOutput and compareOutput[info.key] + if (primaryValue and primaryValue ~= "") or (compareValue and compareValue ~= "") then + local primaryLines = (primaryValue and primaryValue ~= "") and wrapInfoLine(info.label .. ": " .. primaryValue, wrapWidth) or {} + local compareLines = (compareValue and compareValue ~= "") and wrapInfoLine(info.label .. ": " .. compareValue, wrapWidth) or {} + local primaryHeight = #primaryLines * 18 + local compareHeight = #compareLines * 18 + local rowH = m_max(primaryHeight, compareHeight, 18) -- Check hover per-side for lines that have breakdown data if info.breakdown and cursorY >= textY and cursorY < textY + rowH then - local onLeft = cursorX >= leftX and cursorX < rightX and pH > 0 and cursorY < textY + pH - local onRight = cursorX >= rightX and cursorX < vp.x + vp.width and cH > 0 and cursorY < textY + cH + local onLeft = cursorX >= leftX and cursorX < rightX and primaryHeight > 0 and cursorY < textY + primaryHeight + local onRight = cursorX >= rightX and cursorX < vp.x + vp.width and compareHeight > 0 and cursorY < textY + compareHeight if onLeft then SetDrawColor(0.15, 0.25, 0.15) - DrawImage(nil, leftX, textY, colWidth, pH) + DrawImage(nil, leftX, textY, colWidth, primaryHeight) self.calcsSkillHeaderHover = { breakdown = info.breakdown, label = info.label, build = self.primaryBuild, - x = leftX, y = textY, w = colWidth, h = pH, + x = leftX, y = textY, w = colWidth, h = primaryHeight, } elseif onRight then SetDrawColor(0.15, 0.25, 0.15) - DrawImage(nil, rightX, textY, colWidth, cH) + DrawImage(nil, rightX, textY, colWidth, compareHeight) self.calcsSkillHeaderHover = { breakdown = info.breakdown, label = info.label, build = compareEntry, - x = rightX, y = textY, w = colWidth, h = cH, + x = rightX, y = textY, w = colWidth, h = compareHeight, } end end - for i, line in ipairs(pLines) do + for i, line in ipairs(primaryLines) do DrawString(leftX, textY + 1 + (i - 1) * 18, "LEFT", 14, "VAR", "^7" .. line) end - for i, line in ipairs(cLines) do + for i, line in ipairs(compareLines) do DrawString(rightX, textY + 1 + (i - 1) * 18, "LEFT", 14, "VAR", "^7" .. line) end textY = textY + rowH @@ -4323,7 +4462,52 @@ function CompareTabClass:DrawCalcsSkillHeader(vp, compareEntry, headerHeight, pr -- Separator line SetDrawColor(0.4, 0.4, 0.4) - DrawImage(nil, vp.x + 2, vp.y + headerHeight - 2, vp.width - 4, 1) + DrawImage(nil, vp.x + 2, vp.y + headerHeight - 2, vp.width - 4, 2) +end + +local function calcRowMatchesBetweenBuilds(self, colData, primaryActor, compareActor, compareEntry) + if not colData or not colData.format then return false end + local primaryFormatOk, primaryFormattedValue = pcall(formatCalcStr, colData.format, primaryActor, colData) + local compareFormatOk, compareFormattedValue = pcall(formatCalcStr, colData.format, compareActor, colData) + if not primaryFormatOk or not compareFormatOk or tostring(primaryFormattedValue or "") ~= tostring(compareFormattedValue or "") then + return false + end + for _, sectionData in ipairs(colData) do + if sectionData.modName then -- Compare mod rows to see if they match + local primaryRows = calcsHelpers.TabulateMods(sectionData, primaryActor) + local compareRows = calcsHelpers.TabulateMods(sectionData, compareActor) + if #primaryRows ~= #compareRows then return false end + local counts = {} + for _, row in ipairs(primaryRows) do + local key = calcsHelpers.ModRowKey(row) .. "|" .. tostring(row.value) + counts[key] = (counts[key] or 0) + 1 + end + for _, row in ipairs(compareRows) do + local key = calcsHelpers.ModRowKey(row) .. "|" .. tostring(row.value) + local count = counts[key] + if not count then return false end + counts[key] = count > 1 and count - 1 or nil + end + end + if sectionData.breakdown then -- Compare breakdown to see if they are the same + local primaryBreakdownLines = calcsHelpers.GetBreakdownLines(sectionData, self.primaryBuild) + local compareBreakdownLines = calcsHelpers.GetBreakdownLines(sectionData, compareEntry) + local primaryLineCount = primaryBreakdownLines and #primaryBreakdownLines or 0 + local compareLineCount = compareBreakdownLines and #compareBreakdownLines or 0 + if primaryLineCount ~= compareLineCount then return false end + for i = 1, primaryLineCount do + if primaryBreakdownLines[i] ~= compareBreakdownLines[i] then return false end + end + end + end + return true +end + +local function subSectionExtraMatches(subSecData, primaryActor, compareActor) + if not subSecData or not subSecData.extra then return true end + local primaryExtraOk, primaryExtraText = pcall(formatCalcStr, subSecData.extra, primaryActor) + local compareExtraOk, compareExtraText = pcall(formatCalcStr, subSecData.extra, compareActor) + return primaryExtraOk and compareExtraOk and tostring(primaryExtraText or "") == tostring(compareExtraText or "") end function CompareTabClass:DrawCalcs(vp, compareEntry) @@ -4360,7 +4544,8 @@ function CompareTabClass:DrawCalcs(vp, compareEntry) local maxCol = m_max(1, m_floor(gridWidth / (cardWidth + 8))) local baseX = 4 local headerBarHeight = LAYOUT.calcsHeaderBarHeight - local baseY = headerBarHeight + local filterRowOffset = self.controls.calcsShowOnlyDifferencesCheck:IsShown() and 12 or 0 + local baseY = headerBarHeight + filterRowOffset -- Pre-compute section visibility and heights local sections = {} @@ -4376,12 +4561,21 @@ function CompareTabClass:DrawCalcs(vp, compareEntry) for _, rowData in ipairs(subSec.data) do -- Only include rows with a label and a first column with a format string if rowData.label and rowData[1] and rowData[1].format then - if self.primaryBuild.calcsTab:CheckFlag(rowData, primaryActor) or self.primaryBuild.calcsTab:CheckFlag(rowData, compareActor) then - t_insert(rows, rowData) + local primaryVisible = self.primaryBuild.calcsTab:CheckFlag(rowData, primaryActor) + local compareVisible = self.primaryBuild.calcsTab:CheckFlag(rowData, compareActor) + if primaryVisible or compareVisible then + local keepRow = true + if self.calcsShowOnlyDifferences and primaryVisible and compareVisible then + keepRow = not calcRowMatchesBetweenBuilds(self, rowData[1], primaryActor, compareActor, compareEntry) + end + if keepRow then + t_insert(rows, rowData) + end end end end - if #rows > 0 then + local keepSubSection = #rows > 0 or (self.calcsShowOnlyDifferences and not subSectionExtraMatches(subSec.data, primaryActor, compareActor)) + if keepSubSection then t_insert(subSecInfo, { label = subSec.label, rows = rows, data = subSec.data }) sectionHasRows = true end @@ -4467,11 +4661,11 @@ function CompareTabClass:DrawCalcs(vp, compareEntry) if subSec.data and subSec.data.extra then local extraTextW = DrawStringWidth(16, "VAR BOLD", subSec.label .. ":") local extraX = x + 3 + extraTextW + 8 - local ok1, pExtra = pcall(formatCalcStr, subSec.data.extra, primaryActor) - local ok2, cExtra = pcall(formatCalcStr, subSec.data.extra, compareActor) - if ok1 and ok2 then + local primaryExtraOk, primaryExtraText = pcall(formatCalcStr, subSec.data.extra, primaryActor) + local compareExtraOk, compareExtraText = pcall(formatCalcStr, subSec.data.extra, compareActor) + if primaryExtraOk and compareExtraOk then DrawString(extraX, lineY + 3, "LEFT", 16, "VAR", - colorCodes.POSITIVE .. pExtra .. " ^8| " .. colorCodes.WARNING .. cExtra) + colorCodes.POSITIVE .. primaryExtraText .. " ^8| " .. colorCodes.WARNING .. compareExtraText) end end -- Separator below header diff --git a/src/Classes/CompareTradeHelpers.lua b/src/Classes/CompareTradeHelpers.lua index bfbf64be04..eb1802348d 100644 --- a/src/Classes/CompareTradeHelpers.lua +++ b/src/Classes/CompareTradeHelpers.lua @@ -249,15 +249,15 @@ function M.getSlotDiffLabel(pItem, cItem) end end --- Helper: draw Copy, Copy+Use, and Buy buttons at the given position. +-- Helper: draw Copy, Equip, and Buy buttons at the given position. -- btnStartX is the left edge where the first button (Buy) should appear. -- copyBtnW, copyBtnH, buyBtnW are button dimensions (passed from LAYOUT by caller). --- Returns copyHovered, copyUseHovered, buyHovered booleans. -function M.drawCopyButtons(cursorX, cursorY, btnStartX, btnY, slotMissing, copyBtnW, copyBtnH, buyBtnW, copyUseBtnW) +-- Returns copyHovered, equipHovered, buyHovered booleans. +function M.drawCopyButtons(cursorX, cursorY, btnStartX, btnY, slotMissing, copyBtnW, copyBtnH, buyBtnW, equipBtnW) local btnW = copyBtnW local btnH = copyBtnH local buyW = buyBtnW - local copyUseW = copyUseBtnW + local equipW = equipBtnW local btn3X = btnStartX local btn1X = btn3X + buyW + 4 local btn2X = btn1X + btnW + 4 @@ -297,18 +297,18 @@ function M.drawCopyButtons(cursorX, cursorY, btnStartX, btnY, slotMissing, copyB local b2Hover if slotMissing then - -- Show "Missing slot" label instead of Copy+Use button + -- Show "Missing slot" label instead of Equip button SetDrawColor(1, 1, 1) - DrawString(btn2X + copyUseW / 2, btnY + 1, "CENTER_X", 14, "VAR", "^xBBBBBBMissing slot") + DrawString(btn2X + equipW / 2, btnY + 1, "CENTER_X", 14, "VAR", "^xBBBBBBMissing slot") b2Hover = false else - -- "Copy+Use" button - b2Hover = cursorX >= btn2X and cursorX < btn2X + copyUseW + -- "Equip" button + b2Hover = cursorX >= btn2X and cursorX < btn2X + equipW and cursorY >= btnY and cursorY < btnY + btnH - drawBtn(btn2X, copyUseW, b2Hover, "^7Copy+Use") + drawBtn(btn2X, equipW, b2Hover, "^7Equip") end - return b1Hover, b2Hover, b3Hover, btn2X, btnY, copyUseW, btnH + return b1Hover, b2Hover, b3Hover, btn2X, btnY, equipW, btnH end -- Helper: fit a colored item name within maxW pixels, truncating with "..." if needed. @@ -338,7 +338,7 @@ local ITEM_BOX_H = 20 function M.drawCompactSlotRow(drawY, slotLabel, pItem, cItem, colWidth, cursorX, cursorY, maxLabelW, primaryItemsTab, compareItemsTab, pWarn, cWarn, slotMissing, - copyBtnW, copyBtnH, buyBtnW, copyUseBtnW, xOffset) + copyBtnW, copyBtnH, buyBtnW, equipBtnW, xOffset) xOffset = xOffset or 0 local pName = pItem and pItem.name or "(empty)" @@ -396,7 +396,7 @@ function M.drawCompactSlotRow(drawY, slotLabel, pItem, cItem, if cItem then local btnStartX = cBoxX + cBoxW + 6 b1Hover, b2Hover, b3Hover, b2X, b2Y, b2W, b2H = - M.drawCopyButtons(cursorX, cursorY, btnStartX, drawY + 1, slotMissing, copyBtnW, copyBtnH, buyBtnW, copyUseBtnW) + M.drawCopyButtons(cursorX, cursorY, btnStartX, drawY + 1, slotMissing, copyBtnW, copyBtnH, buyBtnW, equipBtnW) end -- Determine hovered item and tooltip anchor position diff --git a/src/Classes/ItemsTab.lua b/src/Classes/ItemsTab.lua index c0fa7b14d5..3825af2d33 100644 --- a/src/Classes/ItemsTab.lua +++ b/src/Classes/ItemsTab.lua @@ -3394,12 +3394,12 @@ function ItemsTabClass:FormatItemSource(text) :gsub("prophecy{([^}]+)}",colorCodes.PROPHECY.."%1"..colorCodes.SOURCE) end -function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode) +function ItemsTabClass:AddItemTooltip(tooltip, item, slot, dbMode, maxWidth) local fontSizeSmall = main.showFlavourText and 16 or 14 local fontSizeBig = main.showFlavourText and 18 or 16 local fontSizeTitle = main.showFlavourText and 22 or 20 local rarityCode = colorCodes[item.rarity] - tooltip.maxWidth = 600 -- Should instead get the longest mod and set the width to that. Some flavour text is way too long so we need a cap of sorts. + tooltip.maxWidth = m_min(maxWidth or 600, 600) -- Cap very long lines. Can use a narrower width for small viewports tooltip.tooltipHeader = item.rarity tooltip.foilType = item.foilType tooltip.center = true