Files
ElvUI_CoA/Modules/WardrobeFrame.lua
T
Narcasung 13f36e7817 Skin the Clear invalid slots button and grey the dead ones
A third button sits on the wardrobe model alongside Apply and Cancel and
appears under the same condition: Clear invalid slots, shown with the warning
that an appearance has not been unlocked yet. It was simply not in the list,
so it kept its native green pill against the two now-flat buttons beside it.

It is drawn from the same atlas as Apply -- the green variant of the gold file
-- so Skin.RedButtonArt already matches it and the existing path covers it
whole; probing its regions after the pass comes back nil for all of them. With
three names the list is read in a loop rather than repeated call by call.

Apply is disabled for as long as an invalid slot is pending, and with the pill
gone nothing said so any more: the ElvUI panel is drawn the same either way,
and the atlas was the only thing carrying the distinction. The label is greyed
instead, the same yellow/grey pair the specialization menu's Activate button
uses. There is no event for the state change -- clearing the invalid slots
enables Apply on the spot -- so it is driven from the button's update and
compared against the cached state, again as Activate does it, and the border
goes with it: Apply comes alive under the very cursor that just clicked Clear
invalid slots, and OnEnter has been and gone by then.
2026-08-18 16:44:09 +02:00

236 lines
9.3 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 = "AppearanceWardrobeFrame"
-- Standard UIPanelButtonTemplate art, same as Vanity's action buttons --
-- S:HandleButton's own texture clearing handles these directly.
local function SkinActionButtons()
Skin:Button(_G[FRAME_NAME.."PlayerModelSaveOutfitButton"])
Skin:Button(_G[FRAME_NAME.."DisableTransmogButton"])
Skin:Button(_G[FRAME_NAME.."DisableSpellVisualsButton"])
end
-- Apply, Cancel and Clear invalid slots only exist while a transmog change is
-- pending, which is why they were missed for so long -- but they exist from the
-- start rather than being created on the first change (probed: they are
-- Buttons, hidden, with nothing pending), so the normal skin pass reaches them.
--
-- Same widget as the talent frame's Activate button: the pill is drawn across
-- plain regions, with no Normal/Pushed texture for HandleButton to clear
-- (probed: they come back nil). Apply and Clear invalid slots take the green
-- variant of the gold atlas, Cancel is drawn from the red file instead --
-- Skin.RedButtonArt matches both files, and Cancel's HIGHLIGHT region goes with
-- the rest of its art.
--
-- The labels don't carry the atlas's green/red split over: the buttons sit in a
-- row and read apart by their text, and they take the same yellow every other
-- templated button on these frames uses -- the talent frame's live Activate
-- label included -- so a pending change doesn't get its own colour scheme. The
-- grey is the disabled half of that same pair.
local LABEL_COLOR = {1, 0.82, 0}
local DISABLED_LABEL_COLOR = {0.55, 0.55, 0.55}
-- Apply is disabled for as long as an invalid slot is pending, and with the
-- pill gone nothing else says so -- the ElvUI panel is drawn the same either
-- way. The label is greyed instead, exactly as the talent frame does it for the
-- active spec's Activate button.
--
-- Driven from the button's update because there's no event for the state
-- change: clearing the invalid slots enables Apply on the spot. The cached
-- state is what keeps that cheap -- the colours are only written when the state
-- actually flips.
local function UpdatePendingButtonState(button)
local enabled = Skin:IsEnabled(button)
if enabled == button.CoAPendingEnabled then return end
button.CoAPendingEnabled = enabled
-- Clearing the invalid slots enables Apply under the very cursor that just
-- clicked Clear invalid slots, so its hover border would stay dark until
-- the pointer moved off. This update already watches the state, so it
-- carries the border too.
Skin:RefreshButtonBorder(button)
local text = button.GetFontString and button:GetFontString()
if text then text:SetTextColor(unpack(enabled and LABEL_COLOR or DISABLED_LABEL_COLOR)) end
end
-- The strip runs on every pass rather than once behind the skinned guard: the
-- art is only there to be cleared once a change is pending, and nothing fires
-- the frame's OnShow at that point -- the window is already open. The buttons'
-- own OnShow is what catches it, since that is exactly when they appear.
local function SkinPendingButton(name)
local button = _G[name]
if not button then return end
Skin:StripArtByFile(button, Skin.RedButtonArt)
if not button.CoASkinned then
button.CoASkinned = true
-- Stripped before templating: HandleButton adds its backdrop as regions
-- of this same button, so a strip afterwards takes the backdrop with the
-- pill.
Skin:Button(button)
button:HookScript("OnShow", function(self)
Skin:StripArtByFile(self, Skin.RedButtonArt)
end)
button:HookScript("OnUpdate", UpdatePendingButtonState)
end
UpdatePendingButtonState(button)
end
local PENDING_BUTTONS = {
"PlayerModelApplyButton",
"PlayerModelCancelButton",
"PlayerModelClearInvalidButton",
}
local function SkinPendingButtons()
for _, suffix in ipairs(PENDING_BUTTONS) do
SkinPendingButton(FRAME_NAME..suffix)
end
end
local function SkinSearchBox()
S:HandleEditBox(_G[FRAME_NAME.."CollectionSearchBox"])
end
-- suffix is "Filter" or "Sorting" -- both are CollectionX buttons with a
-- matching CollectionXMenu popout (confirmed by probe), and both are the same
-- widget as the vanity frame's single dropdown, so both go through the shared
-- handler.
local function SkinCollectionDropdown(suffix)
Skin:Dropdown(_G[FRAME_NAME.."Collection"..suffix], _G[FRAME_NAME.."Collection"..suffix.."Menu"])
end
local function SkinPagerArrows()
local prevButton = _G[FRAME_NAME.."CollectionPageLeftButton"]
local nextButton = _G[FRAME_NAME.."CollectionPageRightButton"]
if prevButton then S:HandleNextPrevButton(prevButton, "left") end
if nextButton then S:HandleNextPrevButton(nextButton, "right") end
end
-- Same art naming as the talent frame's tabs (confirmed by probe), so they go
-- through the same shared handler. No level parent is passed: these are proper
-- descendants of Collection rather than separately-placed siblings, so normal
-- parent/child z-order already puts them above the panel and the frame-level
-- bump the talent tabs need doesn't apply.
--
-- Named PoolFrameAppearanceTypeTabTemplate1 through 8 (confirmed by probe),
-- not pooled/created dynamically like the talent frame's spec choices, so a
-- plain indexed loop is enough -- no OnShow hook needed to catch late pool
-- fills.
local TAB_COUNT = 8
local function SkinCategoryTabs()
for i = 1, TAB_COUNT do
Skin:Tab(_G[FRAME_NAME.."CollectionPoolFrameAppearanceTypeTabTemplate"..i])
end
end
local function SkinContents()
-- Scale is set on the shared container while this window is the open one,
-- which carries the tab row along with it (see Skinning.lua).
Skin:ApplyWindowScale("wardrobeScale")
Skin:CollectionTabs(_G[FRAME_NAME])
Skin:Title(_G[FRAME_NAME.."TitleText"])
Skin:CloseButton(_G[FRAME_NAME.."CloseButton"])
SkinActionButtons()
SkinPendingButtons()
SkinSearchBox()
SkinCollectionDropdown("Filter")
SkinCollectionDropdown("Sorting")
SkinPagerArrows()
SkinCategoryTabs()
end
local function SkinFrame(frame)
if not frame.CoASkinned then
frame.CoASkinned = true
-- The native NineSlice panel draws its own ornate border/background on
-- top of the Transparent template's border, doubling up. Same fix as
-- the talent frame: strip and hide it.
Skin:HideArt(_G[FRAME_NAME.."NineSlice"])
-- The round medallion overhangs the top-left corner (confirmed by
-- probe: 61x61, TOPLEFT -6,8 -- matches the frame's own corner, not a
-- counter badge elsewhere, unlike Vanity's same-named "Portrait2"
-- texture). No flat equivalent, so it goes rather than getting
-- reskinned, same treatment as the talent frame's portrait.
Skin:HideArt(_G[FRAME_NAME.."PortraitFrame"])
-- The frame's own regions (confirmed by probe: 4 total, all BACKGROUND/
-- BORDER/OVERLAY textures, none of them functional -- unlike Vanity's
-- frame, nothing here doubles as a counter badge) are the wood-panel
-- background art, so this one is safe to strip before templating.
Skin:Panel(frame)
-- InsetOverlay's NineSlice and the ShadowOverlay are separate decorative
-- art layered over the item grid area (8-piece atlas borders/shadow
-- edges, confirmed via probe), stripped outright -- no functional
-- content lives on either.
Skin:HideArt(_G[FRAME_NAME.."CollectionInsetOverlayNineSlice"])
-- The actual panel behind the grid: Collection itself (confirmed via
-- /fstack -- InsetOverlay was the wrong target, its own rect doesn't
-- match the visible panel). Collection is the grid's real parent, so
-- no frame-level/strata juggling needed -- children always draw above
-- their own parent.
--
-- 16 of its own regions turned out to hold real art -- a background
-- tile plus ~14 atlas border/corner pieces (the ornate corners
-- /fstack couldn't ever pick out, since loose regions aren't frames
-- and don't show up there) -- none of it functional (the "Collected
-- 50/2744" counter is a separate FontString region, untouched by
-- StripTextures). Painting an opaque/red template over it earlier
-- just masked it; it was still there underneath, which is why
-- Transparent let it bleed back through. Strip first, then template,
-- same order as the outer frame -- SetTemplate's own WHITE8X8 backdrop
-- pieces land as regions on this same frame too, so stripping after
-- would wipe them right back off.
Skin:Panel(_G[FRAME_NAME.."Collection"])
-- The 3D model preview's vanilla border is an anonymous child (first
-- of PlayerModel's own, confirmed by probe: 8-piece "UIFrame" atlas
-- border, sized to match the model panel). No name to key off of, same
-- as Vanity's pager arrows -- picked out positionally instead. The
-- race-specific scenic backdrop texture is PlayerModel's own region,
-- not this child's, so it's untouched.
local playerModel = _G[FRAME_NAME.."PlayerModel"]
local modelBorder = playerModel and select(1, playerModel:GetChildren())
if modelBorder and modelBorder:GetObjectType() == "Frame" then
Skin:HideArt(modelBorder)
end
Skin:HideArt(_G[FRAME_NAME.."CollectionShadowOverlay"])
frame:HookScript("OnShow", SkinContents)
end
SkinContents()
end
local function TryHook()
local frame = _G[FRAME_NAME]
if not frame then return false end
SkinFrame(frame)
return true
end
function CoA:InitializeWardrobeFrame()
if not E.private.skins.blizzard.enable then return end
Skin:OnFrameAvailable(TryHook)
end