From eb824f9282ecf2a189f1e65aa0f827ca92cf0638 Mon Sep 17 00:00:00 2001 From: labrie75 <82786635+labrie75@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:14:27 +0900 Subject: [PATCH 1/2] Update EUI_QoL_Options.lua --- EllesmereUIQoL/EUI_QoL_Options.lua | 207 +++++++++++++++++++++-------- 1 file changed, 150 insertions(+), 57 deletions(-) diff --git a/EllesmereUIQoL/EUI_QoL_Options.lua b/EllesmereUIQoL/EUI_QoL_Options.lua index ebe7dfea..190d2b78 100644 --- a/EllesmereUIQoL/EUI_QoL_Options.lua +++ b/EllesmereUIQoL/EUI_QoL_Options.lua @@ -1491,34 +1491,121 @@ initFrame:SetScript("OnEvent", function(self) return not EllesmereUI.QoLExtrasGet("showSecondaryStats") end - -- Color swatch for label color (defaults to class color) - local ssSwGet = function() - local c = EllesmereUI.QoLExtrasGet("secondaryStatsColor") - if c then return c.r, c.g, c.b end - local _, cls = UnitClass("player") - local cc = cls and EllesmereUI.GetClassColor(cls) - if cc then return cc.r, cc.g, cc.b end - return 1, 1, 1 + -- Inline default + class + custom colour swatches, following the + -- minimap border's swatch-row convention: the active mode renders + -- at full alpha, the others dimmed, each with a naming tooltip. + local function ssMode() + local m = EllesmereUI.QoLExtrasGet("secondaryStatsColorMode") + if m then return m end + -- No mode saved: an old profile with a stored color was using it. + return EllesmereUI.QoLExtrasGet("secondaryStatsColor") and "custom" or "palette" end - local ssSwSet = function(r, g, b) - EllesmereUI.QoLExtrasSet("secondaryStatsColor", { r = r, g = g, b = b }) + local ssUpdateState -- forward: swatches reference it from OnClick + local function ssSetMode(v) + EllesmereUI.QoLExtrasSet("secondaryStatsColorMode", v) if EllesmereUI._applySecondaryStats then EllesmereUI._applySecondaryStats() end + if ssUpdateState then ssUpdateState() end end - local ssSwatch, ssUpdateSwatch = EllesmereUI.BuildColorSwatch(leftRgn, leftRgn:GetFrameLevel() + 5, ssSwGet, ssSwSet, nil, 20) - PP.Point(ssSwatch, "RIGHT", leftRgn._control, "LEFT", -12, 0) - leftRgn._lastInline = ssSwatch - - -- Blocking overlay for swatch when Secondary Stat Display is off - local ssSwBlock = CreateFrame("Frame", nil, ssSwatch) - ssSwBlock:SetAllPoints() - ssSwBlock:SetFrameLevel(ssSwatch:GetFrameLevel() + 10) - ssSwBlock:EnableMouse(true) - ssSwBlock:SetScript("OnEnter", function() - EllesmereUI.ShowWidgetTooltip(ssSwatch, EllesmereUI.DisabledTooltip("Secondary Stat Display")) - end) - ssSwBlock:SetScript("OnLeave", function() EllesmereUI.HideWidgetTooltip() end) - -- Cog popup: Show Tertiary Stats toggle + Tertiary Label Color + Scale slider + -- Custom swatch (nearest the control): stored custom colour. A + -- click switches to custom mode first; a second click opens the + -- picker (same two-step as the minimap border swatches). + local ssCustom, ssUpdCustom = EllesmereUI.BuildColorSwatch( + leftRgn, leftRgn:GetFrameLevel() + 5, + function() + local c = EllesmereUI.QoLExtrasGet("secondaryStatsColor") + if c then return c.r, c.g, c.b end + return 1, 1, 1 + end, + function(r, g, b) + EllesmereUI.QoLExtrasSet("secondaryStatsColor", { r = r, g = g, b = b }) + EllesmereUI.QoLExtrasSet("secondaryStatsColorMode", "custom") + if EllesmereUI._applySecondaryStats then EllesmereUI._applySecondaryStats() end + if ssUpdateState then ssUpdateState() end + end, nil, 20) + do + local openPicker = ssCustom:GetScript("OnClick") + ssCustom:SetScript("OnClick", function(self) + if ssMode() ~= "custom" then ssSetMode("custom") return end + if openPicker then openPicker(self) end + end) + end + PP.Point(ssCustom, "RIGHT", leftRgn._control, "LEFT", -12, 0) + leftRgn._lastInline = ssCustom + + -- Class-colour swatch: live player class colour. + local ssClass, ssUpdClass = EllesmereUI.BuildColorSwatch( + leftRgn, leftRgn:GetFrameLevel() + 5, + function() + local cc = EllesmereUI.GetClassColor and EllesmereUI.GetClassColor(select(2, UnitClass("player"))) + if cc then return cc.r, cc.g, cc.b end + return 1, 1, 1 + end, + function() end, nil, 20) + ssClass:SetScript("OnClick", function() ssSetMode("class") end) + PP.Point(ssClass, "RIGHT", leftRgn._lastInline, "LEFT", -8, 0) + leftRgn._lastInline = ssClass + + -- Default swatch (outermost): the per-stat palette, previewed by + -- its first hue (crit gold). + local ssDefault, ssUpdDefault = EllesmereUI.BuildColorSwatch( + leftRgn, leftRgn:GetFrameLevel() + 5, + function() return 1, 209 / 255, 0 end, + function() end, nil, 20) + ssDefault:SetScript("OnClick", function() ssSetMode("palette") end) + PP.Point(ssDefault, "RIGHT", leftRgn._lastInline, "LEFT", -8, 0) + leftRgn._lastInline = ssDefault + + local ssTips = { { ssDefault, "Default" }, { ssClass, "Class Color" }, { ssCustom, "Custom Color" } } + for _, e in ipairs(ssTips) do + e[1]:HookScript("OnEnter", function() EllesmereUI.ShowWidgetTooltip(e[1], e[2]) end) + e[1]:HookScript("OnLeave", function() EllesmereUI.HideWidgetTooltip() end) + end + + -- Blocking overlays while Secondary Stat Display is off (shown from + -- the single refresh below, like the swatch alphas). + local ssBlocks = {} + for _, e in ipairs(ssTips) do + local sw = e[1] + local block = CreateFrame("Frame", nil, sw) + block:SetAllPoints() + block:SetFrameLevel(sw:GetFrameLevel() + 10) + block:EnableMouse(true) + block:SetScript("OnEnter", function() + EllesmereUI.ShowWidgetTooltip(sw, EllesmereUI.DisabledTooltip("Secondary Stat Display")) + end) + block:SetScript("OnLeave", function() EllesmereUI.HideWidgetTooltip() end) + ssBlocks[#ssBlocks + 1] = block + end + + -- While the display is off every swatch dims flat; while on, the + -- active mode is bright and the others dimmed. One refresh owns + -- both, plus the swatch fills (class color changes, custom picks). + ssUpdateState = function() + if ssUpdCustom then ssUpdCustom() end + if ssUpdClass then ssUpdClass() end + if ssUpdDefault then ssUpdDefault() end + local off = statsOff() + for _, block in ipairs(ssBlocks) do block:SetShown(off) end + local m = not off and ssMode() or nil + ssDefault:SetAlpha(m == "palette" and 1 or 0.3) + ssClass:SetAlpha(m == "class" and 1 or 0.3) + ssCustom:SetAlpha(m == "custom" and 1 or 0.3) + end + EllesmereUI.RegisterWidgetRefresh(ssUpdateState) + ssUpdateState() + + -- Cog popup: Show Tertiary Stats toggle + tertiary swatch pair + Scale + local function tsMode() + local m = EllesmereUI.QoLExtrasGet("tertiaryStatsColorMode") + if m then return m end + -- No mode saved: an old profile with a stored color was using it. + return EllesmereUI.QoLExtrasGet("tertiaryStatsColor") and "custom" or "class" + end + local function tsSetMode(v) + EllesmereUI.QoLExtrasSet("tertiaryStatsColorMode", v) + if EllesmereUI._applySecondaryStats then EllesmereUI._applySecondaryStats() end + end local _, ssCogShow = EllesmereUI.BuildCogPopup({ title = "Secondary Stats Settings", rows = { @@ -1530,23 +1617,41 @@ initFrame:SetScript("OnEvent", function(self) EllesmereUI.QoLExtrasSet("showTertiaryStats", v) if EllesmereUI._applySecondaryStats then EllesmereUI._applySecondaryStats() end end }, - { type = "colorpicker", label = "Tertiary Label Color", + -- Class / custom swatch pair, the same convention as the + -- minimap border row: the active mode at full alpha, a + -- naming tooltip on each swatch. + { type = "multiswatch", label = "Tertiary Label Color", disabled = function() return not EllesmereUI.QoLExtrasGet("showTertiaryStats") end, disabledTooltip = "Show Tertiary Stats", - get = function() - local c = EllesmereUI.QoLExtrasGet("tertiaryStatsColor") - if c then return c.r, c.g, c.b end - local _, cls = UnitClass("player") - local cc = cls and EllesmereUI.GetClassColor(cls) - if cc then return cc.r, cc.g, cc.b end - return 1, 1, 1 - end, - set = function(r, g, b) - EllesmereUI.QoLExtrasSet("tertiaryStatsColor", { r = r, g = g, b = b }) - if EllesmereUI._applySecondaryStats then EllesmereUI._applySecondaryStats() end - end }, + swatches = { + { tooltip = "Class Color", + getValue = function() + local cc = EllesmereUI.GetClassColor and EllesmereUI.GetClassColor(select(2, UnitClass("player"))) + if cc then return cc.r, cc.g, cc.b end + return 1, 1, 1 + end, + onClick = function() tsSetMode("class") end, + refreshAlpha = function() return tsMode() == "class" and 1 or 0.3 end }, + { tooltip = "Custom Color", + getValue = function() + local c = EllesmereUI.QoLExtrasGet("tertiaryStatsColor") + if c then return c.r, c.g, c.b end + return 1, 1, 1 + end, + setValue = function(r, g, b) + EllesmereUI.QoLExtrasSet("tertiaryStatsColor", { r = r, g = g, b = b }) + EllesmereUI.QoLExtrasSet("tertiaryStatsColorMode", "custom") + if EllesmereUI._applySecondaryStats then EllesmereUI._applySecondaryStats() end + end, + -- First click switches to custom; a second opens the picker. + onClick = function(self, ...) + if tsMode() ~= "custom" then tsSetMode("custom") return end + if self._eabOrigClick then self._eabOrigClick(self, ...) end + end, + refreshAlpha = function() return tsMode() == "custom" and 1 or 0.3 end }, + } }, { type = "slider", label = "Scale", min = 50, max = 200, step = 5, get = function() local pos = EllesmereUI.QoLExtrasGet("secondaryStatsPos") @@ -1587,27 +1692,15 @@ initFrame:SetScript("OnEvent", function(self) end) ssCogBlock:SetScript("OnLeave", function() EllesmereUI.HideWidgetTooltip() end) - -- Refresh: dim + block swatch/cog when toggle is off - EllesmereUI.RegisterWidgetRefresh(function() + -- Refresh: dim + block the cog with the toggle. The swatches and + -- their overlays are owned by ssUpdateState above. + local function ssCogRefresh() local off = statsOff() - if off then - ssSwatch:SetAlpha(0.3) - ssSwBlock:Show() - ssCogBtn:SetAlpha(0.15) - ssCogBlock:Show() - else - ssSwatch:SetAlpha(1) - ssSwBlock:Hide() - ssCogBtn:SetAlpha(0.4) - ssCogBlock:Hide() - end - ssUpdateSwatch() - end) - local ssInitOff = statsOff() - ssSwatch:SetAlpha(ssInitOff and 0.3 or 1) - if ssInitOff then ssSwBlock:Show() else ssSwBlock:Hide() end - ssCogBtn:SetAlpha(ssInitOff and 0.15 or 0.4) - if ssInitOff then ssCogBlock:Show() else ssCogBlock:Hide() end + ssCogBtn:SetAlpha(off and 0.15 or 0.4) + ssCogBlock:SetShown(off) + end + EllesmereUI.RegisterWidgetRefresh(ssCogRefresh) + ssCogRefresh() end -- Row 4: Rested Indicator (left) | From 05841addf724f7f6e88b0a153584e575906518af Mon Sep 17 00:00:00 2001 From: labrie75 <82786635+labrie75@users.noreply.github.com> Date: Sat, 8 Aug 2026 11:14:53 +0900 Subject: [PATCH 2/2] Update EllesmereUIQoL.lua --- EllesmereUIQoL/EllesmereUIQoL.lua | 103 +++++++++++++++++++----------- 1 file changed, 67 insertions(+), 36 deletions(-) diff --git a/EllesmereUIQoL/EllesmereUIQoL.lua b/EllesmereUIQoL/EllesmereUIQoL.lua index 92c96d40..b6c1b871 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,21 @@ 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 + -- Label color mode: "palette" (one hue per stat), "class", or "custom". + -- Older profiles have no mode saved; a stored custom color means the + -- user picked one back when picking implied using it, so it still wins. 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 + local mode = EllesmereUI.QoLExtrasGet("secondaryStatsColorMode") + or (c and "custom" or "palette") + local customHex + if mode == "custom" and c then + customHex = format("%02x%02x%02x", c.r * 255, c.g * 255, c.b * 255) + elseif mode == "class" then + customHex = statsFrame._classHex end - local labelHex = c and format("%02x%02x%02x", cr * 255, cg * 255, cb * 255) or statsFrame._classHex local crit = GetCritChance("player") local haste = UnitSpellHaste("player") @@ -1892,33 +1908,38 @@ 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 tmode = EllesmereUI.QoLExtrasGet("tertiaryStatsColorMode") + or (tc and "custom" or "class") + local tertHex = (tmode == "custom" and 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 +1959,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 +1985,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.