From 7862871e3f7b26758820eb0b49864ce9847a86bd Mon Sep 17 00:00:00 2001 From: Narcasung Date: Mon, 17 Aug 2026 21:24:42 +0200 Subject: [PATCH] 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. --- Modules/TalentFrame.lua | 22 +++++++++++++++++----- Modules/VanityFrame.lua | 15 +++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Modules/TalentFrame.lua b/Modules/TalentFrame.lua index 11bfc56..3450b3e 100644 --- a/Modules/TalentFrame.lua +++ b/Modules/TalentFrame.lua @@ -851,7 +851,12 @@ local function SkinFrame(frame) SkinCloseButton(frame) 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) SkinTabs() SkinSpecChoices() @@ -870,10 +875,17 @@ function CoA:InitializeTalentFrame() if not E.private.skins.blizzard.enable 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 - self:CancelTimer(self.talentFrameTimer) - self.talentFrameTimer = nil + self:UnregisterEvent("ADDON_LOADED") end - end, 0.5) + end) end diff --git a/Modules/VanityFrame.lua b/Modules/VanityFrame.lua index 77fbc9d..85de314 100644 --- a/Modules/VanityFrame.lua +++ b/Modules/VanityFrame.lua @@ -163,10 +163,17 @@ function CoA:InitializeVanityFrame() if not E.private.skins.blizzard.enable 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 - self:CancelTimer(self.vanityFrameTimer) - self.vanityFrameTimer = nil + self:UnregisterEvent("ADDON_LOADED") end - end, 0.5) + end) end