@@ -125,6 +125,11 @@ local function auraHasTrackedIndicator(auraCfg)
125125 if hb and not hb .showWhenMissing then return true end
126126 if bg and not bg .showWhenMissing then return true end
127127 if bd and not bd .showWhenMissing then return true end
128+ -- Text colour-by-cover (recovered): a present-mode name/health text indicator is a
129+ -- rendering visual, so it dedups. SWM-flagged text renders nothing (unsupported).
130+ local nt , ht = auraCfg .nametext , auraCfg .healthtext
131+ if nt and nt .color and not nt .showWhenMissing then return true end
132+ if ht and ht .color and not ht .showWhenMissing then return true end
128133 return false
129134end
130135
@@ -280,6 +285,27 @@ local function buildBorderConfig(unit, map, spec)
280285 }
281286end
282287
288+ -- AD text-colour types -> the Text Designer mirror category they cover.
289+ local TEXT_MIRROR_TYPES = { nametext = " name" , healthtext = " health" }
290+
291+ -- Overlay container whose only style region is a MIRROR HOST: a plain slot-child frame
292+ -- handed back via onHost, to which the Text Designer parents its colour-by-cover mirror
293+ -- FontStrings (Render:EnableMirrors). The host contributes ONLY the slot's secret
294+ -- visibility (aura present -> covers render); the mirrors position themselves on the
295+ -- real FontStrings. frameLevelOffset 30: the host lands ~frame+32 (anchor + container +
296+ -- slot nesting), above the TD overlay (frame+25) so the cover draws over the real text.
297+ local function buildMirrorHostConfig (unit , map , onHost )
298+ return {
299+ unit = unit ,
300+ mode = " overlay" ,
301+ filter = " HELPFUL" ,
302+ candidateFilters = { includeSpellIDs = map },
303+ enabled = true ,
304+ frameLevelOffset = 30 ,
305+ style = { overlay = { mirrorHost = { onHost = onHost } } },
306+ }
307+ end
308+
283309-- Resolve the DF.Border spec for an AD border indicator from its CONFIG block (never a
284310-- live aura), mirroring Indicators:ApplyBorderToOverlay: canonical keys via BuildSpec (the
285311-- border-key fold ran in SyncFrame), black default colour. Returns nil when the border
@@ -1276,7 +1302,8 @@ function Factory:SyncFrame(frame)
12761302 -- SetUnit self-defers to regen in combat). Cheap per-pass: one config read per handle.
12771303 do
12781304 local u = frame .unit
1279- for _ , storeKey in ipairs ({ " healthbar" , " background" , " border" , " placed" }) do
1305+ for _ , storeKey in ipairs ({ " healthbar" , " background" , " border" , " placed" ,
1306+ " nametext" , " healthtext" }) do
12801307 local t = store [storeKey ]
12811308 if t then
12821309 for _ , entry in pairs (t ) do
@@ -1526,6 +1553,66 @@ function Factory:SyncFrame(frame)
15261553 teardownExcept (bd , bestName )
15271554 end
15281555
1556+ -- ---- NAME / HEALTH TEXT (colour-by-cover via Text Designer mirrors) --------------
1557+ -- Recovered 12.1 casualties. The factory never touches the real fontstrings: it
1558+ -- stands up an overlay container whose slot-child HOST is handed to the Text
1559+ -- Designer (Render:EnableMirrors), which keeps glyph-identical coloured covers in
1560+ -- sync with the real elements. Aura present -> slot shows -> covers render over the
1561+ -- text; absent -> covers hide. Single highest-priority winner per category (one
1562+ -- cover colour per element, like the other frame-level effects). Show-when-missing
1563+ -- is NOT supported for text (rendering an SWM indicator present-mode would invert
1564+ -- the user's intent) — SWM-flagged text indicators render nothing and don't dedup.
1565+ do
1566+ local TDRender = DF .TextDesigner and DF .TextDesigner .Render
1567+ for typeKey , category in pairs (TEXT_MIRROR_TYPES ) do
1568+ local st = store [typeKey ]
1569+ if not st then st = {}; store [typeKey ] = st end
1570+
1571+ local bestName , bestCfg , bestMap = pickWinner (spec , specAuras , typeKey ,
1572+ function (c ) return c .color and not c .showWhenMissing end )
1573+ if bestName and TDRender then
1574+ local r , g , b , a = readADColor (bestCfg .color )
1575+ local color = { r = r , g = g , b = b , a = a }
1576+ local structSig = includeSig (bestMap )
1577+ local coSig = colSig (bestCfg .color )
1578+ -- onHost fires on every style pass (create/ApplyStyle/Blizzard re-init):
1579+ -- stash the host for the TD-teardown recovery below and (re)register the
1580+ -- mirrors — EnableMirrors is idempotent per parent and restamps colour.
1581+ local function onHost (host )
1582+ local e = st [bestName ]
1583+ if e then e .host = host end
1584+ st ._lastHost = host
1585+ TDRender :EnableMirrors (frame , category , host , color )
1586+ end
1587+ local entry = st [bestName ]
1588+ if not entry then
1589+ local handle = DF .AuraContainer :Create (frame ,
1590+ buildMirrorHostConfig (frame .unit , bestMap , onHost ))
1591+ if handle then
1592+ st [bestName ] = { handle = handle , structSig = structSig ,
1593+ coSig = coSig , host = st ._lastHost }
1594+ end
1595+ elseif entry .structSig ~= structSig then
1596+ entry .structSig , entry .coSig = structSig , coSig
1597+ entry .handle :Rebuild (buildMirrorHostConfig (frame .unit , bestMap , onHost ))
1598+ elseif entry .coSig ~= coSig then
1599+ entry .coSig = coSig
1600+ entry .handle :ApplyStyle ({ overlay = { mirrorHost = { onHost = onHost } } })
1601+ elseif entry .host and not (frame ._tdMirrors and frame ._tdMirrors [category ]) then
1602+ -- TD Teardown (mode/profile switch) dropped the mirror registry while
1603+ -- our container persisted — re-register on the stashed host. Cheap
1604+ -- nil-check per pass, only fires after a TD teardown.
1605+ TDRender :EnableMirrors (frame , category , entry .host , color )
1606+ end
1607+ elseif TDRender then
1608+ TDRender :DisableMirrors (frame , category )
1609+ end
1610+ -- _lastHost is a scratch field, not an entry: keep teardownExcept off it.
1611+ st ._lastHost = nil
1612+ teardownExcept (st , bestName )
1613+ end
1614+ end
1615+
15291616 -- ---- PLACED ICON / SQUARE / BAR (per-indicator 1-slot containers) ----------------
15301617 -- NO winner pick: every configured icon/square/bar indicator is its own placed display,
15311618 -- keyed by instanceKey; many coexist (all share the `store.placed` store and per-key
@@ -1714,6 +1801,13 @@ function Factory:ClearFrame(frame)
17141801 teardownExcept (store .background or {}, nil )
17151802 teardownExcept (store .border or {}, nil )
17161803 teardownExcept (store .placed or {}, nil ) -- per-indicator icon/square/bar containers
1804+ teardownExcept (store .nametext or {}, nil )
1805+ teardownExcept (store .healthtext or {}, nil )
1806+ -- Release the Text Designer mirror covers owned by the two text containers above.
1807+ if DF .TextDesigner and DF .TextDesigner .Render then
1808+ DF .TextDesigner .Render :DisableMirrors (frame , " name" )
1809+ DF .TextDesigner .Render :DisableMirrors (frame , " health" )
1810+ end
17171811 releaseBgAnchor (store ) -- containers gone above; drop the background anchor too
17181812 frame .dfADHealthMirror = nil -- health-mirror bar torn down; drop the feed ref
17191813 -- Sound: reconcile to config with AD now off -> unregisters every applied-sound handle
@@ -1737,20 +1831,17 @@ end
17371831-- re-assert alpha every update) with no arbitration layer. → casualty. P4.7 overlays the
17381832-- framealpha type's controls (and its Expiring group).
17391833--
1740- -- * healthtext (ref Indicators:ApplyHealthText) — recolours the health-text fontstring on
1741- -- presence. Recolouring the REAL fontstring needs a presence-gated SetAuraColorOverride
1742- -- (a Lua call gated on a secret we can't read). The duplicate-fontstring workaround is
1743- -- impossible here: the health-text STRING is a SECRET value in combat, so a child clone
1744- -- can't be populated read-free. → hard casualty. P4.7 overlays the healthtext controls.
1745- --
1746- -- * nametext (ref Indicators:ApplyNameText) — same recolour-on-presence shape. The unit
1747- -- NAME is public, so a duplicate fontstring clone is technically read-free (unlike
1748- -- healthtext) — but it must mirror the Text Designer's live font/anchor/justify AND its
1749- -- formatted string (class colour, truncation, status suffixes), re-syncing on every TD
1750- -- re-render, or it ghosts/drifts over the real name. That's a fragile reimplementation of
1751- -- TD name rendering for a colour tint, and the failure mode is user-visible doubled text.
1752- -- Rejected on robustness grounds (correctness > coverage). → casualty. P4.7 overlays the
1753- -- nametext controls. (Revisit only if a clean TD-fontstring clone lands.)
1834+ -- * nametext / healthtext — RECOVERED (colour-by-cover). Originally written off: the
1835+ -- real fontstring can't be recoloured (presence-gated call on a secret), and a clone
1836+ -- was thought impossible for health text ("the string is secret in combat"). Both
1837+ -- conclusions fell to the secret-passthrough finding: FontStrings ACCEPT secret
1838+ -- values, so the Text Designer feeds a duplicate cover FontString the SAME resolved
1839+ -- font + SafeText value it gives the real element (glyph-identical by construction,
1840+ -- zero reads — TextDesigner/Render.lua EnableMirrors), and the cover rides an AD
1841+ -- overlay slot's secret visibility. See the NAME / HEALTH TEXT block in SyncFrame.
1842+ -- Residual limits: expiring colour swaps stay dead (remaining-time), text SWM is
1843+ -- unsupported, inline |c codes in group items keep their embedded colour, and the
1844+ -- cover ignores the OOR text fade (it lives outside the TD overlay).
17541845-- ============================================================
17551846
17561847-- ============================================================
0 commit comments