Kill first-render flicker on Talent/Vanity Blizzard-frame skins

The 0.5s ScheduleRepeatingTimer poll that caught these frames couldn't
catch them before their first-ever :Show() this session, since they're
created on-demand the instant the player opens them -- native art got
one frame of paint before the poll's next tick could skin it.

Switched to a generic ADDON_LOADED listener instead: it fires
synchronously (before control returns to whatever opens the frame)
the moment the owning addon finishes loading, so skinning now lands
before the frame is ever shown. Confirmed in-game via an ADDON_LOADED
probe on the talent frame's addon before implementing.

Also fixes a regression this introduced: TalentFrame's CropBackground
was caching the background texture's "original" tex coords on its
first-ever call for later crop math. That first call now happens
pre-Show, before Blizzard's own code sets the texture's real art-tile
region, so it was permanently baking in the XML-template default
(full 0..1) as the baseline. Moved the crop to run only from OnShow,
where the real coords are already set.
This commit is contained in:
2026-08-17 21:24:42 +02:00
parent 62a6a2cc57
commit 7862871e3f
2 changed files with 28 additions and 9 deletions
+17 -5
View File
@@ -851,7 +851,12 @@ local function SkinFrame(frame)
SkinCloseButton(frame) SkinCloseButton(frame)
SkinBottomBar() SkinBottomBar()
CropBackground() -- Not CropBackground() here: this runs pre-Show now (see InitializeTalentFrame),
-- before Blizzard's own code sets the background texture's real art-tile
-- coords -- it's still XML-template default (full 0..1) at this point. Crop
-- caches "original" coords on first call and never recaptures, so cropping
-- here would permanently bake in the wrong baseline. OnShow (below) is the
-- only place this is safe to run.
SkinChildren(frame, 1) SkinChildren(frame, 1)
SkinTabs() SkinTabs()
SkinSpecChoices() SkinSpecChoices()
@@ -870,10 +875,17 @@ function CoA:InitializeTalentFrame()
if not E.private.skins.blizzard.enable then return end if not E.private.skins.blizzard.enable then return end
if TryHook() then return end if TryHook() then return end
self.talentFrameTimer = self:ScheduleRepeatingTimer(function() -- Frame is created on-demand by its owning addon (e.g. Ascension_CoATalents),
-- the instant the player first opens it -- a poll can't catch that before the
-- native art gets a paint. ADDON_LOADED fires (synchronously, before control
-- returns to whatever code calls :Show()) the moment that addon finishes
-- loading, so skinning here lands before the first-ever :Show(), killing the
-- one-frame flicker a poll-based catch can't avoid. Confirmed in-game.
local loader = CreateFrame("Frame")
loader:RegisterEvent("ADDON_LOADED")
loader:SetScript("OnEvent", function(self)
if TryHook() then if TryHook() then
self:CancelTimer(self.talentFrameTimer) self:UnregisterEvent("ADDON_LOADED")
self.talentFrameTimer = nil
end end
end, 0.5) end)
end end
+11 -4
View File
@@ -163,10 +163,17 @@ function CoA:InitializeVanityFrame()
if not E.private.skins.blizzard.enable then return end if not E.private.skins.blizzard.enable then return end
if TryHook() then return end if TryHook() then return end
self.vanityFrameTimer = self:ScheduleRepeatingTimer(function() -- Frame is created on-demand by its owning addon, the instant the player
-- first opens it -- a poll can't catch that before native art gets a
-- paint. ADDON_LOADED fires (synchronously, before control returns to
-- whatever code calls :Show()) the moment that addon finishes loading, so
-- skinning here lands before the first-ever :Show(). Confirmed in-game on
-- the talent frame's identical pattern; see TalentFrame.lua.
local loader = CreateFrame("Frame")
loader:RegisterEvent("ADDON_LOADED")
loader:SetScript("OnEvent", function(self)
if TryHook() then if TryHook() then
self:CancelTimer(self.vanityFrameTimer) self:UnregisterEvent("ADDON_LOADED")
self.vanityFrameTimer = nil
end end
end, 0.5) end)
end end