From 5f74e44252264cc9e36842c5951bce17b3da0372 Mon Sep 17 00:00:00 2001 From: JuJuFX-dev Date: Thu, 10 Sep 2026 12:13:24 +0200 Subject: [PATCH 1/2] Render Damage Meter bar height in UI units like the rest of the window barHeight, shBarHeight and the spell history iconSize were converted to a physical pixel count (value * PP.mult), while the window width, fonts and bar spacing are coordinate units. At any UI scale other than pixel-perfect the bars drifted out of proportion with their own window, so a shared profile rendered visibly thinner or thicker bars depending on the receiving user's resolution and UI scale. Treat these values as coordinates and only snap them to the pixel grid. RowMetrics keeps both terms on the frame's effective-scale grid, and its fourth return value is still one physical pixel, so no call site changes. Line thicknesses (borders, dividers, the thin line texture) stay physical. --- .../EllesmereUIDamageMeters.lua | 22 +++++++++---------- .../EllesmereUIDamageMeters_SpellHistory.lua | 16 ++++++-------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/EllesmereUIDamageMeters/EllesmereUIDamageMeters.lua b/EllesmereUIDamageMeters/EllesmereUIDamageMeters.lua index 92cdce4a2..c880a1a1e 100644 --- a/EllesmereUIDamageMeters/EllesmereUIDamageMeters.lua +++ b/EllesmereUIDamageMeters/EllesmereUIDamageMeters.lua @@ -996,24 +996,22 @@ local function PhysicalPixels(userValue) return value end --- Row geometry with both terms on ONE pixel grid. barHeight is stored as a --- physical pixel count (plain slider), barSpacing in coordinate units (pixel --- slider), so it carries the UI scale it was set at. Snapping only the height --- left the stride between two grids and -((i-1) * stride) drifted down the --- list: a spacing of 1 then rendered as 0px on some rows and 2px on others, at --- a fractional UI scale and equally at a pixel-perfect one whenever the value --- had been saved at another scale. +-- Row geometry with both terms on ONE pixel grid. barHeight and barSpacing are +-- both coordinate units, like the window width and fonts, so bars keep their +-- proportion to the window at any UI scale and a shared profile renders the +-- same relative size for everyone. Both are snapped against the same effective +-- scale: a stride between two grids drifts down the list (-((i-1) * stride)), +-- rendering a spacing of 1 as 0px on some rows and 2px on others. -- Returns barH, barSp, stride and one physical pixel, in coordinate units. -local function RowMetrics(heightPx, spacingCoord, es) +local function RowMetrics(height, spacingCoord, es) local PP = EUI and EUI.PP if PP and PP.perfect and PP.SnapForES then if not es or es <= 0 then es = (UIParent and UIParent:GetEffectiveScale()) or 1 end - local onePixel = PP.perfect / es - local barH = PP.SnapForES((heightPx or 18) * onePixel, es) + local barH = PP.SnapForES(height or 18, es) local barSp = PP.SnapForES(spacingCoord or 2, es) - return barH, barSp, barH + barSp, onePixel + return barH, barSp, barH + barSp, PP.perfect / es end - local barH, barSp = PhysicalPixels(heightPx or 18), spacingCoord or 2 + local barH, barSp = height or 18, spacingCoord or 2 return barH, barSp, barH + barSp, (PP and PP.mult) or 1 end -- On ns as well: CreateDMWindow sits at Lua 5.1's 60-upvalue cap, so its call diff --git a/EllesmereUIDamageMeters/EllesmereUIDamageMeters_SpellHistory.lua b/EllesmereUIDamageMeters/EllesmereUIDamageMeters_SpellHistory.lua index c5a730c1b..38a91e561 100644 --- a/EllesmereUIDamageMeters/EllesmereUIDamageMeters_SpellHistory.lua +++ b/EllesmereUIDamageMeters/EllesmereUIDamageMeters_SpellHistory.lua @@ -112,14 +112,12 @@ local function SetFont(fs, size) fs:SetFont(font, size, flags) end --- Snapped through PP.Scale so accumulated row offsets (stride * index) don't drift off the pixel --- grid from float dust -- without this, a spacing of 1 can round to 0px on some rows and 2px on others. -local function PhysicalPixels(val) +-- Icon size is a coordinate value like the window width and icon spacing, so it +-- keeps its proportion at any UI scale; only snapped onto the pixel grid. +local function SnapSize(val) local PP = EUI and EUI.PP - local mult = (PP and PP.mult) or 1 - local value = (val or 0) * mult - if PP and PP.Scale then return PP.Scale(value) end - return value + if PP and PP.Snap then return PP.Snap(val or 0) end + return val or 0 end local function GetBarTexturePath() @@ -485,7 +483,7 @@ local ANIM_SLIDE_PX = 6 -- the history length or growth direction never makes the row wander. local function IconStripGeometry(count) local sh = DB() - local iconSz = PhysicalPixels(sh.iconSize or 24) + local iconSz = SnapSize(sh.iconSize or 24) local gap = sh.iconSpacing or 1 local dir = sh.growDirection or "LEFT" count = max(1, count or sh.iconCount or 5) @@ -807,7 +805,7 @@ BuildIconStrip = function() -- during animation. No strip-level background needed. end - local iconSz = PhysicalPixels(sh.iconSize or 24) + local iconSz = SnapSize(sh.iconSize or 24) local gap = sh.iconSpacing or 1 local dir = sh.growDirection or "LEFT" local iconZoom = sh.iconZoom or 0.08 From f1d68372e79eb03049337dd05430ca98e0c7e77a Mon Sep 17 00:00:00 2001 From: JuJuFX-dev Date: Thu, 10 Sep 2026 12:24:59 +0200 Subject: [PATCH 2/2] Keep existing Damage Meter bar sizes through the UI unit change Add a one-time global migration that multiplies barHeight and the Spell History shBarHeight/iconSize by the old physical-pixel factor (768 / screen height / ppUIScale), so every profile that exists at update time keeps its exact rendered size. Pixel-perfect setups (factor within 1%) are left untouched. Global scope keeps imports out of it: profile-scope stamps ride exports, so an old string would be converted with the recipient's factor and freeze their UI scale into it. After this ran, imported values are read as UI units. A per-profile stamp keeps the body idempotent if a full-account import replaces the global migration flags. --- EllesmereUI_Migration.lua | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/EllesmereUI_Migration.lua b/EllesmereUI_Migration.lua index 5477c0fbe..9c709930b 100644 --- a/EllesmereUI_Migration.lua +++ b/EllesmereUI_Migration.lua @@ -4226,3 +4226,63 @@ EllesmereUI.RegisterMigration({ strip(ctx.profile.condOverrides) end, }) + +-- Damage Meters barHeight and Spell History shBarHeight/iconSize now render in UI +-- units instead of physical pixels (value * perfect / ppUIScale, UIParent-parented +-- frames). Multiply by that factor once so existing profiles keep their exact size. +-- GLOBAL on purpose: a profile stamp rides exports, so an old string would get the +-- RECIPIENT's factor frozen in; imports after this ran are read as UI units. +-- Per-profile stamps keep it idempotent when a full-account import resets flags. +EllesmereUI.RegisterMigration({ + id = "dm_bar_height_ui_units_v1", + scope = "global", + description = "Convert Damage Meters bar height and Spell History bar height/icon size from physical pixels to UI units, keeping every existing profile's rendered size.", + body = function(ctx) + local ID = "dm_bar_height_ui_units_v1" + local db = ctx.db + if not db or type(db.profiles) ~= "table" then return end + -- No stamp without the real factor: an error retries next session + -- (Startup seeds ppUIScale at login). + local _, physH = GetPhysicalScreenSize() + if type(physH) ~= "number" or physH <= 0 then error("physical screen size not available") end + local uiScale = db.ppUIScale + if type(uiScale) ~= "number" or uiScale <= 0 then error("ppUIScale not set yet") end + -- Same legacy normalization EllesmereUI_Startup applies before SetScale. + if uiScale == 0.53 then uiScale = 0.5333333333 + elseif uiScale == 0.71 then uiScale = 0.7111111111 end + local factor = (768 / physH) / uiScale + -- Within 1% the 40px slider maximum moves by under half a pixel, so the + -- snapped size is identical: pixel-perfect setups keep their values as-is. + local convert = math.abs(factor - 1) > 0.01 + local function conv(v, default) + local n = type(v) == "number" and v or default + return floor(n * factor * 10000 + 0.5) / 10000 + end + for _, profData in pairs(db.profiles) do + if type(profData) == "table" then + local stamps = profData._migrations + if type(stamps) ~= "table" then + stamps = {} + profData._migrations = stamps + end + if not stamps[ID] then + -- Folder present = DM ran in this profile; dm itself is + -- missing when every value was a stripped default. + local addon = convert and type(profData.addons) == "table" + and profData.addons.EllesmereUIDamageMeters + if type(addon) == "table" then + if type(addon.dm) ~= "table" then addon.dm = {} end + local dm = addon.dm + dm.barHeight = conv(dm.barHeight, 18) + local sh = dm.spellHistory + if type(sh) == "table" then + sh.shBarHeight = conv(sh.shBarHeight, 20) + sh.iconSize = conv(sh.iconSize, 36) + end + end + stamps[ID] = true + end + end + end + end, +})