@@ -170,6 +170,7 @@ local function applyAppearance(fs, frame, elem, globalDefaults)
170170 fs ._useClassColor = false
171171 fs :SetTextColor (app .color .r , app .color .g , app .color .b , app .color .a or 1 )
172172 end
173+ return app -- resolved appearance, reused by the AD mirror sync
173174end
174175
175176-- Applies position to the FontString (separate from appearance so we can
@@ -181,6 +182,90 @@ local function applyPosition(fs, frame, elem, fontStringsById, enabledById)
181182 elem .offsetX or 0 , elem .offsetY or 0 )
182183end
183184
185+ -- ============================================================
186+ -- AURA DESIGNER MIRRORS (12.1 name/health text colour-by-cover)
187+ -- A mirror is a DUPLICATE FontString parented to a region the Aura Designer
188+ -- owns (an aura-slot child whose visibility Blizzard drives from the tracked
189+ -- aura's secret presence). Aura present -> the slot shows -> the coloured
190+ -- cover renders over the real text; absent -> the cover hides and the real
191+ -- text shows. Recolour-by-cover: the REAL FontString is never touched, so
192+ -- nothing is gated on a secret.
193+ -- SYNC-AT-SOURCE: the mirror is fed inside updateOne from the SAME resolved
194+ -- inputs as the real FontString — DF:SafeSetFont with the same font/size/
195+ -- outline (so pixel-perfect adjustments match, glyph-identical cover) and
196+ -- SetText with the same SafeText value (secrets pass through unread; never
197+ -- compare/measure/string-op mirror text). Position tracks via SetAllPoints
198+ -- on the real FontString (render-side, secret rects fine). Colour is the one
199+ -- property never copied — the mirror keeps the AD override colour.
200+ -- KNOWN LIMIT: inline |c colour codes embedded by group items keep their
201+ -- embedded colour under the cover (SetTextColor can't override them, and
202+ -- stripping possibly-secret text is forbidden).
203+ -- ============================================================
204+
205+ -- Does this element belong to a mirror category? Single elements match via
206+ -- CONTENT_HINTS; a "group" element matches when ANY of its items does (the
207+ -- category set a group renders is its items' union).
208+ local function mirrorCategoryMatches (elem , category )
209+ if CONTENT_HINTS [elem .contentType ] == category then return true end
210+ if elem .contentType == " group" and type (elem .items ) == " table" then
211+ for _ , raw in ipairs (elem .items ) do
212+ local ct = type (raw ) == " table" and (raw .contentType or raw .type ) or raw
213+ if CONTENT_HINTS [ct ] == category then return true end
214+ end
215+ end
216+ return false
217+ end
218+
219+ -- Feed one rendered element to every matching mirror (called from updateOne
220+ -- with the element's resolved appearance + the exact safe text just set on
221+ -- the real FontString). A registered-but-no-longer-matching mirror (the user
222+ -- re-typed a group's items) is hidden so it can't linger as a stale cover.
223+ local function mirrorElement (frame , elem , fs , app , safeText )
224+ for _ , reg in pairs (frame ._tdMirrors ) do
225+ local m = reg .byId [elem .id ]
226+ if mirrorCategoryMatches (elem , reg .category ) then
227+ if not m then
228+ m = reg .parent :CreateFontString (nil , " OVERLAY" )
229+ m :SetAllPoints (fs ) -- anchors track the real text render-side
230+ local c = reg .color
231+ m :SetTextColor (c .r , c .g , c .b , c .a or 1 )
232+ reg .byId [elem .id ] = m
233+ end
234+ -- Same setter, same resolved inputs -> identical rendering.
235+ DF :SafeSetFont (m , fontPath (app .font ), app .fontSize , app .outline )
236+ -- Same SafeText value the real FontString just received (secret
237+ -- passthrough; pcall + warn-once purely as a degrade path).
238+ local ok = pcall (m .SetText , m , safeText )
239+ if ok then
240+ m :SetShown (fs :IsShown ())
241+ else
242+ m :Hide ()
243+ if not Render ._mirrorWarned then
244+ Render ._mirrorWarned = true
245+ DF :DebugWarn (" TD" , " mirror SetText failed — AD text colour degraded off" )
246+ end
247+ end
248+ elseif m then
249+ m :Hide ()
250+ end
251+ end
252+ end
253+
254+ -- Post-render visibility pass: mirrors follow their real FontStrings' shown
255+ -- state (covers disabled/deleted elements and the master-off path — every
256+ -- hide funnels through here). Content/appearance are owned by mirrorElement.
257+ local function syncMirrors (frame )
258+ local regs = frame ._tdMirrors
259+ if not regs then return end
260+ local fss = frame ._tdFontStrings
261+ for _ , reg in pairs (regs ) do
262+ for id , m in pairs (reg .byId ) do
263+ local fs = fss and fss [id ]
264+ if fs then m :SetShown (fs :IsShown ()) else m :Hide () end
265+ end
266+ end
267+ end
268+
184269-- ============================================================
185270-- RENDER ONE ELEMENT
186271-- ============================================================
@@ -196,7 +281,7 @@ local function updateOne(frame, elem, source, globalDefaults, enabledById)
196281 return
197282 end
198283 local fs = acquireFontString (frame , elem )
199- applyAppearance (fs , frame , elem , globalDefaults )
284+ local app = applyAppearance (fs , frame , elem , globalDefaults )
200285 applyPosition (fs , frame , elem , frame ._tdFontStrings , enabledById )
201286 -- Apply class color if requested
202287 if fs ._useClassColor then
@@ -218,81 +303,12 @@ local function updateOne(frame, elem, source, globalDefaults, enabledById)
218303 fs :SetTextColor (ov .r , ov .g , ov .b , ov .a or 1 )
219304 end
220305 local text = getResolver ():Resolve (elem , source )
221- fs :SetText (getMS ().SafeText (text ))
306+ local safeText = getMS ().SafeText (text )
307+ fs :SetText (safeText )
222308 fs :Show ()
223- end
224-
225- -- ============================================================
226- -- AURA DESIGNER MIRRORS (12.1 name/health text colour-by-cover)
227- -- A mirror is a DUPLICATE FontString parented to a region the Aura Designer
228- -- owns (an aura-slot child whose visibility Blizzard drives from the tracked
229- -- aura's secret presence). TD keeps every mirror glyph-identical to its real
230- -- FontString — font, anchors, justify, shadow, text — EXCEPT the colour,
231- -- which stays the AD override colour. Aura present -> the slot shows -> the
232- -- coloured cover renders over the real text; absent -> the cover hides and
233- -- the real text shows. Recolour-by-cover: the REAL FontString is never
234- -- touched, so nothing is gated on a secret.
235- -- SECRET RULES: the text copy passes the real FontString's content through
236- -- GetText -> SetText without inspecting it (FontStrings accept secret
237- -- values; only Lua string ops on them error). Never compare, measure or
238- -- string-op mirror text. Everything else copied is a plain getter.
239- -- ============================================================
240-
241- local function syncOneMirror (reg , id , fs )
242- local m = reg .byId [id ]
243- if not m then
244- m = reg .parent :CreateFontString (nil , " OVERLAY" )
245- m :SetAllPoints (fs ) -- anchors track the real text render-side (secret rects OK)
246- local c = reg .color
247- m :SetTextColor (c .r , c .g , c .b , c .a or 1 )
248- reg .byId [id ] = m
249- end
250- -- Font / justify / shadow: plain getters, never secret.
251- pcall (function ()
252- local path , size , flags = fs :GetFont ()
253- if path then m :SetFont (path , size , flags ) end
254- m :SetJustifyH (fs :GetJustifyH ())
255- m :SetJustifyV (fs :GetJustifyV ())
256- local sr , sg , sb , sa = fs :GetShadowColor ()
257- if sr then m :SetShadowColor (sr , sg , sb , sa ) end
258- local sx , sy = fs :GetShadowOffset ()
259- if sx then m :SetShadowOffset (sx , sy ) end
260- end )
261- -- Text: SECRET PASSTHROUGH — hand the value straight across, never inspect
262- -- it. pcall + warn-once: if a client build ever rejects the round-trip we
263- -- degrade to a hidden mirror instead of an error storm.
264- local ok = pcall (function () m :SetText (fs :GetText ()) end )
265- if ok then
266- m :SetShown (fs :IsShown ())
267- else
268- m :Hide ()
269- if not Render ._mirrorWarned then
270- Render ._mirrorWarned = true
271- DF :DebugWarn (" TD" , " mirror text passthrough failed (GetText->SetText) — AD text colour degraded off" )
272- end
273- end
274- end
275-
276- -- Post-render pass: sync every registered mirror to its real FontString and
277- -- hide mirrors whose element vanished. Runs at the END of UpdateFrame (after
278- -- every hide in the pass has landed), on the master-off early-out, and from
279- -- EnableMirrors. Zero work when no mirrors are registered.
280- local function syncMirrors (frame )
281- local regs = frame ._tdMirrors
282- if not regs then return end
283- local fss = frame ._tdFontStrings
284- for _ , reg in pairs (regs ) do
285- for id , m in pairs (reg .byId ) do
286- local fs = fss and fss [id ]
287- if not fs or fs ._tdCategory ~= reg .category then m :Hide () end
288- end
289- if fss then
290- for id , fs in pairs (fss ) do
291- if fs ._tdCategory == reg .category then
292- syncOneMirror (reg , id , fs )
293- end
294- end
295- end
309+ -- AD mirrors: feed the coloured cover the SAME resolved appearance + text.
310+ if frame ._tdMirrors then
311+ mirrorElement (frame , elem , fs , app , safeText )
296312 end
297313end
298314
0 commit comments