From 48521037929a0e5af133410cd37dd33d82f2ba22 Mon Sep 17 00:00:00 2001 From: labrie75 <82786635+labrie75@users.noreply.github.com> Date: Sat, 8 Aug 2026 10:29:34 +0900 Subject: [PATCH] Secondary Stats Display: right-aligned values, row spacing, per-stat colors ## What does this PR do? Three readability improvements to the Secondary Stats overlay, all inside its own section of `EllesmereUIQoL.lua`: **Right-aligned values.** The overlay was a single left-justified FontString, so each number started wherever its label happened to end and the column wobbled. The text is now two FontStrings -- labels justified left, values justified right -- sharing the same font, size and line spacing, so the rows stay in step and the numbers line up on their right edge. Label widths vary a lot between locales (compare the English and Korean screenshots), and the value column absorbs all of it. **Row spacing.** The lines sat directly on top of each other. Both columns now get `SetSpacing(3)`, scaled together with the font size, so the block stays readable at any overlay scale. **Per-stat colors.** With no custom color picked, each secondary now has its own hue -- Crit gold `ffd100`, Haste green `2ecc71`, Mastery blue `55aaff`, Versatility violet `c77dff` -- and the value takes the row's color too, so each stat reads as one piece. Tertiaries keep the original default, the player's class color. The existing options are unchanged: picking a custom secondary or tertiary color still paints that whole group the chosen color exactly as before -- the palette only applies while no custom color is set. Saved position, scale, the tertiary toggle, and the secret-safe value path are untouched, and no new locale keys are introduced (the labels reuse the existing `L("Crit")` family). ## How was it tested? Tested in game on the 12.1 PTR with a koKR client: default palette with tertiary rows enabled, and overlay rescaling. Values stay right-aligned as the numbers change width, the row gap scales with the font, and tertiaries render in the class color. No load errors. ## Screenshots Same overlay on an English client (left) and a Korean client (right): ## Checklist - [x] New settings default **OFF** -- n/a, no setting added - [x] Zero cost while disabled -- the section still early-returns when the overlay is off - [x] Cheap while enabled -- same update path; one extra FontString, built once - [x] No writes onto Blizzard-owned frames -- the addon's own overlay frame only - [x] Tested in-game, works on the 12.1 PTR; no load errors --- EllesmereUIQoL/EllesmereUIQoL.lua | 94 +++++++++++++++++++------------ 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/EllesmereUIQoL/EllesmereUIQoL.lua b/EllesmereUIQoL/EllesmereUIQoL.lua index 92c96d40..70d553bc 100644 --- a/EllesmereUIQoL/EllesmereUIQoL.lua +++ b/EllesmereUIQoL/EllesmereUIQoL.lua @@ -1847,9 +1847,26 @@ end -- On-screen overlay showing crit/haste/mastery/vers (+ optional tertiaries). ------------------------------------------------------------------------------- do - local statsFrame, statsText + local statsFrame, statsText, statsValues local format = string.format + -- Two-column layout: labels left-justified, numbers right-justified in a + -- second FontString, so values line up regardless of label width. Both + -- columns share font/size/spacing, which keeps their rows in step. + local COL_GAP = 10 -- gap between the label and value columns + local ROW_GAP = 3 -- extra pixels between rows (SetSpacing) + + -- Per-stat label colors, used when no custom color is picked in options. + -- One hue per secondary so the rows scan at a glance; tertiaries share a + -- single distinct hue so the block reads as its own group. + local STAT_HEX = { + crit = "ffd100", -- gold + haste = "2ecc71", -- green + mastery = "55aaff", -- blue + vers = "c77dff", -- violet + } + -- Tertiaries keep the original default: the player's class color. + -- Secret-safe percent text: stat getters can return secret numbers in -- restricted content, and string.format errors on a secret value. local function PctText(v) @@ -1862,22 +1879,13 @@ do if not statsFrame._classHex then local _, cls = UnitClass("player") local cc = cls and EllesmereUI.GetClassColor(cls) - if cc then - statsFrame._classR, statsFrame._classG, statsFrame._classB = cc.r, cc.g, cc.b - statsFrame._classHex = format("%02x%02x%02x", cc.r * 255, cc.g * 255, cc.b * 255) - else - statsFrame._classR, statsFrame._classG, statsFrame._classB = 1, 1, 1 - statsFrame._classHex = "ffffff" - end + statsFrame._classHex = cc + and format("%02x%02x%02x", cc.r * 255, cc.g * 255, cc.b * 255) or "ffffff" end + -- A custom color from options paints every secondary label that color + -- (as before); with no custom color each stat gets its own hue. local c = EllesmereUI.QoLExtrasGet("secondaryStatsColor") - local cr, cg, cb - if c then - cr, cg, cb = c.r, c.g, c.b - else - cr, cg, cb = statsFrame._classR, statsFrame._classG, statsFrame._classB - end - local labelHex = c and format("%02x%02x%02x", cr * 255, cg * 255, cb * 255) or statsFrame._classHex + local customHex = c and format("%02x%02x%02x", c.r * 255, c.g * 255, c.b * 255) or nil local crit = GetCritChance("player") local haste = UnitSpellHaste("player") @@ -1892,33 +1900,35 @@ do vers = versRating + versBase end - local txt = - format("|cff%s%s:|r |cffffffff%s|r", labelHex, EllesmereUI.L("Crit"), PctText(crit)) .. "\n" .. - format("|cff%s%s:|r |cffffffff%s|r", labelHex, EllesmereUI.L("Haste"), PctText(haste)) .. "\n" .. - format("|cff%s%s:|r |cffffffff%s|r", labelHex, EllesmereUI.L("Mastery"), PctText(mastery)) .. "\n" .. - format("|cff%s%s:|r |cffffffff%s|r", labelHex, EllesmereUI.L("Vers"), PctText(vers)) + local labels, values = {}, {} + -- Value takes the row's color too, so each stat reads as one piece. + local function Row(hex, label, value) + labels[#labels + 1] = format("|cff%s%s:|r", hex, label) + values[#values + 1] = format("|cff%s%s|r", hex, value) + end + Row(customHex or STAT_HEX.crit, EllesmereUI.L("Crit"), PctText(crit)) + Row(customHex or STAT_HEX.haste, EllesmereUI.L("Haste"), PctText(haste)) + Row(customHex or STAT_HEX.mastery, EllesmereUI.L("Mastery"), PctText(mastery)) + Row(customHex or STAT_HEX.vers, EllesmereUI.L("Vers"), PctText(vers)) if EllesmereUI.QoLExtrasGet("showTertiaryStats") then local tc = EllesmereUI.QoLExtrasGet("tertiaryStatsColor") - local tr, tg, tb - if tc then - tr, tg, tb = tc.r, tc.g, tc.b - else - tr, tg, tb = statsFrame._classR, statsFrame._classG, statsFrame._classB - end - local tertHex = tc and format("%02x%02x%02x", tr * 255, tg * 255, tb * 255) or statsFrame._classHex + local tertHex = tc and format("%02x%02x%02x", tc.r * 255, tc.g * 255, tc.b * 255) + or statsFrame._classHex local leech = GetLifesteal() local avoidance = GetAvoidance() local speed = GetSpeed() - txt = txt .. "\n" .. - format("|cff%s%s:|r |cffffffff%s|r", tertHex, EllesmereUI.L("Leech"), PctText(leech)) .. "\n" .. - format("|cff%s%s:|r |cffffffff%s|r", tertHex, EllesmereUI.L("Avoidance"), PctText(avoidance)) .. "\n" .. - format("|cff%s%s:|r |cffffffff%s|r", tertHex, EllesmereUI.L("Speed"), PctText(speed)) + Row(tertHex, EllesmereUI.L("Leech"), PctText(leech)) + Row(tertHex, EllesmereUI.L("Avoidance"), PctText(avoidance)) + Row(tertHex, EllesmereUI.L("Speed"), PctText(speed)) end - statsText:SetText(txt) - statsFrame:SetSize(statsText:GetStringWidth() + 2, statsText:GetStringHeight() + 2) + statsText:SetText(table.concat(labels, "\n")) + statsValues:SetText(table.concat(values, "\n")) + statsFrame:SetSize( + statsText:GetStringWidth() + COL_GAP + statsValues:GetStringWidth() + 2, + statsText:GetStringHeight() + 2) end local function ApplySecondaryStats() @@ -1938,11 +1948,17 @@ do statsText = statsFrame:CreateFontString(nil, "OVERLAY") statsText:SetPoint("TOPLEFT") statsText:SetJustifyH("LEFT") + statsValues = statsFrame:CreateFontString(nil, "OVERLAY") + statsValues:SetPoint("TOPRIGHT") + statsValues:SetJustifyH("RIGHT") end if statsText then local font = EllesmereUI.ResolveFontName(EllesmereUI.GetFontsDB().global) - if EllesmereUI and EllesmereUI.PrimeFontShadow then EllesmereUI.PrimeFontShadow(statsText, EllesmereUI.GetFontUseShadow("extras")) end - statsText:SetFont(font, 12, EllesmereUI.GetFontOutlineFlag("extras")) + for _, fs in ipairs({ statsText, statsValues }) do + if EllesmereUI and EllesmereUI.PrimeFontShadow then EllesmereUI.PrimeFontShadow(fs, EllesmereUI.GetFontUseShadow("extras")) end + fs:SetFont(font, 12, EllesmereUI.GetFontOutlineFlag("extras")) + fs:SetSpacing(ROW_GAP) + end end local pos = EllesmereUI.QoLExtrasGet("secondaryStatsPos") local scale = 1.0 @@ -1958,8 +1974,12 @@ do if statsText then local font = EllesmereUI.ResolveFontName(EllesmereUI.GetFontsDB().global) local fontSize = math.floor(12 * scale + 0.5) - if EllesmereUI and EllesmereUI.PrimeFontShadow then EllesmereUI.PrimeFontShadow(statsText, EllesmereUI.GetFontUseShadow("extras")) end - statsText:SetFont(font, fontSize, EllesmereUI.GetFontOutlineFlag("extras")) + for _, fs in ipairs({ statsText, statsValues }) do + if EllesmereUI and EllesmereUI.PrimeFontShadow then EllesmereUI.PrimeFontShadow(fs, EllesmereUI.GetFontUseShadow("extras")) end + fs:SetFont(font, fontSize, EllesmereUI.GetFontOutlineFlag("extras")) + -- Row gap scales with the font so the block keeps its rhythm. + fs:SetSpacing(math.floor(ROW_GAP * scale + 0.5)) + end end -- Unit-scoped events filter at the engine (player only); in a raid a -- plain RegisterEvent would deliver every member's stat changes.