Skip to content

Commit 20f6427

Browse files
author
mergetest
committed
Aura Designer 12.1: recover name/health text colour via colour-by-cover (T2)
Wire the Text Designer mirror engine to aura presence. A new additive mirrorHost overlay region hands the AD a plain slot-child frame; the factory stands up one overlay container per text category (single highest-priority winner, like the other frame-level effects) and registers the TD mirrors on its host - aura present, the slot shows and the glyph-identical coloured covers render over the real name/health text; absent, they hide. The factory never touches the real fontstrings. TD teardown (mode/profile switch) is self-healing via a cheap per-pass re-register; unit retarget and ClearFrame cover the new stores; present-mode text indicators now count for buff-bar dedup. GUI: the nametext/healthtext whole-type limitation lifts - Color is editable; Show When Missing (unsupported for text covers) and Expiring stay greyed. Frame alpha remains the only whole-type casualty.
1 parent 2ed4be0 commit 20f6427

3 files changed

Lines changed: 149 additions & 23 deletions

File tree

AuraDesigner/Factory.lua

Lines changed: 106 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -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
129134
end
130135

@@ -280,6 +285,27 @@ local function buildBorderConfig(unit, map, spec)
280285
}
281286
end
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
-- ============================================================

AuraDesigner/Options.lua

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3831,9 +3831,10 @@ local function BuildTypeContent(parent, typeKey, auraName, width, optProxy, yOff
38313831
-- Appearance
38323832
AddGroup(L["Appearance"], function(g)
38333833
g:AddWidget(GUI:CreateColorPicker(parent, L["Color"], proxy, "color", true, RPL, RPL, true), 28)
3834-
g:AddWidget(GUI:CreateCheckbox(parent, L["Show When Missing"], proxy, "showWhenMissing", function()
3834+
swmCheck = GUI:CreateCheckbox(parent, L["Show When Missing"], proxy, "showWhenMissing", function()
38353835
DF.AuraDesigner.Engine:ForceRefreshAllFrames()
3836-
end), 28)
3836+
end)
3837+
g:AddWidget(swmCheck, 28)
38373838
end)
38383839
-- Expiring
38393840
AddGroup(L["Expiring"], function(g)
@@ -3855,9 +3856,10 @@ local function BuildTypeContent(parent, typeKey, auraName, width, optProxy, yOff
38553856
-- Appearance
38563857
AddGroup(L["Appearance"], function(g)
38573858
g:AddWidget(GUI:CreateColorPicker(parent, L["Color"], proxy, "color", true, RPL, RPL, true), 28)
3858-
g:AddWidget(GUI:CreateCheckbox(parent, L["Show When Missing"], proxy, "showWhenMissing", function()
3859+
swmCheck = GUI:CreateCheckbox(parent, L["Show When Missing"], proxy, "showWhenMissing", function()
38593860
DF.AuraDesigner.Engine:ForceRefreshAllFrames()
3860-
end), 28)
3861+
end)
3862+
g:AddWidget(swmCheck, 28)
38613863
end)
38623864
-- Expiring
38633865
AddGroup(L["Expiring"], function(g)
@@ -4192,10 +4194,26 @@ local function BuildTypeContent(parent, typeKey, auraName, width, optProxy, yOff
41924194
GUI:BlockControl12_1(expireAlertGroup, "limitation",
41934195
{ id = "ad:sound:expire", page = L["Aura Designer"], when = ADgate })
41944196
end
4195-
elseif typeKey == "framealpha" or typeKey == "nametext" or typeKey == "healthtext" then
4196-
-- Permanent: these indicators need a read-free value the 12.1 aura
4197-
-- system can't provide, so the whole effect is unavailable.
4198-
BlockGroups("limitation", "ad:" .. typeKey)
4197+
elseif typeKey == "framealpha" then
4198+
-- Permanent: whole-frame alpha needs frame:SetAlpha gated on secret presence
4199+
-- and collides with the range/OOR alpha owners — the whole effect is unavailable.
4200+
BlockGroups("limitation", "ad:framealpha")
4201+
elseif typeKey == "nametext" or typeKey == "healthtext" then
4202+
-- RECOVERED (colour-by-cover): the base Color works — the Text Designer keeps a
4203+
-- glyph-identical coloured cover in sync with the real element (Render mirrors)
4204+
-- and the aura slot's secret visibility shows it on presence. Two surgical blocks:
4205+
-- * Show When Missing — unsupported for text covers (drawing the cover in
4206+
-- present-mode would invert the intent; a text missing-window is future work).
4207+
if swmCheck then
4208+
GUI:BlockControl12_1(swmCheck, "limitation",
4209+
{ id = "ad:" .. typeKey .. ":swm", page = L["Aura Designer"], when = ADgate })
4210+
end
4211+
-- * Expiring — permanent limitation (the colour swap near expiry needs
4212+
-- remaining-time, unreadable on the container path).
4213+
if expiringGroup then
4214+
GUI:BlockControl12_1(expiringGroup, "limitation",
4215+
{ id = "ad:" .. typeKey .. ":expiring", page = L["Aura Designer"], when = ADgate })
4216+
end
41994217
elseif typeKey == "healthbar" or typeKey == "background" or typeKey == "border" then
42004218
-- Base effect settings (colour / mode / style) work on the container
42014219
-- engine. Two surgical blocks on top:

Frames/AuraContainer.lua

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,23 @@ local function styleButton_regions(slot, config)
306306
sb:SetAlpha(hm.alpha or 1)
307307
if type(hm.onBar) == "function" then hm.onBar(sb) end
308308
end
309+
-- MIRROR HOST — a plain child frame of the slot handed back to the consumer
310+
-- (the Aura Designer name/health text colour-by-cover). The consumer parents
311+
-- Text-Designer mirror FontStrings to it: the host contributes ONLY the slot's
312+
-- secret visibility chain (aura present -> host visible -> covers render); the
313+
-- mirrors position themselves by anchoring to the real FontStrings. onHost fires
314+
-- every style pass (create + ApplyStyle + Blizzard re-init) so the consumer's
315+
-- EnableMirrors registration is always current. ADDITIVE: only the AD text
316+
-- consumer sets it; tintColor/healthMirror and the #205 rows are untouched.
317+
local mh = ov and ov.mirrorHost
318+
if mh then
319+
if not slot.dfMirrorHost then
320+
slot.dfMirrorHost = CreateFrame("Frame", nil, slot)
321+
slot.dfMirrorHost:SetAllPoints(slot)
322+
slot.dfMirrorHost:EnableMouse(false)
323+
end
324+
if type(mh.onHost) == "function" then mh.onHost(slot.dfMirrorHost) end
325+
end
309326
else
310327
-- ROW mode content is EITHER the aura's own ICON texture (native SetIcon fills
311328
-- it) OR a solid-colour SQUARE fill (DF-owned static colour — the Aura Designer

0 commit comments

Comments
 (0)