From 796eacc363e0e518f59aa63a406c4b308b4567a1 Mon Sep 17 00:00:00 2001 From: Dmytro Kharchenko Date: Sat, 22 Aug 2026 08:05:27 +0000 Subject: [PATCH 1/3] Add ElvUI-style health-by-color gradient text option Adds a colorByHealth toggle for the health1/health2/shields/healAbsorbs health text segments: when enabled, the segment's color blends from red (low health) to yellow to its normal base color as health rises, instead of a static baked-in color. --- Defaults/Layout_Defaults.lua | 4 ++ Defaults/Layout_Defaults_Cata_Wrath.lua | 4 ++ Defaults/Layout_Defaults_Mists.lua | 4 ++ Defaults/Layout_Defaults_TBC_Vanilla.lua | 4 ++ Indicators/Built-in.lua | 53 +++++++++++++++-- Locales/enUS.lua | 1 + Utils.lua | 8 +++ Widgets/Widgets_IndicatorSettings.lua | 75 +++++++++++++++--------- 8 files changed, 118 insertions(+), 35 deletions(-) diff --git a/Defaults/Layout_Defaults.lua b/Defaults/Layout_Defaults.lua index a073480d..a4eaa1f7 100644 --- a/Defaults/Layout_Defaults.lua +++ b/Defaults/Layout_Defaults.lua @@ -169,22 +169,26 @@ Cell.defaults.layout = { ["health1"] = { ["format"] = "effective_percent", ["color"] = {"custom_color", {1, 1, 1}}, + ["colorByHealth"] = false, ["hideIfEmptyOrFull"] = false, }, ["health2"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 1, 1}}, + ["colorByHealth"] = false, ["hideIfEmptyOrFull"] = false, ["delimiter"] = " ", }, ["shields"] = { ["format"] = "none", ["color"] = {"custom_color", {0, 1, 0}}, + ["colorByHealth"] = false, ["delimiter"] = "+", }, ["healAbsorbs"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 0, 0}}, + ["colorByHealth"] = false, ["delimiter"] = "-", }, }, diff --git a/Defaults/Layout_Defaults_Cata_Wrath.lua b/Defaults/Layout_Defaults_Cata_Wrath.lua index 0ddc4ee4..9598375c 100644 --- a/Defaults/Layout_Defaults_Cata_Wrath.lua +++ b/Defaults/Layout_Defaults_Cata_Wrath.lua @@ -157,22 +157,26 @@ Cell.defaults.layout = { ["health1"] = { ["format"] = "effective_percent", ["color"] = {"custom_color", {1, 1, 1}}, + ["colorByHealth"] = false, ["hideIfEmptyOrFull"] = false, }, ["health2"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 1, 1}}, + ["colorByHealth"] = false, ["hideIfEmptyOrFull"] = false, ["delimiter"] = " ", }, ["shields"] = { ["format"] = "none", ["color"] = {"custom_color", {0, 1, 0}}, + ["colorByHealth"] = false, ["delimiter"] = "+", }, ["healAbsorbs"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 0, 0}}, + ["colorByHealth"] = false, ["delimiter"] = "-", }, }, diff --git a/Defaults/Layout_Defaults_Mists.lua b/Defaults/Layout_Defaults_Mists.lua index e77008e2..769384a2 100644 --- a/Defaults/Layout_Defaults_Mists.lua +++ b/Defaults/Layout_Defaults_Mists.lua @@ -164,22 +164,26 @@ Cell.defaults.layout = { ["health1"] = { ["format"] = "effective_percent", ["color"] = {"custom_color", {1, 1, 1}}, + ["colorByHealth"] = false, ["hideIfEmptyOrFull"] = false, }, ["health2"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 1, 1}}, + ["colorByHealth"] = false, ["hideIfEmptyOrFull"] = false, ["delimiter"] = " ", }, ["shields"] = { ["format"] = "none", ["color"] = {"custom_color", {0, 1, 0}}, + ["colorByHealth"] = false, ["delimiter"] = "+", }, ["healAbsorbs"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 0, 0}}, + ["colorByHealth"] = false, ["delimiter"] = "-", }, }, diff --git a/Defaults/Layout_Defaults_TBC_Vanilla.lua b/Defaults/Layout_Defaults_TBC_Vanilla.lua index d724785c..c1c15c6e 100644 --- a/Defaults/Layout_Defaults_TBC_Vanilla.lua +++ b/Defaults/Layout_Defaults_TBC_Vanilla.lua @@ -155,22 +155,26 @@ Cell.defaults.layout = { ["health1"] = { ["format"] = "effective_percent", ["color"] = {"custom_color", {1, 1, 1}}, + ["colorByHealth"] = false, ["hideIfEmptyOrFull"] = false, }, ["health2"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 1, 1}}, + ["colorByHealth"] = false, ["hideIfEmptyOrFull"] = false, ["delimiter"] = " ", }, ["shields"] = { ["format"] = "none", ["color"] = {"custom_color", {0, 1, 0}}, + ["colorByHealth"] = false, ["delimiter"] = "+", }, ["healAbsorbs"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 0, 0}}, + ["colorByHealth"] = false, ["delimiter"] = "-", }, }, diff --git a/Indicators/Built-in.lua b/Indicators/Built-in.lua index 43c4a6bf..f66a9b73 100644 --- a/Indicators/Built-in.lua +++ b/Indicators/Built-in.lua @@ -2019,7 +2019,8 @@ local function BuildPattern(config) local suffix = config.format:find("percent$") and "%%" or "" - if config.color[1] == "class_color" then + -- colorByHealth: color is computed dynamically per update (see ApplyHealthGradientColor), so leave it unbaked here + if config.colorByHealth or config.color[1] == "class_color" then return prefix .. "%s" .. suffix else return prefix .. "|cff" .. F.ConvertRGBToHEX(F.ConvertRGB_256(unpack(config.color[2]))) .. "%s" .. suffix .. "|r" @@ -2043,6 +2044,9 @@ local function BuildSecretSegment(config) local isPercent = config.format:find("percent$") and true or false + -- colorByHealth needs live (secret-safe) percent evaluation which isn't available at C-level + -- here, so secret segments just fall back to their static base color (no gradient), + -- same as when colorByHealth is off - class_color still relies on the fontstring's ambient tint. local colorStart, colorEnd if config.color[1] == "class_color" then colorStart, colorEnd = "", "" @@ -2095,6 +2099,14 @@ local function HealthText_SetFormat(self, format) self.shields = BuildPattern(format.shields) self.healAbsorbs = BuildPattern(format.healAbsorbs) + -- colorByHealth: remember which segments need their color recomputed every update + -- (against the segment's own class/custom base color) instead of the baked-in one + self.health1GradientColor = format.health1.colorByHealth and format.health1.color or nil + self.health2GradientColor = format.health2.colorByHealth and format.health2.color or nil + self.shieldsGradientColor = format.shields.colorByHealth and format.shields.color or nil + self.healAbsorbsGradientColor = format.healAbsorbs.colorByHealth and format.healAbsorbs.color or nil + + -- Pre-build C-level format segments for use when values are secret. -- Each active component gets a segment with a %d/%s placeholder. -- Stored individually so the render path can skip zero-value absorb components. local segments = {} @@ -2116,6 +2128,26 @@ local function HealthText_SetFormat(self, format) end +-- Wraps an already-formatted segment string with a color computed from the current +-- health percent (ElvUI-style red -> yellow -> base color), when colorByHealth is enabled +-- for that segment. `colorConfig` is the segment's {mode, {r,g,b}} color table, or nil. +local function ApplyHealthGradientColor(text, colorConfig, percent, unit) + if text == "" or not colorConfig then + return text + end + + local r, g, b + if colorConfig[1] == "class_color" then + -- no live unit in the settings preview (e.g. widget:SetValue with a fake sample) - fall back to the player's class + r, g, b = unit and F.GetUnitClassColor(unit) or F.GetClassColor(Cell.vars.playerClass) + else + r, g, b = colorConfig[2][1], colorConfig[2][2], colorConfig[2][3] + end + r, g, b = F.GetHealthTextColor(percent, r, g, b) + + return "|cff" .. F.ConvertRGBToHEX(F.ConvertRGB_256(r, g, b)) .. text .. "|r" +end + local function HealthText_SetValue(self, health, maxHealth, shields, healAbsorbs, unit) -- safety net for any remaining Lua arithmetic in the formatters below. if not F.IsValueNonSecret(health) or not F.IsValueNonSecret(maxHealth) @@ -2179,11 +2211,20 @@ local function HealthText_SetValue(self, health, maxHealth, shields, healAbsorbs end maxHealth = maxHealth == 0 and 1 or maxHealth - self.text:SetFormattedText("%s%s%s%s", - self.GetHealth1(self.health1, false, health, maxHealth, shields, healAbsorbs), - self.GetHealth2(self.health2, false, health, maxHealth, shields, healAbsorbs), - self.GetShields(self.shields, health, maxHealth, shields, healAbsorbs), - self.GetHealAbsorbs(self.healAbsorbs, health, maxHealth, shields, healAbsorbs)) + if self.health1GradientColor or self.health2GradientColor or self.shieldsGradientColor or self.healAbsorbsGradientColor then + local percent = health / maxHealth + self.text:SetFormattedText("%s%s%s%s", + ApplyHealthGradientColor(self.GetHealth1(self.health1, false, health, maxHealth, shields, healAbsorbs), self.health1GradientColor, percent, unit), + ApplyHealthGradientColor(self.GetHealth2(self.health2, false, health, maxHealth, shields, healAbsorbs), self.health2GradientColor, percent, unit), + ApplyHealthGradientColor(self.GetShields(self.shields, health, maxHealth, shields, healAbsorbs), self.shieldsGradientColor, percent, unit), + ApplyHealthGradientColor(self.GetHealAbsorbs(self.healAbsorbs, health, maxHealth, shields, healAbsorbs), self.healAbsorbsGradientColor, percent, unit)) + else + self.text:SetFormattedText("%s%s%s%s", + self.GetHealth1(self.health1, false, health, maxHealth, shields, healAbsorbs), + self.GetHealth2(self.health2, false, health, maxHealth, shields, healAbsorbs), + self.GetShields(self.shields, health, maxHealth, shields, healAbsorbs), + self.GetHealAbsorbs(self.healAbsorbs, health, maxHealth, shields, healAbsorbs)) + end self:SetWidth(self.text:GetStringWidth()) end diff --git a/Locales/enUS.lua b/Locales/enUS.lua index 080633e3..ac2549bf 100644 --- a/Locales/enUS.lua +++ b/Locales/enUS.lua @@ -26,6 +26,7 @@ select(2, ...).L = setmetatable({ ["dispellableByMe"] = "Only show debuffs dispellable by me", ["nonPlayerAuras"] = "Only show non-player auras", ["nonPlayerAurasTip"] = "Hides lust aftereffects such as Exhaustion, Sated, Forbearance and Temporal Displacement. Encounter debuffs stay visible.", + ["colorByHealthTip"] = "Blend this segment's color from red (low health) to yellow to its normal color as health rises, like ElvUI.", ["Private Dispel Overlay"] = "Private Dispel Overlay", ["Show private dispel overlay"] = "Show private dispel overlay", ["Only dispellable by me"] = "Only dispellable by me", diff --git a/Utils.lua b/Utils.lua index 78c1f9e6..49e95ca1 100644 --- a/Utils.lua +++ b/Utils.lua @@ -1435,6 +1435,14 @@ function F.GetHealthBarColor(percent, isDeadOrGhost, r, g, b) return barR, barG, barB, lossR, lossG, lossB end +-- ElvUI-style health text coloring: red at 0% health, yellow at 50%, original color (e.g. class color) at 100% +function F.GetHealthTextColor(percent, r, g, b) + if not Cell.loaded then + return r, g, b + end + return F.ColorGradient(percent or 1, {1, 0, 0}, {1, 1, 0}, {r, g, b}) +end + ------------------------------------------------- -- units ------------------------------------------------- diff --git a/Widgets/Widgets_IndicatorSettings.lua b/Widgets/Widgets_IndicatorSettings.lua index 0a9c1b43..189da2c4 100644 --- a/Widgets/Widgets_IndicatorSettings.lua +++ b/Widgets/Widgets_IndicatorSettings.lua @@ -941,6 +941,7 @@ local function CreateSetting_TextWidth(parent) end + local function CreateSetting_Alpha(parent) local widget @@ -1048,12 +1049,14 @@ local function CreateSetting_HealthFormat(parent) local health1Enabled = widget.format.health1.format ~= "none" widget.health1ColorDropdown:SetEnabled(health1Enabled) widget.health1ColorPicker:SetEnabled(health1Enabled) + widget.health1GradientCB:SetEnabled(health1Enabled) local health2Enabled = widget.format.health2.format ~= "none" widget.health2DelimiterEB:SetEnabled(health2Enabled) widget.health2DelimiterEB.confirmBtn:Hide() widget.health2ColorDropdown:SetEnabled(health2Enabled) widget.health2ColorPicker:SetEnabled(health2Enabled) + widget.health2GradientCB:SetEnabled(health2Enabled) if health2Enabled then widget.health2DelimiterText:SetTextColor(1, 1, 1) else @@ -1065,6 +1068,7 @@ local function CreateSetting_HealthFormat(parent) widget.shieldDelimiterEB.confirmBtn:Hide() widget.shieldColorDropdown:SetEnabled(shieldEnabled) widget.shieldColorPicker:SetEnabled(shieldEnabled) + widget.shieldGradientCB:SetEnabled(shieldEnabled) if shieldEnabled then widget.shieldDelimiterText:SetTextColor(1, 1, 1) else @@ -1076,6 +1080,7 @@ local function CreateSetting_HealthFormat(parent) widget.healAbsorbDelimiterEB.confirmBtn:Hide() widget.healAbsorbColorDropdown:SetEnabled(healAbsorbEnabled) widget.healAbsorbColorPicker:SetEnabled(healAbsorbEnabled) + widget.healAbsorbGradientCB:SetEnabled(healAbsorbEnabled) if healAbsorbEnabled then widget.healAbsorbDelimiterText:SetTextColor(1, 1, 1) else @@ -1156,6 +1161,12 @@ local function CreateSetting_HealthFormat(parent) end) widget.health1ColorPicker:SetPoint("LEFT", widget.health1ColorDropdown, "RIGHT", 5, 0) + widget.health1GradientCB = Cell.CreateCheckButton(widget, "", function(checked) + widget.format.health1.colorByHealth = checked + widget.func() + end, L["colorByHealthTip"]) + widget.health1GradientCB:SetPoint("LEFT", widget.health1ColorPicker, "RIGHT", 10, 0) + -- health2 ------------------------------ widget.health2FormatDropdown = Cell.CreateDropdown(widget, 127) widget.health2FormatDropdown:SetPoint("TOPLEFT", widget.health1ColorDropdown, "BOTTOMLEFT", 0, -35) @@ -1208,6 +1219,12 @@ local function CreateSetting_HealthFormat(parent) end) widget.health2ColorPicker:SetPoint("LEFT", widget.health2ColorDropdown, "RIGHT", 5, 0) + widget.health2GradientCB = Cell.CreateCheckButton(widget, "", function(checked) + widget.format.health2.colorByHealth = checked + widget.func() + end, L["colorByHealthTip"]) + widget.health2GradientCB:SetPoint("LEFT", widget.health2ColorPicker, "RIGHT", 10, 0) + -- shield ------------------------------- local shieldList = { {L["None"], "none"}, @@ -1270,6 +1287,12 @@ local function CreateSetting_HealthFormat(parent) end) widget.shieldColorPicker:SetPoint("LEFT", widget.shieldColorDropdown, "RIGHT", 5, 0) + widget.shieldGradientCB = Cell.CreateCheckButton(widget, "", function(checked) + widget.format.shields.colorByHealth = checked + widget.func() + end, L["colorByHealthTip"]) + widget.shieldGradientCB:SetPoint("LEFT", widget.shieldColorPicker, "RIGHT", 10, 0) + -- heal absorb -------------------------- local healAbsorbList = { {L["None"], "none"}, @@ -1332,6 +1355,12 @@ local function CreateSetting_HealthFormat(parent) end) widget.healAbsorbColorPicker:SetPoint("LEFT", widget.healAbsorbColorDropdown, "RIGHT", 5, 0) + widget.healAbsorbGradientCB = Cell.CreateCheckButton(widget, "", function(checked) + widget.format.healAbsorbs.colorByHealth = checked + widget.func() + end, L["colorByHealthTip"]) + widget.healAbsorbGradientCB:SetPoint("LEFT", widget.healAbsorbColorPicker, "RIGHT", 10, 0) + -- callback function widget:SetFunc(func) widget.func = func @@ -1346,24 +1375,28 @@ local function CreateSetting_HealthFormat(parent) widget.health1FormatDropdown:SetSelectedValue(format.health1.format) widget.health1ColorDropdown:SetSelectedValue(format.health1.color[1]) widget.health1ColorPicker:SetColor(unpack(format.health1.color[2])) + widget.health1GradientCB:SetChecked(format.health1.colorByHealth) -- health2 widget.health2FormatDropdown:SetSelectedValue(format.health2.format) widget.health2DelimiterEB:SetText(format.health2.delimiter) widget.health2ColorDropdown:SetSelectedValue(format.health2.color[1]) widget.health2ColorPicker:SetColor(unpack(format.health2.color[2])) + widget.health2GradientCB:SetChecked(format.health2.colorByHealth) -- shields widget.shieldFormatDropdown:SetSelectedValue(format.shields.format) widget.shieldDelimiterEB:SetText(format.shields.delimiter) widget.shieldColorDropdown:SetSelectedValue(format.shields.color[1]) widget.shieldColorPicker:SetColor(unpack(format.shields.color[2])) + widget.shieldGradientCB:SetChecked(format.shields.colorByHealth) -- heal absorbs widget.healAbsorbFormatDropdown:SetSelectedValue(format.healAbsorbs.format) widget.healAbsorbDelimiterEB:SetText(format.healAbsorbs.delimiter) widget.healAbsorbColorDropdown:SetSelectedValue(format.healAbsorbs.color[1]) widget.healAbsorbColorPicker:SetColor(unpack(format.healAbsorbs.color[2])) + widget.healAbsorbGradientCB:SetChecked(format.healAbsorbs.colorByHealth) end else widget = settingWidgets["healthFormat"] @@ -1523,6 +1556,7 @@ local function CreateSetting_DurationVisibility(parent) return widget end +-- Midnight: simplified duration visibility with only Always/Never options -- (Blizzard's countdown text doesn't support percentage/time thresholds) local function CreateSetting_DurationVisibilitySimple(parent) local widget @@ -1559,6 +1593,7 @@ local function CreateSetting_DurationVisibilitySimple(parent) end function widget:SetDBValue(durationVisibility) + -- Coerce pre-Midnight threshold values (0.75, 10, etc.) to "Always" -- since Blizzard's countdown text only supports on/off, not thresholds. -- The saved value isn't modified — only the dropdown display is coerced. if durationVisibility and durationVisibility ~= false then @@ -1734,35 +1769,21 @@ local function CreateSetting_BarOrientation(parent) widget = Cell.CreateFrame("CellIndicatorSettings_BarOrientation", parent, 240, 50) settingWidgets["barOrientation"] = widget - widget.orientation = Cell.CreateDropdown(widget, 245) + widget.orientation = Cell.CreateDropdown(widget, 153) widget.orientation:SetPoint("TOPLEFT", 5, -20) widget.orientation:SetItems({ { - ["text"] = L["left-to-right"], - ["value"] = "left-to-right", + ["text"] = L["Horizontal"], + ["value"] = "horizontal", ["onClick"] = function() - widget.func("left-to-right") + widget.func("horizontal") end, }, { - ["text"] = L["right-to-left"], - ["value"] = "right-to-left", - ["onClick"] = function() - widget.func("right-to-left") - end, - }, - { - ["text"] = L["top-to-bottom"], - ["value"] = "top-to-bottom", - ["onClick"] = function() - widget.func("top-to-bottom") - end, - }, - { - ["text"] = L["bottom-to-top"], - ["value"] = "bottom-to-top", + ["text"] = L["Vertical"], + ["value"] = "vertical", ["onClick"] = function() - widget.func("bottom-to-top") + widget.func("vertical") end, }, }) @@ -1778,11 +1799,6 @@ local function CreateSetting_BarOrientation(parent) -- show db value function widget:SetDBValue(orientation) - if orientation == "horizontal" then - orientation = "left-to-right" - elseif orientation == "vertical" then - orientation = "top-to-bottom" - end widget.orientation:SetSelectedValue(orientation) end else @@ -4777,7 +4793,7 @@ local function CreateSetting_Auras(parent, index) -- show db value function widget:SetDBValue(title, t, noUpDownButtons, isZeroValid, hasColorPicker) widget.title = title - widget.t = t or {} + widget.t = t widget.noUpDownButtons = noUpDownButtons widget.isZeroValid = isZeroValid widget.hasColorPicker = hasColorPicker @@ -4786,14 +4802,14 @@ local function CreateSetting_Auras(parent, index) if not auraButtons[index] then auraButtons[index] = {} end - CreateAuraButtons(widget.frame, auraButtons[index], widget.t, noUpDownButtons, isZeroValid, hasColorPicker, function(n, diff) + CreateAuraButtons(widget.frame, auraButtons[index], t, noUpDownButtons, isZeroValid, hasColorPicker, function(n, diff) local height = (n + 1) * P.Scale(20) - n * P.Scale(1) widget.frame:SetHeight(height) widget:SetHeight(height + P.Scale(22) + P.Scale(7)) if diff then parent:SetHeight(parent:GetHeight() + P.Scale(diff)) end end, widget.usePicker) - local height = (#widget.t + 1) * P.Scale(20) - #widget.t * P.Scale(1) + local height = (#t + 1) * P.Scale(20) - #t * P.Scale(1) widget.frame:SetHeight(height) widget:SetHeight(height + P.Scale(22) + P.Scale(7)) end @@ -8059,6 +8075,7 @@ function Cell.CreateIndicatorSettings(parent, settingsTable) elseif string.find(setting, "^numPerLine:") then tinsert(widgetsTable, CreateSetting_NumPerLine(parent)) elseif string.find(setting, "^font%-noOffset:") then + -- Midnight: simplified font widget for paired font configs (no anchor/offset) tinsert(widgetsTable, CreateSetting_FontNoOffset(parent)) elseif string.find(setting, "^font") then tinsert(widgetsTable, CreateSetting_Font(parent, string.match(setting, "^(font%d?):?.*$"))) From 961742ea43692c45571e62a7abfdb13b6612fc24 Mon Sep 17 00:00:00 2001 From: Dmytro Kharchenko Date: Sat, 22 Aug 2026 08:06:43 +0000 Subject: [PATCH 2/3] Add 2-decimal option for abbreviated health text F.FormatNumber now takes an optional decimals argument (defaults to 1, matching prior behavior for backward compatibility). Adds "_short2" format entries and matching dropdown options for health/deficit/ effective/shields/heal-absorb text, so users can opt into 2-decimal precision (e.g. 12.34K) per segment instead of the default 1 decimal. --- Indicators/Built-in.lua | 17 +++++++++++++++++ Locales/enUS.lua | 1 + Utils.lua | 16 +++++++++------- Widgets/Widgets_IndicatorSettings.lua | 6 ++++++ 4 files changed, 33 insertions(+), 7 deletions(-) diff --git a/Indicators/Built-in.lua b/Indicators/Built-in.lua index f66a9b73..8a94ac98 100644 --- a/Indicators/Built-in.lua +++ b/Indicators/Built-in.lua @@ -1952,6 +1952,9 @@ local formatter = { ["health_short"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.FormatNumber(health)) end, + ["health_short2"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) + return pattern:format(F.FormatNumber(health, 2)) + end, ["health_percent"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.Round(health / maxHealth * 100)) end, @@ -1961,6 +1964,9 @@ local formatter = { ["deficit_short"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.FormatNumber(health - maxHealth)) end, + ["deficit_short2"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) + return pattern:format(F.FormatNumber(health - maxHealth, 2)) + end, ["deficit_percent"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.Round((health - maxHealth) / maxHealth * 100)) end, @@ -1972,6 +1978,9 @@ local formatter = { ["effective_short"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.FormatNumber(health + absorbs - healAbsorbs)) end, + ["effective_short2"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) + return pattern:format(F.FormatNumber(health + absorbs - healAbsorbs, 2)) + end, ["effective_percent"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.Round((health + absorbs - healAbsorbs) / maxHealth * 100)) end, @@ -1985,6 +1994,10 @@ local formatter = { if absorbs == 0 then return "" end return pattern:format(F.FormatNumber(absorbs)) end, + ["shields_short2"] = function(pattern, health, maxHealth, absorbs, healAbsorbs) + if absorbs == 0 then return "" end + return pattern:format(F.FormatNumber(absorbs, 2)) + end, ["shields_percent"] = function(pattern, health, maxHealth, absorbs, healAbsorbs) if absorbs == 0 then return "" end return pattern:format(F.Round(absorbs / maxHealth * 100)) @@ -1999,6 +2012,10 @@ local formatter = { if healAbsorbs == 0 then return "" end return pattern:format(F.FormatNumber(healAbsorbs)) end, + ["healabsorbs_short2"] = function(pattern, health, maxHealth, absorbs, healAbsorbs) + if healAbsorbs == 0 then return "" end + return pattern:format(F.FormatNumber(healAbsorbs, 2)) + end, ["healabsorbs_percent"] = function(pattern, health, maxHealth, absorbs, healAbsorbs) if healAbsorbs == 0 then return "" end return pattern:format(F.Round(healAbsorbs / maxHealth * 100)) diff --git a/Locales/enUS.lua b/Locales/enUS.lua index ac2549bf..d8a3b935 100644 --- a/Locales/enUS.lua +++ b/Locales/enUS.lua @@ -27,6 +27,7 @@ select(2, ...).L = setmetatable({ ["nonPlayerAuras"] = "Only show non-player auras", ["nonPlayerAurasTip"] = "Hides lust aftereffects such as Exhaustion, Sated, Forbearance and Temporal Displacement. Encounter debuffs stay visible.", ["colorByHealthTip"] = "Blend this segment's color from red (low health) to yellow to its normal color as health rises, like ElvUI.", + ["2 Decimals"] = "2 Decimals", ["Private Dispel Overlay"] = "Private Dispel Overlay", ["Show private dispel overlay"] = "Show private dispel overlay", ["Only dispellable by me"] = "Only dispellable by me", diff --git a/Utils.lua b/Utils.lua index 49e95ca1..b5881d10 100644 --- a/Utils.lua +++ b/Utils.lua @@ -454,23 +454,25 @@ end local abs = math.abs if Cell.isAsian then - function F.FormatNumber(n) + function F.FormatNumber(n, decimals) + decimals = decimals or 1 if abs(n) >= 100000000 then - return F.Round(n / 100000000, 2) .. symbol_1B + return F.Round(n / 100000000, decimals) .. symbol_1B elseif abs(n) >= 10000 then - return F.Round(n / 10000, 1) .. symbol_10K + return F.Round(n / 10000, decimals) .. symbol_10K else return n end end else - function F.FormatNumber(n) + function F.FormatNumber(n, decimals) + decimals = decimals or 1 if abs(n) >= 1000000000 then - return F.Round(n / 1000000000, 2) .. "B" + return F.Round(n / 1000000000, decimals) .. "B" elseif abs(n) >= 1000000 then - return F.Round(n / 1000000, 2) .. "M" + return F.Round(n / 1000000, decimals) .. "M" elseif abs(n) >= 1000 then - return F.Round(n / 1000, 1) .. "K" + return F.Round(n / 1000, decimals) .. "K" else return n end diff --git a/Widgets/Widgets_IndicatorSettings.lua b/Widgets/Widgets_IndicatorSettings.lua index 189da2c4..6f3fc3ab 100644 --- a/Widgets/Widgets_IndicatorSettings.lua +++ b/Widgets/Widgets_IndicatorSettings.lua @@ -1105,18 +1105,22 @@ local function CreateSetting_HealthFormat(parent) end local effective = " |cff7b7b7b" .. L["Effective"] .. "|r" + local decimals2 = " |cff7b7b7b(" .. L["2 Decimals"] .. ")|r" local healthList = { {L["None"], "none"}, {(health + shield - healAbsorb) .. effective, "effective"}, {F.FormatNumber(health + shield - healAbsorb) .. effective, "effective_short"}, + {F.FormatNumber(health + shield - healAbsorb, 2) .. effective .. decimals2, "effective_short2"}, {F.Round((health + shield - healAbsorb) / healthMax * 100) .. "%" .. effective, "effective_percent"}, {F.Round((health + shield - healAbsorb) / healthMax * 100) .. effective, "effective_percent_no_sign"}, {health, "health"}, {F.FormatNumber(health), "health_short"}, + {F.FormatNumber(health, 2) .. decimals2, "health_short2"}, {F.Round(health / healthMax * 100) .. "%", "health_percent"}, {F.Round(health / healthMax * 100), "health_percent_no_sign"}, {health - healthMax, "deficit"}, {F.FormatNumber(health - healthMax), "deficit_short"}, + {F.FormatNumber(health - healthMax, 2) .. decimals2, "deficit_short2"}, {F.Round((health - healthMax) / healthMax * 100) .. "%", "deficit_percent"}, {F.Round((health - healthMax) / healthMax * 100), "deficit_percent_no_sign"}, } @@ -1230,6 +1234,7 @@ local function CreateSetting_HealthFormat(parent) {L["None"], "none"}, {shield, "shields"}, {F.FormatNumber(shield), "shields_short"}, + {F.FormatNumber(shield, 2) .. decimals2, "shields_short2"}, {F.Round(shield / healthMax * 100) .. "%", "shields_percent"}, {F.Round(shield / healthMax * 100), "shields_percent_no_sign"}, } @@ -1298,6 +1303,7 @@ local function CreateSetting_HealthFormat(parent) {L["None"], "none"}, {healAbsorb, "healabsorbs"}, {F.FormatNumber(healAbsorb), "healabsorbs_short"}, + {F.FormatNumber(healAbsorb, 2) .. decimals2, "healabsorbs_short2"}, {F.Round(healAbsorb / healthMax * 100) .. "%", "healabsorbs_percent"}, {F.Round(healAbsorb / healthMax * 100), "healabsorbs_percent_no_sign"}, } From 88c5110b92328f56c1c96d5e697c1f9ecbd6795f Mon Sep 17 00:00:00 2001 From: Dmytro Kharchenko Date: Sat, 22 Aug 2026 08:07:20 +0000 Subject: [PATCH 3/3] Hide deficit text at 0, with a toggle to always show it The deficit/deficit_short/deficit_short2/deficit_percent formats now suppress their output once a unit is topped off, instead of always showing "-0"/"0%". Controlled per-segment by the segment's existing (previously unused) hideIfEmptyOrFull setting, now with a real checkbox in the health text settings. Defaults to hidden for new layouts. --- Defaults/Layout_Defaults.lua | 4 +-- Defaults/Layout_Defaults_Cata_Wrath.lua | 4 +-- Defaults/Layout_Defaults_Mists.lua | 4 +-- Defaults/Layout_Defaults_TBC_Vanilla.lua | 4 +-- Indicators/Built-in.lua | 38 ++++++++++++++++-------- Locales/enUS.lua | 1 + Widgets/Widgets_IndicatorSettings.lua | 17 +++++++++++ 7 files changed, 52 insertions(+), 20 deletions(-) diff --git a/Defaults/Layout_Defaults.lua b/Defaults/Layout_Defaults.lua index a4eaa1f7..18883253 100644 --- a/Defaults/Layout_Defaults.lua +++ b/Defaults/Layout_Defaults.lua @@ -170,13 +170,13 @@ Cell.defaults.layout = { ["format"] = "effective_percent", ["color"] = {"custom_color", {1, 1, 1}}, ["colorByHealth"] = false, - ["hideIfEmptyOrFull"] = false, + ["hideIfEmptyOrFull"] = true, }, ["health2"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 1, 1}}, ["colorByHealth"] = false, - ["hideIfEmptyOrFull"] = false, + ["hideIfEmptyOrFull"] = true, ["delimiter"] = " ", }, ["shields"] = { diff --git a/Defaults/Layout_Defaults_Cata_Wrath.lua b/Defaults/Layout_Defaults_Cata_Wrath.lua index 9598375c..aa8b7bc5 100644 --- a/Defaults/Layout_Defaults_Cata_Wrath.lua +++ b/Defaults/Layout_Defaults_Cata_Wrath.lua @@ -158,13 +158,13 @@ Cell.defaults.layout = { ["format"] = "effective_percent", ["color"] = {"custom_color", {1, 1, 1}}, ["colorByHealth"] = false, - ["hideIfEmptyOrFull"] = false, + ["hideIfEmptyOrFull"] = true, }, ["health2"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 1, 1}}, ["colorByHealth"] = false, - ["hideIfEmptyOrFull"] = false, + ["hideIfEmptyOrFull"] = true, ["delimiter"] = " ", }, ["shields"] = { diff --git a/Defaults/Layout_Defaults_Mists.lua b/Defaults/Layout_Defaults_Mists.lua index 769384a2..d68c17d8 100644 --- a/Defaults/Layout_Defaults_Mists.lua +++ b/Defaults/Layout_Defaults_Mists.lua @@ -165,13 +165,13 @@ Cell.defaults.layout = { ["format"] = "effective_percent", ["color"] = {"custom_color", {1, 1, 1}}, ["colorByHealth"] = false, - ["hideIfEmptyOrFull"] = false, + ["hideIfEmptyOrFull"] = true, }, ["health2"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 1, 1}}, ["colorByHealth"] = false, - ["hideIfEmptyOrFull"] = false, + ["hideIfEmptyOrFull"] = true, ["delimiter"] = " ", }, ["shields"] = { diff --git a/Defaults/Layout_Defaults_TBC_Vanilla.lua b/Defaults/Layout_Defaults_TBC_Vanilla.lua index c1c15c6e..a9717f31 100644 --- a/Defaults/Layout_Defaults_TBC_Vanilla.lua +++ b/Defaults/Layout_Defaults_TBC_Vanilla.lua @@ -156,13 +156,13 @@ Cell.defaults.layout = { ["format"] = "effective_percent", ["color"] = {"custom_color", {1, 1, 1}}, ["colorByHealth"] = false, - ["hideIfEmptyOrFull"] = false, + ["hideIfEmptyOrFull"] = true, }, ["health2"] = { ["format"] = "none", ["color"] = {"custom_color", {1, 1, 1}}, ["colorByHealth"] = false, - ["hideIfEmptyOrFull"] = false, + ["hideIfEmptyOrFull"] = true, ["delimiter"] = " ", }, ["shields"] = { diff --git a/Indicators/Built-in.lua b/Indicators/Built-in.lua index 8a94ac98..be2e6c86 100644 --- a/Indicators/Built-in.lua +++ b/Indicators/Built-in.lua @@ -1958,17 +1958,27 @@ local formatter = { ["health_percent"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) return pattern:format(F.Round(health / maxHealth * 100)) end, - ["deficit"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) - return pattern:format(health - maxHealth) + -- hideIfZero (the segment's "Hide if 0" setting): suppress the deficit text entirely + -- once a unit is topped off, instead of always showing "-0" / "0%". + ["deficit"] = function(pattern, hideIfZero, health, maxHealth, absorbs, healAbsorbs) + local deficit = health - maxHealth + if hideIfZero and deficit == 0 then return "" end + return pattern:format(deficit) end, - ["deficit_short"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) - return pattern:format(F.FormatNumber(health - maxHealth)) + ["deficit_short"] = function(pattern, hideIfZero, health, maxHealth, absorbs, healAbsorbs) + local deficit = health - maxHealth + if hideIfZero and deficit == 0 then return "" end + return pattern:format(F.FormatNumber(deficit)) end, - ["deficit_short2"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) - return pattern:format(F.FormatNumber(health - maxHealth, 2)) + ["deficit_short2"] = function(pattern, hideIfZero, health, maxHealth, absorbs, healAbsorbs) + local deficit = health - maxHealth + if hideIfZero and deficit == 0 then return "" end + return pattern:format(F.FormatNumber(deficit, 2)) end, - ["deficit_percent"] = function(pattern, _, health, maxHealth, absorbs, healAbsorbs) - return pattern:format(F.Round((health - maxHealth) / maxHealth * 100)) + ["deficit_percent"] = function(pattern, hideIfZero, health, maxHealth, absorbs, healAbsorbs) + local deficit = health - maxHealth + if hideIfZero and deficit == 0 then return "" end + return pattern:format(F.Round(deficit / maxHealth * 100)) end, -- effective health @@ -2116,6 +2126,10 @@ local function HealthText_SetFormat(self, format) self.shields = BuildPattern(format.shields) self.healAbsorbs = BuildPattern(format.healAbsorbs) + -- hideIfEmptyOrFull: for deficit formats, suppress the "-0"/"0%" text once a unit is topped off + self.health1HideIfZero = format.health1.hideIfEmptyOrFull and true or false + self.health2HideIfZero = format.health2.hideIfEmptyOrFull and true or false + -- colorByHealth: remember which segments need their color recomputed every update -- (against the segment's own class/custom base color) instead of the baked-in one self.health1GradientColor = format.health1.colorByHealth and format.health1.color or nil @@ -2231,14 +2245,14 @@ local function HealthText_SetValue(self, health, maxHealth, shields, healAbsorbs if self.health1GradientColor or self.health2GradientColor or self.shieldsGradientColor or self.healAbsorbsGradientColor then local percent = health / maxHealth self.text:SetFormattedText("%s%s%s%s", - ApplyHealthGradientColor(self.GetHealth1(self.health1, false, health, maxHealth, shields, healAbsorbs), self.health1GradientColor, percent, unit), - ApplyHealthGradientColor(self.GetHealth2(self.health2, false, health, maxHealth, shields, healAbsorbs), self.health2GradientColor, percent, unit), + ApplyHealthGradientColor(self.GetHealth1(self.health1, self.health1HideIfZero, health, maxHealth, shields, healAbsorbs), self.health1GradientColor, percent, unit), + ApplyHealthGradientColor(self.GetHealth2(self.health2, self.health2HideIfZero, health, maxHealth, shields, healAbsorbs), self.health2GradientColor, percent, unit), ApplyHealthGradientColor(self.GetShields(self.shields, health, maxHealth, shields, healAbsorbs), self.shieldsGradientColor, percent, unit), ApplyHealthGradientColor(self.GetHealAbsorbs(self.healAbsorbs, health, maxHealth, shields, healAbsorbs), self.healAbsorbsGradientColor, percent, unit)) else self.text:SetFormattedText("%s%s%s%s", - self.GetHealth1(self.health1, false, health, maxHealth, shields, healAbsorbs), - self.GetHealth2(self.health2, false, health, maxHealth, shields, healAbsorbs), + self.GetHealth1(self.health1, self.health1HideIfZero, health, maxHealth, shields, healAbsorbs), + self.GetHealth2(self.health2, self.health2HideIfZero, health, maxHealth, shields, healAbsorbs), self.GetShields(self.shields, health, maxHealth, shields, healAbsorbs), self.GetHealAbsorbs(self.healAbsorbs, health, maxHealth, shields, healAbsorbs)) end diff --git a/Locales/enUS.lua b/Locales/enUS.lua index d8a3b935..c14cc23c 100644 --- a/Locales/enUS.lua +++ b/Locales/enUS.lua @@ -28,6 +28,7 @@ select(2, ...).L = setmetatable({ ["nonPlayerAurasTip"] = "Hides lust aftereffects such as Exhaustion, Sated, Forbearance and Temporal Displacement. Encounter debuffs stay visible.", ["colorByHealthTip"] = "Blend this segment's color from red (low health) to yellow to its normal color as health rises, like ElvUI.", ["2 Decimals"] = "2 Decimals", + ["Hide the deficit text once a unit is topped off"] = "Hide the deficit text once a unit is topped off", ["Private Dispel Overlay"] = "Private Dispel Overlay", ["Show private dispel overlay"] = "Show private dispel overlay", ["Only dispellable by me"] = "Only dispellable by me", diff --git a/Widgets/Widgets_IndicatorSettings.lua b/Widgets/Widgets_IndicatorSettings.lua index 6f3fc3ab..984125b2 100644 --- a/Widgets/Widgets_IndicatorSettings.lua +++ b/Widgets/Widgets_IndicatorSettings.lua @@ -1050,6 +1050,7 @@ local function CreateSetting_HealthFormat(parent) widget.health1ColorDropdown:SetEnabled(health1Enabled) widget.health1ColorPicker:SetEnabled(health1Enabled) widget.health1GradientCB:SetEnabled(health1Enabled) + widget.health1HideZeroCB:SetEnabled(health1Enabled) local health2Enabled = widget.format.health2.format ~= "none" widget.health2DelimiterEB:SetEnabled(health2Enabled) @@ -1057,6 +1058,7 @@ local function CreateSetting_HealthFormat(parent) widget.health2ColorDropdown:SetEnabled(health2Enabled) widget.health2ColorPicker:SetEnabled(health2Enabled) widget.health2GradientCB:SetEnabled(health2Enabled) + widget.health2HideZeroCB:SetEnabled(health2Enabled) if health2Enabled then widget.health2DelimiterText:SetTextColor(1, 1, 1) else @@ -1171,6 +1173,13 @@ local function CreateSetting_HealthFormat(parent) end, L["colorByHealthTip"]) widget.health1GradientCB:SetPoint("LEFT", widget.health1ColorPicker, "RIGHT", 10, 0) + -- only meaningful for the deficit formats, but harmless (ignored) for every other format + widget.health1HideZeroCB = Cell.CreateCheckButton(widget, "", function(checked) + widget.format.health1.hideIfEmptyOrFull = checked + widget.func() + end, L["hideIfEmptyOrFull"], L["Hide the deficit text once a unit is topped off"]) + widget.health1HideZeroCB:SetPoint("LEFT", widget.health1GradientCB, "RIGHT", 5, 0) + -- health2 ------------------------------ widget.health2FormatDropdown = Cell.CreateDropdown(widget, 127) widget.health2FormatDropdown:SetPoint("TOPLEFT", widget.health1ColorDropdown, "BOTTOMLEFT", 0, -35) @@ -1229,6 +1238,12 @@ local function CreateSetting_HealthFormat(parent) end, L["colorByHealthTip"]) widget.health2GradientCB:SetPoint("LEFT", widget.health2ColorPicker, "RIGHT", 10, 0) + widget.health2HideZeroCB = Cell.CreateCheckButton(widget, "", function(checked) + widget.format.health2.hideIfEmptyOrFull = checked + widget.func() + end, L["hideIfEmptyOrFull"], L["Hide the deficit text once a unit is topped off"]) + widget.health2HideZeroCB:SetPoint("LEFT", widget.health2GradientCB, "RIGHT", 5, 0) + -- shield ------------------------------- local shieldList = { {L["None"], "none"}, @@ -1382,6 +1397,7 @@ local function CreateSetting_HealthFormat(parent) widget.health1ColorDropdown:SetSelectedValue(format.health1.color[1]) widget.health1ColorPicker:SetColor(unpack(format.health1.color[2])) widget.health1GradientCB:SetChecked(format.health1.colorByHealth) + widget.health1HideZeroCB:SetChecked(format.health1.hideIfEmptyOrFull) -- health2 widget.health2FormatDropdown:SetSelectedValue(format.health2.format) @@ -1389,6 +1405,7 @@ local function CreateSetting_HealthFormat(parent) widget.health2ColorDropdown:SetSelectedValue(format.health2.color[1]) widget.health2ColorPicker:SetColor(unpack(format.health2.color[2])) widget.health2GradientCB:SetChecked(format.health2.colorByHealth) + widget.health2HideZeroCB:SetChecked(format.health2.hideIfEmptyOrFull) -- shields widget.shieldFormatDropdown:SetSelectedValue(format.shields.format)