Files
ElvUI_CoA/Modules/TalentFrame.lua
T
Narcasung 63e179e96e Keep the scroll arrows skinned when the list refreshes
The scroll frame re-shows the button's own native art whenever it
recalculates, and one skinning pass can't hold against it: ElvUI's
HandleNextPrevButton guards on isSkinned, so a second call does nothing.

The talent frame already carried the answer -- re-hide the art by file
from OnUpdate and pin the three state textures -- so it moves into the
shared layer as Skin:ScrollArrow and the Challenges list scrolls take it
too. Routing the talent frame through it also picks up the
disabled-hover fix every other button already has.
2026-08-22 16:59:59 +02:00

736 lines
26 KiB
Lua

local E, L, V, P, G = unpack(ElvUI)
local S = E:GetModule("Skins")
local CoA = E:GetModule("CoA")
local Skin = CoA.Skin
local FRAME_NAME = "CoATalentFrame"
-- The talent tree is the one part of the frame we must not touch: every node
-- is an icon button whose border/overlay textures encode rank and
-- availability, so ElvUI's generic button handling would flatten the
-- information out of them. The nodes and connectors live in pools under
-- SpecTree, with the scene art in FXFrame, so those subtrees are skipped by
-- name. TreeView itself is walked, because the bottom bar is inside it.
local EXCLUDED = {"PoolFrame", "SpecTree", "ClassTree", "FXFrame", "ShadowOverlay"}
local function IsTreeSubtree(name)
if not name then return false end
for _, pattern in ipairs(EXCLUDED) do
if name:find(pattern) then return true end
end
return false
end
local function IsDropDown(frame, name)
return name ~= nil and _G[name.."Button"] ~= nil and _G[name.."Text"] ~= nil
end
-- The bottom bar's widgets aren't at a fixed depth (some sit directly on the
-- frame, some are nested one or two containers deep), and their names aren't
-- documented anywhere, so skinning is driven by walking the frame instead of
-- by a hardcoded name list. Depth is capped so a container we didn't expect
-- can't drag us down into the tree's pooled widgets.
local MAX_DEPTH = 5
local SkinChildren
function SkinChildren(frame, depth)
if depth > MAX_DEPTH then return end
for i = 1, frame:GetNumChildren() do
local child = select(i, frame:GetChildren())
local name = child.GetName and child:GetName()
if child.GetObjectType and not IsTreeSubtree(name) then
local objType = child:GetObjectType()
if objType == "EditBox" then
S:HandleEditBox(child)
elseif objType == "Button" then
-- Close buttons are handled explicitly -- the frame's in
-- SkinFrame, where it needs an anchor and a frame level the walk
-- can't supply, and each menu's in SkinBottomBar. Matched on
-- "Close" rather than "CloseButton": the menus name theirs just
-- "...MenuClose", and it was coming out of the walk as an
-- ordinary templated square with the X stripped off it.
if not (name and name:find("Close")) then
Skin:Button(child)
end
elseif objType == "Frame" then
if IsDropDown(child, name) then
S:HandleDropDownBox(child)
else
SkinChildren(child, depth + 1)
end
end
end
end
end
-- TreeView is a sibling drawn above the close button's own frame level, so
-- without the bump the ElvUI close texture ends up behind the tree panel and
-- the corner just looks empty. Anchored explicitly because the native corner
-- position was set relative to the NineSlice art we hide.
local function SkinCloseButton(frame)
local close = _G[FRAME_NAME.."CloseButton"]
if not close then return end
Skin:CloseButton(close)
-- Re-applied on every pass, not once at skin time. The X is drawn on the
-- close button's own frame, so it needs to outrank TreeView, and TreeView
-- rides along whenever the talent frame is raised while the close button,
-- being a sibling, keeps whatever absolute level it was given -- which is
-- how the X ends up buried behind the tree panel with the button still
-- clickable.
close:SetFrameStrata(frame:GetFrameStrata())
close:SetFrameLevel(frame:GetFrameLevel() + 20)
-- HandleCloseButton centres a 12px X inside the button's native 32px box,
-- so anchoring the box to the frame corner drops the X well below the
-- title. Centring the button on the title's own vertical midpoint puts the
-- X on the title line whatever height the bar turns out to be.
local title = _G[FRAME_NAME.."TitleText"]
local top = frame:GetTop()
local titleY = title and select(2, title:GetCenter())
close:ClearAllPoints()
if top and titleY then
close:Point("CENTER", frame, "TOPRIGHT", -16, titleY - top)
else
close:Point("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
end
end
local BOTTOM_BAR = FRAME_NAME.."TreeViewBottomBar"
local BOTTOM_BAR_DROPDOWNS = {"SpecDropDown", "BuildDropDown"}
-- The two menus don't name their list alike: the spec menu's inset and scroll
-- frame hang off "List", the build creator's off "BuildList". Assuming they
-- shared the layout is why nothing on the build creator's scrollbar was ever
-- being found.
local BOTTOM_BAR_MENUS = {
{menu = "SpecializationMenu", list = "List"},
{menu = "BuildCreatorMenu", list = "BuildList"}
}
-- Rows are pooled, so this walks by index until it runs out rather than
-- tracking how many the list currently holds.
local MENU_ROW_LIMIT = 50
-- Each row's icon sits in a rounded empty-slot ring, which reads as a raised
-- bevel against flat panels. The ring goes and the icon gets the usual ElvUI
-- treatment: cropped edges and a backdrop sized to it.
local function SkinMenuRowIcon(iconFrame)
if not iconFrame or iconFrame.CoASkinned then return end
iconFrame.CoASkinned = true
for i = 1, iconFrame:GetNumRegions() do
local region = select(i, iconFrame:GetRegions())
local texture = region.GetTexture and region:GetTexture()
if texture and texture:find("EmptySlot") then
region:SetTexture(nil)
end
end
local name = iconFrame:GetName()
local icon = name and _G[name..".Icon"]
if icon then
S:HandleIcon(icon, iconFrame)
end
end
local ROW_BORDER_EDGES = {"TOP", "BOTTOM", "LEFT", "RIGHT"}
-- The active row is edged rather than filled, and the edge is four textures on
-- the row for the same reason the fill is a texture: a backdrop frame is a
-- child, so it either covers the row's art or is covered by the neighbouring
-- rows, which overlap far enough that only its bottom line came through.
local function CreateRowBorder(row)
local border = {}
for _, edge in ipairs(ROW_BORDER_EDGES) do
local line = row:CreateTexture(nil, "OVERLAY")
line:Hide()
border[edge] = line
end
border.TOP:SetPoint("TOPLEFT", row, "TOPLEFT")
border.TOP:SetPoint("TOPRIGHT", row, "TOPRIGHT")
border.TOP:SetHeight(E.mult)
border.BOTTOM:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT")
border.BOTTOM:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT")
border.BOTTOM:SetHeight(E.mult)
border.LEFT:SetPoint("TOPLEFT", row, "TOPLEFT")
border.LEFT:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT")
border.LEFT:SetWidth(E.mult)
border.RIGHT:SetPoint("TOPRIGHT", row, "TOPRIGHT")
border.RIGHT:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT")
border.RIGHT:SetWidth(E.mult)
row.CoABorder = border
end
-- Selection is read off the native overlay rather than tracked here: the row's
-- own code decides which entry is active and there's no event for it, so the
-- overlay is left in place as the signal and only its art is cleared.
local function UpdateRowSelection(row)
local border = row.CoABorder
if not border then return end
local overlay = row.CoASelected
-- Alpha as well as visibility, because it isn't known which of the two the
-- row uses to turn the overlay on.
local selected = overlay ~= nil and overlay:IsShown() and overlay:GetAlpha() > 0
if selected == row.CoASelectionShown then return end
row.CoASelectionShown = selected
local r, g, b = unpack(E.media.rgbvaluecolor)
for _, edge in ipairs(ROW_BORDER_EDGES) do
local line = border[edge]
line:SetTexture(r, g, b, 1)
if selected then line:Show() else line:Hide() end
end
end
-- The row's frame is a rounded plate from the PvP queue art, with a matching
-- background behind it. Both come off in favour of a flat ElvUI panel, and the
-- hover and selection plates have to go with them -- they're cut to the same
-- rounded shape, so they read as bevels once the plate underneath is flat.
-- Hover becomes the flat white wash ElvUI puts on list rows; selection moves
-- onto the 1px edge from CreateRowBorder, which is how the active entry is
-- marked everywhere else in ElvUI.
--
-- Hidden as well as cleared, and re-run from both the row's show and its
-- update: a single pass didn't hold, the rows put their art back as they're
-- refilled from the pool.
local function StripRowArt(row)
local name = row:GetName()
local border = name and _G[name..".Border"]
if border then
border:SetTexture(nil)
border:Hide()
end
-- ".H" is the row's highlight texture, so retexturing it is enough to
-- replace the hover state; there's no separate object to chase.
local hover = name and _G[name..".H"]
if hover and hover.SetTexture then
hover:SetTexture(1, 1, 1, 0.15)
hover:ClearAllPoints()
hover:SetInside(row)
row.CoAHover = hover
end
local selected = name and _G[name..".Selected"]
if selected and selected.SetTexture then
selected:SetTexture(nil)
row.CoASelected = selected
end
for i = 1, row:GetNumRegions() do
local region = select(i, row:GetRegions())
local texture = region.GetTexture and region:GetTexture()
if texture and texture:find("GuildFrame") then
region:SetTexture(nil)
region:Hide()
end
end
end
-- Scrolling refills the pooled rows and puts their native hover and selection
-- art back, and a pooled row doesn't fire OnShow when it's refilled, so there's
-- no event to strip on -- which is why the glow only ever appeared after a
-- scroll. The art is checked from the row's update instead. A colour-set
-- texture reads back as "SolidTexture", so the check stays a string compare
-- rather than a blind re-skin every frame.
local function UpdateRowArt(row)
local hover, selected = row.CoAHover, row.CoASelected
if (hover and hover:GetTexture() ~= "SolidTexture")
or (selected and selected:GetTexture() ~= nil) then
StripRowArt(row)
end
end
local function UpdateRow(row)
UpdateRowArt(row)
UpdateRowSelection(row)
end
local SkinMenuRow
function SkinMenuRow(row)
if not row then return end
if not row.CoASkinned then
row.CoASkinned = true
-- The fill is painted on the row itself rather than through
-- CreateBackdrop. The backdrop is a child frame, and here it covered
-- the row's name, icon and status text at every frame level tried,
-- including one below the row's own. A texture on the row's BACKGROUND
-- layer is ordered against those regions inside a single frame instead,
-- so it can't outrank them. Opaque rather than the transparent template,
-- since the menu panel behind is already see-through and a second
-- see-through layer left the rows reading as holes onto the talent scene.
local background = row:CreateTexture(nil, "BACKGROUND")
background:SetInside(row)
background:SetTexture(unpack(E.media.backdropcolor))
CreateRowBorder(row)
-- Nooped so a refill can't swap the highlight for a fresh texture
-- object, which would leave the retextured one orphaned and the check in
-- UpdateRowArt reading a state nothing draws from any more.
row.SetHighlightTexture = E.noop
row:HookScript("OnShow", SkinMenuRow)
-- No event fires when the active spec changes or when a scroll refills
-- the row, so both are resynced from the row's update. The comparisons
-- in UpdateRowArt and UpdateRowSelection make every tick that changes
-- nothing a no-op.
row:HookScript("OnUpdate", UpdateRow)
end
StripRowArt(row)
UpdateRowSelection(row)
end
-- The Activate button inside an expanded specialization row draws its pill from
-- a single atlas across three BACKGROUND regions plus a HIGHLIGHT one, and
-- switches between the green and the grey variant by tex coord. There are no
-- Normal/Pushed/Disabled textures for HandleButton to clear (probed: both come
-- back nil, and every region's vertex colour is white in either state), so the
-- art is cleared by file the way the dropdown pills are (Skin:StripArtByFile,
-- which noops SetTexture per region as well -- whatever swaps the tex coord on
-- a state change is free to re-art the region), and the enabled/disabled
-- distinction the atlas was carrying has to be re-created on the label.
-- GetFontString covers the templated case; the scan is for a label that was
-- added as a plain region rather than set as the button's own font string.
local function ActivateLabel(button)
if button.CoALabel then return button.CoALabel end
local text = button.GetFontString and button:GetFontString()
if not text then
for i = 1, button:GetNumRegions() do
local region = select(i, button:GetRegions())
if region:GetObjectType() == "FontString" then
text = region
break
end
end
end
button.CoALabel = text
return text
end
-- Yellow live, grey dead: the same pair ElvUI's own templated buttons use, so
-- these read as buttons rather than as labels on a panel.
local ACTIVATE_COLOR = {1, 0.82, 0}
local ACTIVATE_DISABLED_COLOR = {0.55, 0.55, 0.55}
-- The active spec's button is disabled, and with the pill gone nothing else
-- says so -- the ElvUI panel is drawn the same either way. The label is greyed
-- instead, which is how ElvUI marks a dead button everywhere else.
--
-- The enabled state is compared against the cached one rather than written
-- blind because this runs from the button's update: there's no event for a spec
-- becoming active. Skin:IsEnabled does the 0/1-to-boolean normalisation this
-- client needs (see Skinning.lua).
local function UpdateActivateState(button)
local enabled = Skin:IsEnabled(button)
if enabled == button.CoAActivateEnabled then return end
button.CoAActivateEnabled = enabled
-- Activating a spec disables this button under the very cursor that just
-- clicked it, so its hover border would stay lit until the pointer moved
-- off. This update already watches the state, so it carries the border too.
Skin:RefreshButtonBorder(button)
local text = ActivateLabel(button)
if not text then return end
text:SetTextColor(unpack(enabled and ACTIVATE_COLOR or ACTIVATE_DISABLED_COLOR))
end
-- Stripped before templating, as with the dropdown pills: HandleButton adds its
-- backdrop as regions of this same button, so a strip afterwards takes the
-- backdrop with the pill.
local function SkinActivateButton(button)
if not button then return end
if not button.CoASkinned then
button.CoASkinned = true
Skin:StripArtByFile(button, Skin.RedButtonArt)
Skin:Button(button)
button:HookScript("OnUpdate", UpdateActivateState)
end
UpdateActivateState(button)
end
-- Unlike the row's icon these are named without a literal dot
-- ("Button1ExpandedContent", "...ExpandedContentActivateButton"), read off the
-- frame stack.
--
-- The expanded content exists from the moment the menu is built rather than
-- being created when its row is expanded (probed: present, hidden, before any
-- row had been opened), so it's picked up with the rest of the row. The show
-- hook is there for a row whose content is filled in later than this first pass.
local function SkinRowExpansion(rowName)
local content = _G[rowName.."ExpandedContent"]
if not content then return end
local buttonName = rowName.."ExpandedContentActivateButton"
SkinActivateButton(_G[buttonName])
if content.CoASkinned then return end
content.CoASkinned = true
content:HookScript("OnShow", function()
SkinActivateButton(_G[buttonName])
end)
end
-- These children are named with a literal dot ("Button1.SpecIcon"), so they
-- only come out of _G by string key, never as plain identifiers.
local function SkinMenuRows(listName)
for i = 1, MENU_ROW_LIMIT do
local rowName = listName.."ScrollFrameButton"..i
local row = _G[rowName]
if not row then break end
SkinMenuRow(row)
SkinMenuRowIcon(_G[rowName..".SpecIcon"])
SkinRowExpansion(rowName)
end
end
-- S:HandleScrollBar can't be used on these: it assumes the thumb is a texture
-- and calls SetTexture on it, but this thumb is a button, so the call errors
-- and takes the rest of the OnShow skinning down with it.
--
-- It's a proportional scrollbar: the thumb is drawn as three slices that the
-- widget re-applies whenever it resizes, so stripping them doesn't hold. The
-- slices are made transparent instead and a value-coloured panel is stretched
-- from the first to the last, which is how ElvUI handles the same widget in
-- HandleProportionalScroll. Slices are looked up as fields and as globals,
-- since only the global names are confirmed here.
local function SkinScrollThumb(thumb, thumbName)
if not thumb or thumb.backdrop then return end
local first = thumb.Begin or _G[thumbName.."Begin"]
local last = thumb.End or _G[thumbName.."End"]
local middle = thumb.Middle or _G[thumbName.."Middle"]
if first then first:SetAlpha(0) end
if last then last:SetAlpha(0) end
if middle then middle:SetAlpha(0) end
local r, g, b = unpack(E.media.rgbvaluecolor)
thumb:CreateBackdrop("Transparent")
thumb.backdrop:SetFrameLevel(thumb:GetFrameLevel() + 1)
thumb.backdrop:SetBackdropColor(r, g, b, 0.25)
if first and last then
thumb.backdrop:Point("TOPLEFT", first)
thumb.backdrop:Point("BOTTOMRIGHT", last)
end
thumb:HookScript("OnEnter", function(self)
if self.backdrop then self.backdrop:SetBackdropColor(r, g, b, 0.75) end
end)
thumb:HookScript("OnLeave", function(self)
if self.backdrop then self.backdrop:SetBackdropColor(r, g, b, 0.25) end
end)
end
-- The list inside each dropdown popup: a scroll frame with the framed inset
-- and overlay art around it. The arrows are named off the scroll frame rather
-- than off the scrollbar, so they're looked up here instead.
local function SkinMenuScroll(listName)
local scrollBar = _G[listName.."ScrollFrameScrollBar"]
if not scrollBar then return end
local inset = _G[listName.."Inset"]
if inset then inset:StripTextures() end
local overlay = _G[listName.."ScrollFrameArtOverlay"]
if overlay then overlay:StripTextures() end
if scrollBar.backdrop then return end
local frameLevel = scrollBar:GetFrameLevel()
scrollBar:Width(18)
scrollBar:StripTextures()
scrollBar:CreateBackdrop()
scrollBar.backdrop:SetAllPoints()
scrollBar.backdrop:SetFrameLevel(frameLevel)
local up = _G[listName.."ScrollFrameScrollUpButton"]
if up then
up:Point("BOTTOM", scrollBar, "TOP", 0, 1)
Skin:ScrollArrow(up, "up")
end
local down = _G[listName.."ScrollFrameScrollDownButton"]
if down then
down:Point("TOP", scrollBar, "BOTTOM", 0, -1)
Skin:ScrollArrow(down, "down")
end
local thumbName = listName.."ScrollFrameScrollBarThumb"
local thumb = (scrollBar.GetThumbTexture and scrollBar:GetThumbTexture()) or _G[thumbName]
SkinScrollThumb(thumb, thumbName)
end
-- The bar's plate art (BottomBarTexture) is the dark band under the buttons,
-- and it's the bar's only region, so a plain StripTextures clears it without
-- touching the scene art, which lives on TreeView one level up. The buttons,
-- dropdowns and search box sitting on the bar are picked up by the walk.
local function SkinBottomBar()
local bar = _G[BOTTOM_BAR]
if not bar then return end
bar:StripTextures()
-- Each dropdown's caret is a child button carrying the arrow as its own
-- art, so the walk was treating it as an ordinary button and giving it a
-- templated square. Skinned here, ahead of the walk, so it becomes the
-- ElvUI chevron; HandleNextPrevButton's isSkinned flag then makes the walk
-- leave it alone. noBackdrop keeps it a bare white arrow that takes the
-- value colour on hover, and "up" matches the way these menus open.
for _, suffix in ipairs(BOTTOM_BAR_DROPDOWNS) do
local dropdown = _G[BOTTOM_BAR..suffix]
local arrow = _G[BOTTOM_BAR..suffix.."Button"]
if arrow then
S:HandleNextPrevButton(arrow, "up", nil, true)
arrow:Size(20, 20)
end
-- These buttons are wider than they look: the label art only ever
-- covered the part up to the arrow, so templating the whole frame drew
-- a panel running under the icon buttons to their right. Skinned here
-- rather than in the walk so the backdrop can be built separately and
-- stopped at the arrow, and the dead space to its right is taken out
-- of the hit rect so it can't swallow clicks meant for those icons.
if dropdown and arrow then
Skin:Button(dropdown, nil, nil, true)
if dropdown.backdrop then
dropdown.backdrop:ClearAllPoints()
dropdown.backdrop:Point("TOPLEFT", dropdown, "TOPLEFT", -1, 1)
dropdown.backdrop:Point("BOTTOMRIGHT", arrow, "BOTTOMRIGHT", 3, -1)
end
local dropdownRight, arrowRight = dropdown:GetRight(), arrow:GetRight()
if dropdownRight and arrowRight then
dropdown:SetHitRectInsets(0, math.max(0, dropdownRight - arrowRight - 3), 0, 0)
end
end
end
-- The two dropdown popups are plain frames, which the walk descends into
-- for their buttons but never templates, so they get their panel here.
for _, entry in ipairs(BOTTOM_BAR_MENUS) do
local menuName = BOTTOM_BAR..entry.menu
local listName = menuName..entry.list
local menu = _G[menuName]
if menu and not menu.CoASkinned then
menu.CoASkinned = true
Skin:Panel(menu)
Skin:CloseButton(_G[menuName.."Close"])
-- Rows don't exist until the menu is first opened, which happens
-- long after the talent frame's own OnShow, so they're picked up
-- on the menu's.
menu:HookScript("OnShow", function()
SkinMenuRows(listName)
end)
end
SkinMenuScroll(listName)
SkinMenuRows(listName)
end
end
-- The scene art belongs to TreeView, which runs on behind the bottom bar, so
-- clearing the bar's own plate left the art showing through under the
-- buttons. The pieces are stretched over the whole of TreeView and show their
-- slice of the file through tex coords, which occupy only part of it -- the
-- rest of the file holds other art. So the coords have to be trimmed in
-- proportion to their existing values rather than replaced outright, or
-- unrelated regions of the file scroll into view.
--
-- Geometry comes from TreeView and the bar rather than from the texture's own
-- rect, so that a re-run reads the same numbers instead of measuring a rect
-- it already cropped. Coords are kept from the first pass for the same reason.
local function CropTexture(tex, bar, keep)
if not tex.CoAOriginalCoords then
tex.CoAOriginalCoords = {tex:GetTexCoord()}
end
-- GetTexCoord yields the four corners: UL, LL, UR, LR. These are all
-- axis-aligned, so the edges come off the corners that define them.
local coords = tex.CoAOriginalCoords
local left, top, bottom, right = coords[1], coords[2], coords[4], coords[5]
tex:SetTexCoord(left, right, top, top + (bottom - top) * keep)
tex:Point("BOTTOMRIGHT", bar, "TOPRIGHT", 0, 0)
end
local function CropBackground()
local treeView = _G[FRAME_NAME.."TreeView"]
local bar = _G[BOTTOM_BAR]
if not (treeView and bar) then return end
local treeTop, treeBottom, barTop = treeView:GetTop(), treeView:GetBottom(), bar:GetTop()
if not (treeTop and treeBottom and barTop) then return end
local height = treeTop - treeBottom
if height <= 0 then return end
local keep = (treeTop - barTop) / height
if keep <= 0 or keep >= 1 then return end
-- Scoped to TreeView's own regions: earlier this looked the names up as
-- globals over a fixed range, which pulled in same-named textures owned by
-- other frames and re-anchored those too.
for i = 1, treeView:GetNumRegions() do
local region = select(i, treeView:GetRegions())
local name = region.GetName and region:GetName()
if name and name:find("Background") and region:GetObjectType() == "Texture" then
CropTexture(region, bar, keep)
end
end
end
-- The tab row is shared with the vanity and wardrobe windows and is handled in
-- Skinning.lua. The talent frame is passed as its owner while this window is
-- the open one: the tabs aren't its children, so they don't draw above its
-- panel on their own.
local function SkinTabs()
Skin:CollectionTabs(_G[FRAME_NAME])
end
local SPEC_CHOICE = FRAME_NAME.."SpecViewPoolFrameCoASpecChoiceTemplate%d"
local MAX_SPEC_CHOICES = 10
-- Only each card's action button. The cards themselves keep their art: the
-- portraits and the gold wash on the active spec are the whole point of the
-- view, and there's nothing flat that would say the same thing.
--
-- Skinned by name because the walk can't get here -- the cards hang off a
-- PoolFrame, which EXCLUDED skips so the tree's pooled nodes stay untouched.
-- Stripped as well as templated: the red plate isn't one of the Left/Middle/
-- Right pieces HandleButton clears on its own.
local function SkinSpecChoices()
for i = 1, MAX_SPEC_CHOICES do
local cardName = SPEC_CHOICE:format(i)
if not _G[cardName] then break end
Skin:Button(_G[cardName.."SelectButton"], true)
end
end
local function SkinFrame(frame)
if not frame.CoASkinned then
frame.CoASkinned = true
Skin:HideArt(_G[FRAME_NAME.."NineSlice"])
-- The round portrait medallion overhangs the top-left corner and has
-- no flat equivalent, so it goes rather than getting reskinned.
Skin:HideArt(_G[FRAME_NAME.."PortraitFrame"])
Skin:Panel(frame)
-- The choice cards are pulled from the pool as the view opens, so the
-- talent frame's own show is too early to catch them. Run on the view's
-- show, and again a tick later in case the pool is filled after it.
local specView = _G[FRAME_NAME.."SpecView"]
if specView then
specView:HookScript("OnShow", function()
SkinSpecChoices()
E:Delay(0.1, SkinSpecChoices)
end)
end
-- Children are created lazily as tabs are visited, so the walk has to
-- run again on every show rather than once at hook time. The isSkinned
-- / backdrop guards inside ElvUI's handlers make re-runs cheap.
frame:HookScript("OnShow", function(self)
Skin:ApplyWindowScale("advancementScale")
Skin:Title(_G[FRAME_NAME.."TitleText"])
SkinCloseButton(self)
SkinBottomBar()
CropBackground()
SkinChildren(self, 1)
SkinTabs()
SkinSpecChoices()
end)
end
Skin:ApplyWindowScale("advancementScale")
Skin:Title(_G[FRAME_NAME.."TitleText"])
SkinCloseButton(frame)
SkinBottomBar()
-- 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()
end
local function TryHook()
local frame = _G[FRAME_NAME]
if not frame then return false end
SkinFrame(frame)
return true
end
function CoA:InitializeTalentFrame()
if not E.private.skins.blizzard.enable then return end
Skin:OnFrameAvailable(TryHook)
end