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