diff --git a/src/Assets/ui_check.png b/src/Assets/ui_check.png new file mode 100644 index 0000000000..a04708a746 Binary files /dev/null and b/src/Assets/ui_check.png differ diff --git a/src/Assets/ui_round.png b/src/Assets/ui_round.png new file mode 100644 index 0000000000..c8ae7645b5 Binary files /dev/null and b/src/Assets/ui_round.png differ diff --git a/src/Classes/ButtonControl.lua b/src/Classes/ButtonControl.lua index e231280f5e..9d151dfffd 100644 --- a/src/Classes/ButtonControl.lua +++ b/src/Classes/ButtonControl.lua @@ -44,41 +44,26 @@ function ButtonClass:Draw(viewPort, noTooltip) local enabled = self:IsEnabled() local mOver = self:IsMouseOver() local locked = self:GetProperty("locked") - if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif mOver or locked then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - if not enabled then - SetDrawColor(0, 0, 0) - elseif self.clicked and mOver then - SetDrawColor(0.5, 0.5, 0.5) - elseif mOver or locked then - SetDrawColor(0.33, 0.33, 0.33) - else - SetDrawColor(0, 0, 0) + local colors = ui.colors + local radius = ui.FitRadius(ui.radius, width, height) + local border, fill = ui.SurfaceColors(enabled, mOver or locked, self.clicked and mOver) + if locked and enabled then + border = colors.borderActive end - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + ui.DrawBox(x, y, width, height, radius, border, fill) if self.image then if enabled then SetDrawColor(1, 1, 1) else - SetDrawColor(0.33, 0.33, 0.33) + SetDrawColor(0.4, 0.4, 0.4) end DrawImage(self.image, x + 2, y + 2, width - 4, height - 4) if self.clicked and mOver then - SetDrawColor(1, 1, 1, 0.5) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + SetDrawColor(1, 1, 1, 0.25) + ui.DrawRect(x + 1, y + 1, width - 2, height - 2, radius - 1) end end - if enabled then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.33, 0.33, 0.33) - end + ui.SetColor(enabled and colors.text or colors.textDisabled) local label = self:GetProperty("label") if label == "+" then DrawImage(nil, x + width * 0.2, y + height * 0.45, width * 0.6, height * 0.1) diff --git a/src/Classes/CheckBoxControl.lua b/src/Classes/CheckBoxControl.lua index 048d2582a2..c0967e4f71 100644 --- a/src/Classes/CheckBoxControl.lua +++ b/src/Classes/CheckBoxControl.lua @@ -42,42 +42,29 @@ function CheckBoxClass:Draw(viewPort, noTooltip) local size = self.width local enabled = self:IsEnabled() local mOver = self:IsMouseOver() - if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif mOver then - SetDrawColor(1, 1, 1) - elseif self.borderFunc then + local colors = ui.colors + local radius = ui.FitRadius(ui.radiusSmall, size, size) + local border, fill = ui.SurfaceColors(enabled, mOver, self.clicked and mOver) + if self.borderFunc and enabled and not mOver then local r, g, b = self.borderFunc() - SetDrawColor(r, g, b) - else - SetDrawColor(0.5, 0.5, 0.5) + border = ui.PackColor(r, g, b) end - DrawImage(nil, x, y, size, size) - if not enabled then - SetDrawColor(0, 0, 0) - elseif self.clicked and mOver then - SetDrawColor(0.5, 0.5, 0.5) - elseif mOver then - SetDrawColor(0.33, 0.33, 0.33) - else - SetDrawColor(0, 0, 0) + -- A ticked box is filled with the emphasis colour and carries a dark check mark, like the + -- unticked box inverted + if self.state and enabled then + border = mOver and colors.primaryHover or colors.primary + fill = border end - DrawImage(nil, x + 1, y + 1, size - 2, size - 2) + ui.DrawBox(x, y, size, size, radius, border, fill) if self.state then if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif mOver then - SetDrawColor(1, 1, 1) + ui.SetColor(colors.textDisabled) else - SetDrawColor(0.75, 0.75, 0.75) + ui.SetColor(colors.onPrimary) end - main:DrawCheckMark(x + size/2, y + size/2, size * 0.8) - end - if enabled then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.33, 0.33, 0.33) + main:DrawCheckMark(x + size/2, y + size/2, size * 0.7) end + ui.SetColor(enabled and colors.text or colors.textDisabled) local label = self:GetProperty("label") if label and self.labelRight then DrawString(x + self.width + 5, y + 2, nil, size - 4, "VAR", label) diff --git a/src/Classes/DropDownControl.lua b/src/Classes/DropDownControl.lua index bb999c49a4..fdd2e41e11 100644 --- a/src/Classes/DropDownControl.lua +++ b/src/Classes/DropDownControl.lua @@ -8,6 +8,9 @@ local m_min = math.min local m_max = math.max local m_floor = math.floor +-- Text sits in from the left edge rather than against it, so the label has room to breathe +local textInset = 6 + ---@class DropDownControl: Control, ControlHost, TooltipHost, SearchHost local DropDownClass = newClass("DropDownControl", "Control", "ControlHost", "TooltipHost", "SearchHost") @@ -249,47 +252,30 @@ function DropDownClass:Draw(viewPort, noTooltip) local dropExtra = self.dropHeight + 4 scrollBar:SetContentDimension(lineHeight * self:GetDropCount(), self.dropHeight) local dropY = self.dropUp and y - dropExtra or y + height - if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif mOver or self.dropped then - SetDrawColor(1, 1, 1) - elseif self.borderFunc then + local colors = ui.colors + local radius = ui.FitRadius(ui.radius, width, height) + local border, fill = ui.SurfaceColors(enabled, mOver, self.dropped) + if self.borderFunc and enabled and not (mOver or self.dropped) then local r, g, b = self.borderFunc() - SetDrawColor(r, g, b) - else - SetDrawColor(0.5, 0.5, 0.5) + border = ui.PackColor(r, g, b) end - DrawImage(nil, x, y, width, height) + ui.DrawBox(x, y, width, height, radius, border, fill) if self.dropped then SetDrawLayer(nil, 5) - DrawImage(nil, x, dropY, self.droppedWidth, dropExtra) + ui.DrawBox(x, dropY, self.droppedWidth, dropExtra, ui.radiusLarge, colors.border, colors.popover) SetDrawLayer(nil, 0) end - if not enabled or self.dropped then - SetDrawColor(0, 0, 0) - elseif mOver then - SetDrawColor(0.33, 0.33, 0.33) - else - SetDrawColor(0, 0, 0) - end - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) if not enabled then - SetDrawColor(0.33, 0.33, 0.33) + ui.SetColor(colors.textDisabled) elseif mOver or self.dropped then - SetDrawColor(1, 1, 1) + ui.SetColor(colors.text) else - SetDrawColor(0.5, 0.5, 0.5) - end - main:DrawArrow(x + width - height/2, y + height/2, height/2, height/2, "DOWN") - if self.dropped then - SetDrawLayer(nil, 5) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, dropY + 1, self.droppedWidth - 2, dropExtra - 2) - SetDrawLayer(nil, 0) + ui.SetColor(colors.textMuted) end + main:DrawArrow(x + width - height/2, y + height/2, height/2 - 2, height/2 - 4, "DOWN") if self.otherDragSource then - SetDrawColor(0, 1, 0, 0.25) - DrawImage(nil, x, y, width, height) + ui.SetColor(colors.dropTarget, 0.25) + ui.DrawRect(x, y, width, height, radius) end -- draw dropdown bar @@ -303,9 +289,9 @@ function DropDownClass:Draw(viewPort, noTooltip) mOver and "BODY" or "OUT", self.selIndex, self.list[self.selIndex]) SetDrawLayer(nil, 0) end - SetDrawColor(1, 1, 1) + ui.SetColor(colors.text) else - SetDrawColor(0.66, 0.66, 0.66) + ui.SetColor(colors.textDisabled) end -- draw selected label or search term local selLabel = nil @@ -322,21 +308,15 @@ function DropDownClass:Draw(viewPort, noTooltip) end end SetViewport(x + 2, y + 2, width - height, lineHeight) - DrawString(0, 0, "LEFT", lineHeight, "VAR", selLabel or "") + DrawString(textInset, 0, "LEFT", lineHeight, "VAR", selLabel or "") if selDetail ~= nil then local dx = DrawStringWidth(lineHeight, "VAR", selDetail) - if not enabled or self.dropped then - SetDrawColor(0, 0, 0) - elseif mOver then - SetDrawColor(0.33, 0.33, 0.33) - else - SetDrawColor(0, 0, 0) - end + ui.SetColor(fill) DrawImage(nil, width - dx - 4 - 22, 0, width - 4, lineHeight) if enabled then - SetDrawColor(1, 1, 1) + ui.SetColor(colors.textMuted) else - SetDrawColor(0.66, 0.66, 0.66) + ui.SetColor(colors.textDisabled) end DrawString(width - dx - 22, 0, "LEFT", lineHeight, "VAR", selDetail) end @@ -376,14 +356,17 @@ function DropDownClass:Draw(viewPort, noTooltip) local y = (dropIndex - 1) * lineHeight - scrollBar.offset -- highlight background if hovered if index == self.hoverSel then - SetDrawColor(0.33, 0.33, 0.33) - DrawImage(nil, 0, y, width - 4, lineHeight) + ui.SetColor(colors.accent) + ui.DrawRect(0, y, width - 4, lineHeight, ui.radiusSmall) + elseif index == self.selIndex then + ui.SetColor(colors.accentSubtle) + ui.DrawRect(0, y, width - 4, lineHeight, ui.radiusSmall) end -- highlight font color if hovered or selected if index == self.hoverSel or index == self.selIndex then - SetDrawColor(1, 1, 1) + ui.SetColor(colors.text) else - SetDrawColor(0.66, 0.66, 0.66) + ui.SetColor(colors.textMuted) end -- draw actual item label with search match highlight if available local label = nil @@ -394,30 +377,32 @@ function DropDownClass:Draw(viewPort, noTooltip) else label = listVal end - DrawString(0, y, "LEFT", lineHeight, "VAR", label) + DrawString(textInset, y, "LEFT", lineHeight, "VAR", label) if detail ~= nil then local detail = listVal.detail local dx = DrawStringWidth(lineHeight, "VAR", detail) if index == self.hoverSel then - SetDrawColor(0.33, 0.33, 0.33) + ui.SetColor(colors.accent) + elseif index == self.selIndex then + ui.SetColor(colors.accentSubtle) else - SetDrawColor(0, 0, 0) + ui.SetColor(colors.popover) end DrawImage(nil, width - dx - 8 - 22, y, width - 4, lineHeight) -- highlight font color if hovered or selected if index == self.hoverSel or index == self.selIndex then - SetDrawColor(1, 1, 1) + ui.SetColor(colors.text) else - SetDrawColor(0.66, 0.66, 0.66) + ui.SetColor(colors.textMuted) end DrawString(width - dx - 4 - 22, y, "LEFT", lineHeight, "VAR", detail) end - self:DrawSearchHighlights(label, searchInfo, 0, y, width - 4, lineHeight) + self:DrawSearchHighlights(label, searchInfo, textInset, y, width - 4, lineHeight) end end SetDrawColor(1, 1, 1) if self:IsSearchActive() and self:GetMatchCount() == 0 then - DrawString(0, 0 , "LEFT", lineHeight, "VAR", "") + DrawString(textInset, 0 , "LEFT", lineHeight, "VAR", "") end SetViewport() SetDrawLayer(nil, 0) @@ -550,7 +535,7 @@ function DropDownClass:CheckDroppedWidth(enable) line = line.label or "" end -- +10 to stop clipping - dWidth = m_max(dWidth, DrawStringWidth(lineHeight, "VAR", line) + 10) + dWidth = m_max(dWidth, DrawStringWidth(lineHeight, "VAR", line) + 10 + textInset) end -- no greater than self.maxDroppedWidth self.droppedWidth = m_min(dWidth + scrollWidth, self.maxDroppedWidth) @@ -561,7 +546,7 @@ function DropDownClass:CheckDroppedWidth(enable) end -- add 20 to account for the 'down arrow' in the box local boxWidth - boxWidth = DrawStringWidth(lineHeight, "VAR", line or "") + 20 + boxWidth = DrawStringWidth(lineHeight, "VAR", line or "") + 20 + textInset self.width = m_max(m_min(boxWidth, 390), 190) end diff --git a/src/Classes/EditControl.lua b/src/Classes/EditControl.lua index 899f5b0bcf..53d33a74b2 100644 --- a/src/Classes/EditControl.lua +++ b/src/Classes/EditControl.lua @@ -9,6 +9,9 @@ local m_floor = math.floor local protected_replace = "*" local utf8 = require('lua-utf8') +-- Matches the drop down inset so fields and selects sitting side by side line up +local textInset = 6 + local function lastLine(str) local lastLineIndex = 1 while true do @@ -248,30 +251,25 @@ function EditClass:Draw(viewPort, noTooltip) local width, height = self:GetSize() local enabled = self:IsEnabled() local mOver = self:IsMouseOver() + local colors = ui.colors + local radius = ui.FitRadius(ui.radius, width, height) + local border = colors.border + local fill = colors.input if not enabled then - SetDrawColor(0.33, 0.33, 0.33) + border, fill = colors.borderDisabled, colors.surfaceDisabled + elseif self.hasFocus then + border, fill = colors.borderActive, colors.inputHover elseif mOver then - SetDrawColor(1, 1, 1) + border, fill = colors.borderHover, colors.inputHover elseif self.borderFunc then local r, g, b = self.borderFunc() - SetDrawColor(r, g, b) - else - SetDrawColor(0.5, 0.5, 0.5) + border = ui.PackColor(r, g, b) end - DrawImage(nil, x, y, width, height) - if not enabled then - SetDrawColor(0, 0, 0) - elseif self.hasFocus or mOver then - if self.lineHeight then - SetDrawColor(0.1, 0.1, 0.1) - else - SetDrawColor(0.15, 0.15, 0.15) - end - else - SetDrawColor(0, 0, 0) + if self.hasFocus and enabled then + ui.DrawFocusRing(x, y, width, height, radius) end - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) - local textX = x + 2 + ui.DrawBox(x, y, width, height, radius, border, fill) + local textX = x + 2 + textInset local textY = y + 2 local textHeight = self.lineHeight or (height - 4) if self.prompt then @@ -495,7 +493,7 @@ function EditClass:OnKeyDown(key, doubleClick) self.drag = true local x, y = self:GetPos() local width, height = self:GetSize() - local textX = x + 2 + local textX = x + 2 + textInset local textY = y + 2 local textHeight = self.lineHeight or (height - 4) if self.prompt then diff --git a/src/Classes/ListControl.lua b/src/Classes/ListControl.lua index 47dd9a8f90..170662ea4b 100644 --- a/src/Classes/ListControl.lua +++ b/src/Classes/ListControl.lua @@ -184,20 +184,14 @@ function ListClass:Draw(viewPort, noTooltip) if label then DrawString(x + self.labelPositionOffset[1], y - 20 + self.labelPositionOffset[2], "LEFT", 16, self.font, label) end + local colors = ui.colors + local border, fill = colors.border, colors.input if self.otherDragSource and not self.CanDragToValue then - SetDrawColor(0.2, 0.6, 0.2) + border, fill = colors.dropTarget, colors.dropTargetSurface elseif self.hasFocus then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - if self.otherDragSource and not self.CanDragToValue then - SetDrawColor(0, 0.05, 0) - else - SetDrawColor(0, 0, 0) + border = colors.borderActive end - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + ui.DrawBox(x, y, width, height, ui.radius, border, fill) self:DrawControls(viewPort, (noTooltip and not self.forceTooltip) and self) SetViewport(x + 2, y + 2, self.scroll and width - 20 or width, height - 4 - (self.scroll and self.scrollH and 16 or 0)) @@ -236,40 +230,22 @@ function ListClass:Draw(viewPort, noTooltip) ttWidth = m_max(textWidth + 8, relX - colOffset) end end - if self.showRowSeparators then - if self.hasFocus and value == self.selValue then - SetDrawColor(1, 1, 1) - elseif value == ttValue then - SetDrawColor(0.8, 0.8, 0.8) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, colOffset, lineY, not self.scroll and colWidth - 4 or colWidth, rowHeight) - if (value == self.selValue or value == ttValue) then - SetDrawColor(0.33, 0.33, 0.33) - elseif self.otherDragSource and self.CanDragToValue and self:CanDragToValue(index, value, self.otherDragSource) then - SetDrawColor(0, 0.2, 0) - elseif index % 2 == 0 then - SetDrawColor(0.05, 0.05, 0.05) - else - SetDrawColor(0, 0, 0) - end - DrawImage(nil, colOffset, lineY + 1, not self.scroll and colWidth - 4 or colWidth, rowHeight - 2) - elseif value == self.selValue or value == ttValue then - if self.hasFocus and value == self.selValue then - SetDrawColor(1, 1, 1) - elseif value == ttValue then - SetDrawColor(0.8, 0.8, 0.8) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, colOffset, lineY, not self.scroll and colWidth - 4 or colWidth, rowHeight) - if self.otherDragSource and self.CanDragToValue and self:CanDragToValue(index, value, self.otherDragSource) then - SetDrawColor(0, 0.2, 0) - else - SetDrawColor(0.15, 0.15, 0.15) - end - DrawImage(nil, colOffset, lineY + 1, not self.scroll and colWidth - 4 or colWidth, rowHeight - 2) + -- Rows are filled rather than outlined; columns are drawn one after another, so any + -- corner rounding here would show up as notches between them + local rowWidth = not self.scroll and colWidth - 4 or colWidth + local rowFill + if value == self.selValue then + rowFill = self.hasFocus and colors.accent or colors.accentSubtle + elseif value == ttValue then + rowFill = colors.accentSubtle + elseif self.otherDragSource and self.CanDragToValue and self:CanDragToValue(index, value, self.otherDragSource) then + rowFill = colors.dropTargetSurface + elseif self.showRowSeparators and index % 2 == 0 then + rowFill = colors.surface + end + if rowFill then + ui.SetColor(rowFill) + DrawImage(nil, colOffset, lineY + 1, rowWidth, rowHeight - 2) end if not self.SetHighlightColor or not self:SetHighlightColor(index, value) then SetDrawColor(1, 1, 1) @@ -285,33 +261,28 @@ function ListClass:Draw(viewPort, noTooltip) if self.colLabels then local mOver = relX >= colOffset and relX <= colOffset + colWidth and relY >= 0 and relY <= 18 if mOver and self:GetColumnProperty(column, "sortable") then - SetDrawColor(1, 1, 1) - DrawImage(nil, colOffset, 1, colWidth, 18) - SetDrawColor(0.33, 0.33, 0.33) - DrawImage(nil, colOffset + 1, 2, colWidth - 2, 16) + ui.SetColor(colors.accent) else - SetDrawColor(0.5, 0.5, 0.5) - DrawImage(nil, colOffset, 1, colWidth, 18) - SetDrawColor(0.15, 0.15, 0.15) - DrawImage(nil, colOffset + 1, 2, colWidth - 2, 16) + ui.SetColor(colors.surface) end + DrawImage(nil, colOffset, 1, colWidth - 1, 18) + ui.SetColor(colors.border) + DrawImage(nil, colOffset, 19, colWidth - 1, 1) local label = self:GetColumnProperty(column, "label") if label and #label > 0 then - SetDrawColor(1, 1, 1) + ui.SetColor(colors.textMuted) DrawString(colOffset + colWidth/2, 4, "CENTER_X", 12, "VAR", label) end end end if #self.list == 0 and self.defaultText then - SetDrawColor(1, 1, 1) + ui.SetColor(colors.textMuted) DrawString(2, 2, "LEFT", 14, self.font, self.defaultText) end if self.selDragIndex then local lineY = rowHeight * (self.selDragIndex - 1) - scrollOffsetV - SetDrawColor(1, 1, 1) - DrawImage(nil, 0, lineY - 1, width - 20, 3) - SetDrawColor(0, 0, 0) - DrawImage(nil, 0, lineY, width - 20, 1) + ui.SetColor(colors.primary) + ui.DrawRect(0, lineY - 1, width - 20, 2, 1) end SetViewport() diff --git a/src/Classes/PopupDialog.lua b/src/Classes/PopupDialog.lua index 1e54e8c695..d5825c2e5f 100644 --- a/src/Classes/PopupDialog.lua +++ b/src/Classes/PopupDialog.lua @@ -44,20 +44,15 @@ end function PopupDialogClass:Draw(viewPort) local x, y = self:GetPos() local width, height = self:GetSize() + local colors = ui.colors -- Draw dialog background - SetDrawColor(0.8, 0.8, 0.8) - DrawImage(nil, x, y, width, height) - SetDrawColor(0.1, 0.1, 0.1) - DrawImage(nil, x + 2, y + 2, width - 4, height - 4) + ui.DrawBox(x, y, width, height, ui.radiusLarge, colors.borderHover, colors.popover) -- Draw dialog title box local title = self:GetProperty("title") local titleWidth = DrawStringWidth(16, "VAR", title) local titleX = x + m_floor((width - titleWidth - 8) / 2) - SetDrawColor(1, 1, 1) - DrawImage(nil, titleX, y - 10, titleWidth + 8, 24) - SetDrawColor(0, 0, 0) - DrawImage(nil, titleX + 2, y - 8, titleWidth + 4, 20) - SetDrawColor(1, 1, 1) + ui.DrawBox(titleX - 4, y - 10, titleWidth + 16, 24, ui.radius, colors.borderHover, colors.popover) + ui.SetColor(colors.text) DrawString(titleX + 4, y - 7, "LEFT", 16, "VAR", title) if self.scrollBarFunc then self.scrollBarFunc() diff --git a/src/Classes/ScrollBarControl.lua b/src/Classes/ScrollBarControl.lua index 145fb94bfd..e9757d85fb 100644 --- a/src/Classes/ScrollBarControl.lua +++ b/src/Classes/ScrollBarControl.lua @@ -34,14 +34,11 @@ function ScrollBarClass:SetContentDimension(conDim, viewDim) self.offset = 0 else local width, height = self:GetSize() + local length = self.dir == "HORIZONTAL" and width or height self.enabled = true - if self.dir == "HORIZONTAL" then - self.knobDim = m_max(height, (width - height * 2) * viewDim / conDim) - self.knobTravel = (width - height * 2) - self.knobDim - else - self.knobDim = m_max(width, (height - width * 2) * viewDim / conDim) - self.knobTravel = (height - width * 2) - self.knobDim - end + -- The knob runs the whole length of the bar; there are no stepper buttons to make room for + self.knobDim = m_max(m_min(length, 24), length * viewDim / conDim) + self.knobTravel = length - self.knobDim self.offsetMax = conDim - viewDim self.offset = m_min(self.offset, self.offsetMax) end @@ -81,30 +78,14 @@ function ScrollBarClass:IsMouseOver() local mOver = cursorX >= x and cursorY >= y and cursorX < x + width and cursorY < y + height local mOverComp if mOver and self.enabled then - local relDim - local shortDim, longDim - if self.dir == "HORIZONTAL" then - relDim = cursorX - x - shortDim = height - longDim = width - else - relDim = cursorY - y - shortDim = width - longDim = height - end - if relDim < shortDim then - mOverComp = "UP" - elseif relDim >= longDim - shortDim then - mOverComp = "DOWN" + local relDim = self.dir == "HORIZONTAL" and cursorX - x or cursorY - y + local knobPos = self:GetKnobPosForOffset() + if relDim < knobPos then + mOverComp = "SLIDEUP" + elseif relDim >= knobPos + self.knobDim then + mOverComp = "SLIDEDOWN" else - local knobPos = self:GetKnobPosForOffset() - if relDim < shortDim + knobPos then - mOverComp = "SLIDEUP" - elseif relDim >= shortDim + knobPos + self.knobDim then - mOverComp = "SLIDEDOWN" - else - mOverComp = "KNOB" - end + mOverComp = "KNOB" end end return mOver, mOverComp @@ -141,9 +122,9 @@ function ScrollBarClass:Draw() self.holdPauseTime = nil end local time = now - self.holdTime - if self.holdComp == "UP" then + if self.holdComp == "SLIDEUP" then self:SetOffset(self.holdBase - m_ceil(time / 50) * self.step) - elseif self.holdComp == "DOWN" then + elseif self.holdComp == "SLIDEDOWN" then self:SetOffset(self.holdBase + m_ceil(time / 50) * self.step) end end @@ -151,108 +132,39 @@ function ScrollBarClass:Draw() self.holdPauseTime = GetTime() end end - -- Draw up/left button background - if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif mOverComp == "UP" then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - if dir == "HORIZONTAL" then - DrawImage(nil, x, y, height, height) - else - DrawImage(nil, x, y, width, width) - end - if enabled and mOverComp == "UP" then - SetDrawColor(0.33, 0.33, 0.33) - else - SetDrawColor(0, 0, 0) - end - if dir == "HORIZONTAL" then - DrawImage(nil, x + 1, y + 1, height - 2, height - 2) - else - DrawImage(nil, x + 1, y + 1, width - 2, width - 2) - end - -- Draw up/left arrow + -- Nothing to scroll, so nothing to show if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif mOverComp == "UP" then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - if dir == "HORIZONTAL" then - main:DrawArrow(x + height/2, y + height/2, height/2, height/2, "LEFT") - else - main:DrawArrow(x + width/2, y + width/2, width/2, width/2, "UP") - end - -- Draw down/right button background - if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif mOverComp == "DOWN" then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - if dir == "HORIZONTAL" then - DrawImage(nil, x + width - height, y, height, height) - else - DrawImage(nil, x, y + height - width, width, width) - end - if enabled and mOverComp == "DOWN" then - SetDrawColor(0.33, 0.33, 0.33) - else - SetDrawColor(0, 0, 0) - end - if dir == "HORIZONTAL" then - DrawImage(nil, x + width - height + 1, y + 1, height - 2, height - 2) - else - DrawImage(nil, x + 1, y + height - width + 1, width - 2, width - 2) - end - -- Draw down/right arrow - if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif mOverComp == "DOWN" then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) + return end - if dir == "HORIZONTAL" then - main:DrawArrow(x + width - height/2, y + height/2, height/2, height/2, "RIGHT") + local colors = ui.colors + local horizontal = dir == "HORIZONTAL" + -- Thickness of the bar across its short axis; the track is a thin rail down the middle of it, + -- while the full width stays clickable + local shortDim = horizontal and height or width + local active = self.dragging or mOver + local trackDim = m_max(4, shortDim - 8) + local trackOff = m_floor((shortDim - trackDim) / 2) + ui.SetColor(colors.accentSubtle, active and 0.9 or 0.55) + if horizontal then + ui.DrawRect(x, y + trackOff, width, trackDim, trackDim / 2) else - main:DrawArrow(x + width/2, y + height - width/2, width/2, width/2, "DOWN") + ui.DrawRect(x + trackOff, y, trackDim, height, trackDim / 2) end - -- Draw slide background - if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif self.dragging or mOverComp == "KNOB" or mOverComp == "SLIDEUP" or mOverComp == "SLIDEDOWN" then - SetDrawColor(1, 1, 1) + -- Knob, which fattens up while the bar is being used + if self.dragging or mOverComp == "KNOB" then + ui.SetColor(colors.borderActive) + elseif mOver then + ui.SetColor(colors.borderHover) else - SetDrawColor(0.5, 0.5, 0.5) + ui.SetColor(colors.border) end - if dir == "HORIZONTAL" then - DrawImage(nil, x + height, y, width - height * 2, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + height, y + 1, width - height * 2, height - 2) + local knobDim = active and m_max(6, shortDim - 3) or trackDim + local knobOff = m_floor((shortDim - knobDim) / 2) + local knobPos = m_floor(self:GetKnobPosForOffset()) + if horizontal then + ui.DrawRect(x + knobPos, y + knobOff, self.knobDim, knobDim, knobDim / 2) else - DrawImage(nil, x, y + width, width, height - width * 2) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + width, width - 2, height - width * 2) - end - -- Draw knob - if enabled then - if self.dragging or mOverComp == "KNOB" then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - local knobPos = self:GetKnobPosForOffset() - if dir == "HORIZONTAL" then - DrawImage(nil, x + height + knobPos + 1, y + 2, self.knobDim - 2, height - 4) - else - DrawImage(nil, x + 2, y + width + knobPos + 1, width - 4, self.knobDim - 2) - end + ui.DrawRect(x + knobOff, y + knobPos, knobDim, self.knobDim, knobDim / 2) end end @@ -273,20 +185,14 @@ function ScrollBarClass:OnKeyDown(key) self.dragCY = cursorY self.dragKnobPos = self:GetKnobPosForOffset() end - elseif mOverComp == "UP" then - self:Scroll(-1) - self.holdComp = "UP" - self.holdTime = GetTime() - self.holdBase = self.offset - elseif mOverComp == "DOWN" then - self:Scroll(1) - self.holdComp = "DOWN" + elseif mOverComp == "SLIDEUP" or mOverComp == "SLIDEDOWN" then + -- Clicking the track pages towards the cursor, and holding keeps scrolling that way + -- until the knob catches up + local step = mOverComp == "SLIDEUP" and -self.knobDim or self.knobDim + self:SetOffsetFromKnobPos(self:GetKnobPosForOffset() + step) + self.holdComp = mOverComp self.holdTime = GetTime() self.holdBase = self.offset - elseif mOverComp == "SLIDEUP" then - self:SetOffsetFromKnobPos(self:GetKnobPosForOffset() - self.knobDim) - elseif mOverComp == "SLIDEDOWN" then - self:SetOffsetFromKnobPos(self:GetKnobPosForOffset() + self.knobDim) end end return self diff --git a/src/Classes/SectionControl.lua b/src/Classes/SectionControl.lua index e0acb6fd21..8fc1ec202f 100644 --- a/src/Classes/SectionControl.lua +++ b/src/Classes/SectionControl.lua @@ -16,18 +16,15 @@ end function SectionClass:Draw() local x, y = self:GetPos() local width, height = self:GetSize() + local colors = ui.colors SetDrawLayer(nil, -10) - SetDrawColor(0.66, 0.66, 0.66) - DrawImage(nil, x, y, width, height) - SetDrawColor(0.1, 0.1, 0.1) - DrawImage(nil, x + 2, y + 2, width - 4, height - 4) + ui.DrawBox(x, y, width, height, ui.radiusLarge, colors.border, colors.panel) SetDrawLayer(nil, 0) + -- The label straddles the top edge, so it needs to punch a hole in the border it sits on local label = self:GetProperty("label") local labelWidth = DrawStringWidth(14, "VAR", label) - SetDrawColor(0.66, 0.66, 0.66) - DrawImage(nil, x + 6, y - 8, labelWidth + 6, 18) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 7, y - 7, labelWidth + 4, 16) - SetDrawColor(1, 1, 1) - DrawString(x + 9, y - 6, "LEFT", 14, "VAR", label) + ui.SetColor(colors.panel) + ui.DrawRect(x + 8, y - 8, labelWidth + 10, 18, ui.radiusSmall) + ui.SetColor(colors.text) + DrawString(x + 13, y - 6, "LEFT", 14, "VAR", label) end \ No newline at end of file diff --git a/src/Classes/SliderControl.lua b/src/Classes/SliderControl.lua index 89dba3e1fc..d8ee625d9d 100644 --- a/src/Classes/SliderControl.lua +++ b/src/Classes/SliderControl.lua @@ -88,36 +88,34 @@ function SliderClass:Draw(viewPort) self:SetValFromKnobX((cursorX - self.dragCX) + self.dragKnobX) end local mOver, mOverComp = self:IsMouseOver() - if not enabled then - SetDrawColor(0.33, 0.33, 0.33) - elseif self.dragging or mOver then - SetDrawColor(1, 1, 1) - else - SetDrawColor(0.5, 0.5, 0.5) - end - DrawImage(nil, x, y, width, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + local colors = ui.colors + local knobX = self:GetKnobXForVal() + -- Thin track running the length of the control, with the travelled part filled in + local trackHeight = m_max(4, m_ceil(height / 4)) + local trackY = y + m_ceil((height - trackHeight) / 2) + local trackRadius = trackHeight / 2 + ui.SetColor(enabled and colors.accent or colors.surfaceDisabled) + ui.DrawRect(x, trackY, width, trackHeight, trackRadius) if enabled then - if self.divCount then - SetDrawColor(0.33, 0.33, 0.33) - for d = 0, knobTravel + 0.5, knobTravel / self.divCount do - DrawImage(nil, x + self.knobSize/2 + d, y + 1, 2, height - 2) - end - end - if self.dragging or mOverComp == "KNOB" then - SetDrawColor(1, 1, 1) + if self.dragging or mOver then + ui.SetColor(colors.primaryHover) else - SetDrawColor(0.5, 0.5, 0.5) + ui.SetColor(colors.primary) end - local knobX = self:GetKnobXForVal() + ui.DrawRect(x, trackY, m_max(trackHeight, knobX + self.knobSize / 2), trackHeight, trackRadius) if self.divCount then - local arrowHeight = self.knobSize/2 - main:DrawArrow(x + 1 + knobX + self.knobSize/2, y + height/2 - arrowHeight/2, self.knobSize, arrowHeight, "UP") - main:DrawArrow(x + 1 + knobX + self.knobSize/2, y + height/2 + arrowHeight/2, self.knobSize, arrowHeight, "DOWN") - else - DrawImage(nil, x + 2 + knobX, y + 2, self.knobSize - 2, self.knobSize - 2) + ui.SetColor(colors.surface) + for d = 0, knobTravel + 0.5, knobTravel / self.divCount do + DrawImage(nil, x + self.knobSize/2 + d, trackY, 2, trackHeight) + end end + -- Round knob, outlined so it stays readable over the filled part of the track + local knobSize = self.knobSize + local knobY = y + (height - knobSize) / 2 + ui.SetColor((self.dragging or mOverComp == "KNOB") and colors.borderActive or colors.border) + ui.DrawCircle(x + 1 + knobX, knobY, knobSize) + ui.SetColor((self.dragging or mOverComp == "KNOB") and colors.primaryHover or colors.primary) + ui.DrawCircle(x + 2 + knobX, knobY + 1, knobSize - 2) end if enabled and (mOver or self.dragging) then SetDrawLayer(nil, 100) diff --git a/src/Classes/TextListControl.lua b/src/Classes/TextListControl.lua index 50e58a2d16..87ef5dc5fe 100644 --- a/src/Classes/TextListControl.lua +++ b/src/Classes/TextListControl.lua @@ -3,6 +3,10 @@ -- Class: Text List -- Simple list control for displaying a block of text -- +-- A line's height doubles as its font size, so grouped lists pad every row to buy the text some +-- breathing room without inflating it +local rowPad = 6 + ---@class TextListControl: Control, ControlHost local TextListClass = newClass("TextListControl", "Control", "ControlHost") @@ -20,6 +24,59 @@ function TextListClass:TextListControl(anchor, rect, columns, list, sectionHeigh return self end +---Height a line occupies, which is its font size plus the padding grouped lists add. Headers take +---double, to hold the rule under them clear of the card that follows. +function TextListClass:GetLineHeight(lineInfo) + if not self.grouped then + return lineInfo.height + end + return lineInfo.height + (lineInfo.header and rowPad * 2 or rowPad) +end + +-- Banding for lists that arrive as one long run of lines broken up by blank spacers, which is +-- hard to read at the length the side bar stat list reaches. Each run of lines between spacers +-- becomes a card, its rows divided by hairlines, and any line flagged as a header gets a rule +-- under it. Opt in with control.grouped, since the changelog and help lists want the plain +-- treatment. +function TextListClass:DrawGroups(contentWidth, offset) + local colors = ui.colors + -- Cards first, so the dividers land on top of them + local lineY = -offset + local groupStart = nil + local function endGroup(endY) + if groupStart and endY > groupStart then + ui.SetColor(colors.surface) + ui.DrawRect(0, groupStart - 2, contentWidth, endY - groupStart + 4, ui.radiusLarge) + end + groupStart = nil + end + for _, lineInfo in ipairs(self.list) do + if lineInfo.header or not lineInfo[1] then + endGroup(lineY) + else + groupStart = groupStart or lineY + end + lineY = lineY + self:GetLineHeight(lineInfo) + end + endGroup(lineY) + -- Rule under each header, hairline between the rows sharing a card + lineY = -offset + for index, lineInfo in ipairs(self.list) do + local lineHeight = self:GetLineHeight(lineInfo) + if lineInfo.header then + ui.SetColor(colors.border) + DrawImage(nil, 0, lineY + lineInfo.height + rowPad, contentWidth, 1) + elseif lineInfo[1] then + local nextLine = self.list[index + 1] + if nextLine and nextLine[1] and not nextLine.header then + ui.SetColor(colors.border, 0.45) + DrawImage(nil, 8, lineY + lineHeight - 1, contentWidth - 16, 1) + end + end + lineY = lineY + lineHeight + end +end + function TextListClass:IsMouseOver() if not self:IsShown() then return @@ -33,34 +90,46 @@ function TextListClass:Draw(viewPort) local scrollBar = self.controls.scrollBar local contentHeight = 0 for _, lineInfo in pairs(self.list) do - contentHeight = contentHeight + lineInfo.height + contentHeight = contentHeight + self:GetLineHeight(lineInfo) end scrollBar:SetContentDimension(contentHeight, height - 4) - SetDrawColor(0.66, 0.66, 0.66) - DrawImage(nil, x, y, width, height) - SetDrawColor(0.05, 0.05, 0.05) - DrawImage(nil, x + 1, y + 1, width - 2, height - 2) + ui.DrawBox(x, y, width, height, ui.radius, ui.colors.border, ui.colors.input) self:DrawControls(viewPort) SetViewport(x + 2, y + 2, width - 20, height - 4) + if self.grouped then + self:DrawGroups(width - 26, scrollBar.offset) + end + local textPad = self.grouped and rowPad / 2 or 0 + local valueCol = self.columns[2] for colIndex, colInfo in pairs(self.columns) do local lineY = -scrollBar.offset for _, lineInfo in ipairs(self.list) do - if lineInfo[colIndex] then - local textX = lineInfo.x or colInfo.x + local text = lineInfo[colIndex] + if text then + local font = lineInfo.font or "VAR" + local drawX = lineInfo.x or colInfo.x local align = lineInfo.align or colInfo.align - DrawString(textX, lineY, align, lineInfo.height, lineInfo.font or "VAR", lineInfo[colIndex]) + -- Clip a label that would otherwise run underneath its own value + if self.grouped and colIndex == 1 and valueCol and lineInfo[2] and not lineInfo.x then + local space = valueCol.x - drawX - DrawStringWidth(lineInfo.height, font, lineInfo[2]) - 8 + if DrawStringWidth(lineInfo.height, font, text) > space then + local clipWidth = DrawStringWidth(lineInfo.height, font, "..") + local clipIndex = DrawStringCursorIndex(lineInfo.height, font, text, space - clipWidth, 0) + text = text:sub(1, clipIndex - 1) .. ".." + end + end + DrawString(drawX, lineY + textPad, align, lineInfo.height, font, text) if lineInfo.underline and lineInfo.underline[colIndex] then - local width = DrawStringWidth(lineInfo.height, "VAR", StripEscapes(lineInfo[colIndex])) + -- measured off the text as drawn, so a clipped label keeps its rule the same length + local textWidth = DrawStringWidth(lineInfo.height, font, StripEscapes(text)) -- note: not fully handled. this is currently only used for -- the side bar stats - if align == "RIGHT_X" then - textX = textX - width - end - SetDrawColor(0.5, 0.5, 0.5) - DrawImage(nil, textX, lineY + lineInfo.height, width, 1) + local underlineX = align == "RIGHT_X" and drawX - textWidth or drawX + ui.SetColor(ui.colors.textMuted) + DrawImage(nil, underlineX, lineY + textPad + lineInfo.height, textWidth, 1) end end - lineY = lineY + lineInfo.height + lineY = lineY + self:GetLineHeight(lineInfo) end end -- determine which line the user is hovering over @@ -70,11 +139,12 @@ function TextListClass:Draw(viewPort) local rowY = y - scrollBar.offset + 2 -- suboptimal. should do binary search if this causes performance problems for _, lineInfo in ipairs(self.list) do - if cursorY >= rowY and cursorY < rowY + lineInfo.height then - self.hoveredLine = { line = lineInfo, x = x, y = rowY, width = width } + local lineHeight = self:GetLineHeight(lineInfo) + if cursorY >= rowY and cursorY < rowY + lineHeight then + self.hoveredLine = { line = lineInfo, x = x, y = rowY, width = width, height = lineHeight } break end - rowY = rowY + lineInfo.height + rowY = rowY + lineHeight end end SetViewport() diff --git a/src/Modules/Build.lua b/src/Modules/Build.lua index d080d84c90..5dc70e046b 100644 --- a/src/Modules/Build.lua +++ b/src/Modules/Build.lua @@ -157,10 +157,8 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin self.controls.buildName.Draw = function(control) local x, y = control:GetPos() local width, height = control:GetSize() - SetDrawColor(0.5, 0.5, 0.5) - DrawImage(nil, x + 91, y, self.strWidth + 6, 20) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 92, y + 1, self.strWidth + 4, 18) + -- Read-only, so it is framed like a disabled button to match the rest of the top bar + ui.DrawBox(x + 91, y, self.strWidth + 6, 20, ui.radius, ui.colors.borderDisabled, ui.colors.surfaceDisabled) SetDrawColor(1, 1, 1) SetViewport(x, y + 2, self.strWidth + 94, 16) DrawString(0, 0, "LEFT", 16, "VAR", "Current build: "..self.buildName) @@ -197,10 +195,8 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin self.controls.pointDisplay.Draw = function(control) local x, y = control:GetPos() local width, height = control:GetSize() - SetDrawColor(1, 1, 1) - DrawImage(nil, x, y, width + 2, height) - SetDrawColor(0, 0, 0) - DrawImage(nil, x + 1, y + 1, width, height - 2) + -- Read-only, so it is framed like a disabled button to match the rest of the top bar + ui.DrawBox(x, y, width + 2, height, ui.radius, ui.colors.borderDisabled, ui.colors.surfaceDisabled) SetDrawColor(1, 1, 1) DrawString(x + 4, y + 2, "LEFT", 16, "FIXED", control.str) if control:IsMouseInBounds() then @@ -477,7 +473,7 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin self.controls.modeCompare.locked = function() return self.viewMode == "COMPARE" end -- Skills self.controls.mainSkillLabel = new("LabelControl"):LabelControl({"TOPLEFT",self.anchorSideBar,"TOPLEFT"}, {0, 80, 300, 16}, "^7Main Skill:") - self.controls.mainSocketGroup = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSkillLabel,"BOTTOMLEFT"}, {0, 2, 300, 18}, nil, function(index, value) + self.controls.mainSocketGroup = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSkillLabel,"BOTTOMLEFT"}, {0, 4, 300, 20}, nil, function(index, value) self.mainSocketGroup = index self.modFlag = true self.buildFlag = true @@ -489,44 +485,44 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin self.skillsTab:AddSocketGroupTooltip(tooltip, socketGroup) end end - self.controls.mainSkill = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSocketGroup,"BOTTOMLEFT"}, {0, 2, 300, 18}, nil, function(index, value) + self.controls.mainSkill = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSocketGroup,"BOTTOMLEFT"}, {0, 4, 300, 20}, nil, function(index, value) local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup] mainSocketGroup.mainActiveSkill = index self.modFlag = true self.buildFlag = true end) - self.controls.mainSkillPart = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSkill,"BOTTOMLEFT",true}, {0, 2, 300, 18}, nil, function(index, value) + self.controls.mainSkillPart = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSkill,"BOTTOMLEFT",true}, {0, 4, 300, 20}, nil, function(index, value) local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup] local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance srcInstance.skillPart = index self.modFlag = true self.buildFlag = true end) - self.controls.mainSkillStageCountLabel = new("LabelControl"):LabelControl({"TOPLEFT",self.controls.mainSkillPart,"BOTTOMLEFT",true}, {0, 3, 0, 16}, "^7Stages:") { + self.controls.mainSkillStageCountLabel = new("LabelControl"):LabelControl({"TOPLEFT",self.controls.mainSkillPart,"BOTTOMLEFT",true}, {0, 5, 0, 16}, "^7Stages:") { shown = function() return self.controls.mainSkillStageCount:IsShown() end, } - self.controls.mainSkillStageCount = new("EditControl"):EditControl({"LEFT",self.controls.mainSkillStageCountLabel,"RIGHT",true}, {2, 0, 60, 18}, nil, nil, "%D", nil, function(buf) + self.controls.mainSkillStageCount = new("EditControl"):EditControl({"LEFT",self.controls.mainSkillStageCountLabel,"RIGHT",true}, {2, 0, 60, 20}, nil, nil, "%D", nil, function(buf) local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup] local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance srcInstance.skillStageCount = tonumber(buf) self.modFlag = true self.buildFlag = true end) - self.controls.mainSkillMineCountLabel = new("LabelControl"):LabelControl({"TOPLEFT",self.controls.mainSkillStageCountLabel,"BOTTOMLEFT",true}, {0, 3, 0, 16}, "^7Active Mines:") { + self.controls.mainSkillMineCountLabel = new("LabelControl"):LabelControl({"TOPLEFT",self.controls.mainSkillStageCountLabel,"BOTTOMLEFT",true}, {0, 5, 0, 16}, "^7Active Mines:") { shown = function() return self.controls.mainSkillMineCount:IsShown() end, } - self.controls.mainSkillMineCount = new("EditControl"):EditControl({"LEFT",self.controls.mainSkillMineCountLabel,"RIGHT",true}, {2, 0, 60, 18}, nil, nil, "%D", nil, function(buf) + self.controls.mainSkillMineCount = new("EditControl"):EditControl({"LEFT",self.controls.mainSkillMineCountLabel,"RIGHT",true}, {2, 0, 60, 20}, nil, nil, "%D", nil, function(buf) local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup] local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance srcInstance.skillMineCount = tonumber(buf) self.modFlag = true self.buildFlag = true end) - self.controls.mainSkillMinion = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSkillMineCountLabel,"BOTTOMLEFT",true}, {0, 3, 178, 18}, nil, function(index, value) + self.controls.mainSkillMinion = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSkillMineCountLabel,"BOTTOMLEFT",true}, {0, 4, 178, 20}, nil, function(index, value) local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup] local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance if value.itemSetId then @@ -555,18 +551,21 @@ function buildMode:Init(dbFileName, buildName, buildXML, convertBuild, importLin tooltip:AddLine(14, colorCodes.TIP.."Tip: You can drag items from the Items tab onto this dropdown to equip them onto the minion.") end end - self.controls.mainSkillMinionLibrary = new("ButtonControl"):ButtonControl({"LEFT",self.controls.mainSkillMinion,"RIGHT"}, {2, 0, 120, 18}, "Manage Spectres...", function() + self.controls.mainSkillMinionLibrary = new("ButtonControl"):ButtonControl({"LEFT",self.controls.mainSkillMinion,"RIGHT"}, {2, 0, 120, 20}, "Manage Spectres...", function() self:OpenSpectreLibrary() end) - self.controls.mainSkillMinionSkill = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSkillMinion,"BOTTOMLEFT",true}, {0, 2, 200, 16}, nil, function(index, value) + self.controls.mainSkillMinionSkill = new("DropDownControl"):DropDownControl({"TOPLEFT",self.controls.mainSkillMinion,"BOTTOMLEFT",true}, {0, 4, 200, 20}, nil, function(index, value) local mainSocketGroup = self.skillsTab.socketGroupList[self.mainSocketGroup] local srcInstance = mainSocketGroup.displaySkillList[mainSocketGroup.mainActiveSkill].activeEffect.srcInstance srcInstance.skillMinionSkill = index self.modFlag = true self.buildFlag = true end) - self.controls.statBoxAnchor = new("Control"):Control({"TOPLEFT",self.controls.mainSkillMinionSkill,"BOTTOMLEFT",true}, {0, 2, 0, 0}) - self.controls.statBox = new("TextListControl"):TextListControl({"TOPLEFT",self.controls.statBoxAnchor,"BOTTOMLEFT"}, {0, 2, 300, 0}, {{x=170,align="RIGHT_X"},{x=174,align="LEFT"}}) + self.controls.statBoxAnchor = new("Control"):Control({"TOPLEFT",self.controls.mainSkillMinionSkill,"BOTTOMLEFT",true}, {0, 4, 0, 0}) + -- Label and value are pinned to opposite edges rather than meeting in the middle, so both read + -- as columns and long labels have the whole width to run into + self.controls.statBox = new("TextListControl"):TextListControl({"TOPLEFT",self.controls.statBoxAnchor,"BOTTOMLEFT"}, {0, 2, 300, 0}, {{x=6,align="LEFT"},{x=268,align="RIGHT_X"}}) + self.controls.statBox.grouped = true self.controls.statBox.height = function(control) local x, y = control:GetPos() local warnHeight = main.showWarnings and #self.controls.warnings.lines > 0 and 18 or 0 @@ -1325,18 +1324,17 @@ function buildMode:OnFrame(inputEvents) SetDrawLayer(5) - -- Draw top bar background - SetDrawColor(0.2, 0.2, 0.2) - DrawImage(nil, 0, 0, main.screenW, 28) - SetDrawColor(0.85, 0.85, 0.85) - DrawImage(nil, 0, 28, main.screenW, 4) - DrawImage(nil, main.screenW/2 - 2, 0, 4, 28) + -- Draw top bar background, closed off by a hairline rather than the old 4px rule + ui.SetColor(ui.colors.chrome) + DrawImage(nil, 0, 0, main.screenW, 31) + ui.SetColor(ui.colors.chromeBorder) + DrawImage(nil, 0, 31, main.screenW, 1) -- Draw side bar background - SetDrawColor(0.1, 0.1, 0.1) - DrawImage(nil, 0, 32, sideBarWidth - 4, main.screenH - 32) - SetDrawColor(0.85, 0.85, 0.85) - DrawImage(nil, sideBarWidth - 4, 32, 4, main.screenH - 32) + ui.SetColor(ui.colors.chrome) + DrawImage(nil, 0, 32, sideBarWidth - 1, main.screenH - 32) + ui.SetColor(ui.colors.chromeBorder) + DrawImage(nil, sideBarWidth - 1, 32, 1, main.screenH - 32) local hovered = self.controls.statBox and self.controls.statBox.hoveredLine @@ -2081,7 +2079,7 @@ function buildMode:RefreshStatList() end end if self.calcsTab.mainEnv.minion then - t_insert(statBoxList, { height = 18, "^7Minion:" }) + t_insert(statBoxList, { height = 20, header = true, "^7Minion" }) if self.calcsTab.mainEnv.minion.mainSkill.infoMessage then -- Split the line if too long if #self.calcsTab.mainEnv.minion.mainSkill.infoMessage > 40 then @@ -2097,7 +2095,7 @@ function buildMode:RefreshStatList() end self:AddDisplayStatList(self.minionDisplayStats, self.calcsTab.mainEnv.minion, "minion") t_insert(statBoxList, { height = 10 }) - t_insert(statBoxList, { height = 18, "^7Player:" }) + t_insert(statBoxList, { height = 20, header = true, "^7Player" }) end if self.calcsTab.mainEnv.player.mainSkill.skillFlags.disable then t_insert(statBoxList, { height = 16, "^7Skill disabled:" }) diff --git a/src/Modules/Main.lua b/src/Modules/Main.lua index a7451fe269..5a106217f3 100644 --- a/src/Modules/Main.lua +++ b/src/Modules/Main.lua @@ -16,6 +16,10 @@ local m_pi = math.pi LoadModule("GameVersions") LoadModule("Modules/Common") + +-- Load as global so the control classes can share one set of colours and drawing primitives +ui = LoadModule("Modules/UI") + LoadModule("Modules/CalcFormat") LoadModule("Modules/Data") LoadModule("Modules/ModTools") @@ -1511,11 +1515,7 @@ function main:DrawArrow(x, y, width, height, dir) end function main:DrawCheckMark(x, y, size) - size = size / 0.8 - x = x - size / 2 - y = y - size / 2 - DrawImageQuad(nil, x + size * 0.15, y + size * 0.50, x + size * 0.30, y + size * 0.45, x + size * 0.50, y + size * 0.80, x + size * 0.40, y + size * 0.90) - DrawImageQuad(nil, x + size * 0.40, y + size * 0.90, x + size * 0.35, y + size * 0.75, x + size * 0.80, y + size * 0.10, x + size * 0.90, y + size * 0.20) + ui.DrawCheckMark(x, y, size / 0.8) end do diff --git a/src/Modules/UI.lua b/src/Modules/UI.lua new file mode 100644 index 0000000000..cae5a6fe98 --- /dev/null +++ b/src/Modules/UI.lua @@ -0,0 +1,197 @@ +-- Path of Building +-- +-- Module: UI +-- Shared visual language for the base controls (buttons, drop downs, edits, check boxes, ...). +-- +-- Everything here is purely cosmetic: it provides the colour tokens the controls draw with, +-- plus a handful of rounded rectangle primitives, since SimpleGraphic only knows how to draw +-- axis aligned quads. +-- +local m_min = math.min +local m_max = math.max +local m_floor = math.floor + +local ui = { } + +-- TODO: remove this switch before merging upstream. It is here for the review only: not everyone +-- wants rounded corners, and some exiles have said they prefer the sharp edges, so setting this +-- to false squares everything off and makes the two looks easy to compare side by side. +-- Honoured by the primitives rather than the tokens, so it also catches the places that derive +-- their own radius, like the slider knob and the scroll bar thumb. +ui.rounded = true + +-- Corner rounding used by the base controls +ui.radiusSmall = 3 +ui.radius = 5 +ui.radiusLarge = 8 + +-- Colour tokens. Each entry is { red, green, blue } in the 0-1 range that SetDrawColor expects. +-- Colour tokens, packed as 0xRRGGBB. They are plain numbers rather than {r, g, b} tables: the draw +-- path runs every frame for every control, so a colour that has to be built there, or a table that +-- has to be traced by the collector, adds up. Numbers are values in Lua, so passing one around or +-- deriving one at draw time allocates nothing. +ui.colors = { + -- Outlines + border = 0x3D3D45, + borderHover = 0x737380, + borderActive = 0xA1A1B0, + borderDisabled = 0x29292E, + + -- Control bodies. Item names, "(Unused)" notes and other body text use the game's own + -- palette, which is mixed for contrast against black, so anything holding text stays near + -- black and leans on the border for definition. Only the hover and pressed states lift far + -- enough to be felt. + surface = 0x0E0E11, + surfaceHover = 0x1D1D22, + surfaceActive = 0x2B2B33, + surfaceDisabled = 0x09090A, + + -- Containers that float above the rest of the interface + popover = 0x08080A, + panel = 0x0B0B0D, + + -- Window chrome, i.e. the top bar and the side bar the tab buttons sit on. It reads as raised + -- rather than recessed, so it is the one surface that sits above the controls it holds. + chrome = 0x212126, + chromeBorder = 0x4C4C57, + + -- Text entry bodies, which sit a little deeper than a button + input = 0x060607, + inputHover = 0x0B0B0E, + + -- Row highlights inside lists and drop downs + accent = 0x2B2B33, + accentSubtle = 0x18181C, + + -- Filled emphasis, e.g. a ticked check box or a slider knob + primary = 0xE0E0E8, + primaryHover = 0xFFFFFF, + onPrimary = 0x0D0D0F, + + -- Text + text = 0xFAFAFA, + textMuted = 0xB8B8BF, + textDisabled = 0x6B6B73, + + -- Focus ring drawn just outside a focused control + ring = 0x8C8C9E, + + -- Drag and drop feedback + dropTarget = 0x59BF73, + dropTargetSurface = 0x0D170F, +} + +-- A white disc; each quadrant is used as a rounded corner, so a rounded rectangle is +-- four corner draws plus three plain quads. +local roundImage = NewImageHandle() +roundImage:Load("Assets/ui_round.png", "CLAMP", "MIPMAP") + +-- Untextured geometry has no antialiasing, which a diagonal shape like a check mark shows badly, +-- so it comes from a sprite as well +local checkImage = NewImageHandle() +checkImage:Load("Assets/ui_check.png", "CLAMP", "MIPMAP") + +---Set the current draw colour from a packed 0xRRGGBB token +---@param color integer +---@param alpha? number +function ui.SetColor(color, alpha) + SetDrawColor( + m_floor(color / 0x10000) / 255, + m_floor(color / 0x100) % 0x100 / 255, + color % 0x100 / 255, + alpha or 1 + ) +end + +---Pack a colour that only exists at draw time, e.g. one a control's borderFunc returns, into the +---same form the tokens use. Returns a number, so it costs no allocation. +---@return integer +function ui.PackColor(r, g, b) + return m_floor(r * 255 + 0.5) * 0x10000 + m_floor(g * 255 + 0.5) * 0x100 + m_floor(b * 255 + 0.5) +end + +---Draw a filled rectangle with rounded corners, using the current draw colour +---@param radius? number defaults to ui.radius +function ui.DrawRect(x, y, width, height, radius) + if width <= 0 or height <= 0 then + return + end + radius = m_floor(m_min(radius or ui.radius, width / 2, height / 2)) + if not ui.rounded or radius < 1 then + DrawImage(nil, x, y, width, height) + return + end + local right = x + width - radius + local bottom = y + height - radius + DrawImage(roundImage, x, y, radius, radius, 0, 0, 0.5, 0.5) + DrawImage(roundImage, right, y, radius, radius, 0.5, 0, 1, 0.5) + DrawImage(roundImage, x, bottom, radius, radius, 0, 0.5, 0.5, 1) + DrawImage(roundImage, right, bottom, radius, radius, 0.5, 0.5, 1, 1) + DrawImage(nil, x + radius, y, width - radius * 2, radius) + DrawImage(nil, x, y + radius, width, height - radius * 2) + DrawImage(nil, x + radius, bottom, width - radius * 2, radius) +end + +---Draw a filled circle of the given diameter, using the current draw colour +function ui.DrawCircle(x, y, diameter) + if not ui.rounded then + DrawImage(nil, x, y, diameter, diameter) + return + end + DrawImage(roundImage, x, y, diameter, diameter) +end + +---Draw a check mark centred on the given point, using the current draw colour +function ui.DrawCheckMark(x, y, size) + DrawImage(checkImage, x - size / 2, y - size / 2, size, size) +end + +---Draw an outlined, filled rounded rectangle +---@param border? integer outline colour, or nil for no outline +---@param fill? integer body colour, or nil to leave the body untouched +---@param thickness? number outline thickness, defaults to 1 +function ui.DrawBox(x, y, width, height, radius, border, fill, thickness) + radius = radius or ui.radius + thickness = thickness or 1 + if border then + ui.SetColor(border) + ui.DrawRect(x, y, width, height, radius) + elseif not fill then + return + end + if fill then + ui.SetColor(fill) + local inset = border and thickness or 0 + ui.DrawRect(x + inset, y + inset, width - inset * 2, height - inset * 2, radius - inset) + end +end + +---Draw a focus ring around a control. Must be drawn before the control body, which covers its +---inner half. +function ui.DrawFocusRing(x, y, width, height, radius, color, spread) + spread = spread or 2 + ui.SetColor(color or ui.colors.ring, 0.55) + ui.DrawRect(x - spread, y - spread, width + spread * 2, height + spread * 2, (radius or ui.radius) + spread) +end + +---Pick the outline and body colours for an interactive control +---@return integer border +---@return integer fill +function ui.SurfaceColors(enabled, hover, active) + local colors = ui.colors + if not enabled then + return colors.borderDisabled, colors.surfaceDisabled + elseif active then + return colors.borderActive, colors.surfaceActive + elseif hover then + return colors.borderHover, colors.surfaceHover + end + return colors.border, colors.surface +end + +---Clamp a corner radius so it never exceeds half of the smaller side +function ui.FitRadius(radius, width, height) + return m_max(0, m_min(radius, m_floor(m_min(width, height) / 2))) +end + +return ui