10 Commits

Author SHA1 Message Date
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
Narcasung d2e55eb3cb Stop disabled buttons taking the hover border colour
S:HandleButton ends with an unconditional OnEnter/OnLeave pair that swaps
the border to the value colour and back, and neither handler looks at
IsEnabled. A disabled button still fires both scripts on this client, so
the greyed Activate button, Save changes with nothing pending and Purchase
with nothing selected all lit up under the cursor and read as clickable.

HookScript can't be undone, but handlers fire in registration order, so a
hook registered after HandleButton runs last and has the final say on the
colour. Skin:Button wraps HandleButton with that hook, and every call site
in the plugin now goes through it, so the correction is inherited rather
than repeated per module.

The Activate button carries the border from its own update as well:
activating a spec disables it under the very cursor that just clicked it,
and OnEnter has been and gone by then, so nothing else would put the border
back until the pointer moved off.

Skin:IsEnabled hoists the 0/1-to-boolean normalisation this client needs
out of UpdateActivateState, which held the only copy of it.
2026-08-18 16:30:27 +02:00
Narcasung cddfc7e7af Skin the Apply and Cancel buttons on the wardrobe model
Both only exist while a transmog change is pending, which is why every
earlier pass over the frame missed them -- it was always skinned in its idle
state -- so they still wore their native green and red pills against an
otherwise flat window.

They are the same widget as the specialization menu's Activate button: no
Normal/Pushed texture for HandleButton to clear, the pill drawn across plain
regions from an atlas. Two files, not one -- Apply takes the green variant of
128GoldRedButton, Cancel is drawn from 128RedButton, HIGHLIGHT region and all,
which is what left a red glow under the cursor once only the gold file was
being cleared. The shared "RedButton" suffix matches both. Their labels take
ElvUI's yellow rather than carrying the atlas's green/red split over: the two
sit side by side and read apart by their text.

The buttons exist from the start rather than being created on the first
pending change, so the normal skin pass reaches them, but the art is only
there to be cleared once a change is pending -- and nothing fires the frame's
OnShow at that point, since the window is already open. The buttons' own
OnShow catches it instead.

The clear-by-file loop the Activate button and the dropdown pills had a copy
of each now lives in Skinning.lua as Skin:StripArtByFile, with the atlas name
alongside it.
2026-08-18 16:29:12 +02:00
Narcasung 58fae4418a Skin the Activate button in the specialization menu
The one button in the Character Advancement frame the skin never reached:
the Activate button inside an expanded specialization row still wore its
native pill against otherwise flat rows.

It has no Normal/Pushed/Disabled textures for HandleButton to clear -- the
pill is three BACKGROUND regions plus a HIGHLIGHT one, all drawn from the
128GoldRedButton atlas, switching between the green and grey variant by tex
coord with the vertex colour left white throughout. So the art is cleared by
file and the regions' SetTexture noop'd, as the dropdown pills already are,
and the enabled/disabled distinction the atlas was carrying is re-created on
the label: ElvUI's yellow when live, grey when dead.

The expanded content exists from the moment the menu is built rather than
being created when its row is expanded, so it is picked up with the rest of
the row.
2026-08-18 16:15:05 +02:00
Narcasung 01f1f5c7c3 Centre wardrobe tab labels and name the skin for what it covers
The label nudge in the shared tab handler exists to clear the icon on the
talent window's tabs, where the native anchor sits tight against it and
grown text would collide. The wardrobe's category tabs carry no icon and
centre their label, so the same nudge pushed every one of them off to the
right. It's now applied only to tabs that actually have a visible icon.

The settings entry covers three windows, so it's named for all three:
Advancement/Vanity/Wardrobe. The keys behind it move with it -- the db
table is skins.collections, after Collections, the container the three
windows and the tab row all hang off, and talentScale becomes
advancementScale. Nothing here has been released, so there's no stored
setting to migrate.
2026-08-17 22:41:25 +02:00
Narcasung 964f88559d Add a Talents skin entry with an enable switch and per-window scale
The talent window, the Vanity and Wardrobe windows it switches between and
the tab row along its bottom are one feature to the player, so they get one
entry in the Skins tree and one master switch gating all three
Initialize calls together. Skinning one of them without the others reads as
a bug.

Scale is a slider per window, 0.5 to 1.5, applied as a multiplier on the
existing scale rather than an absolute -- the three don't ship at the same
size (the vanity store's frame runs about 9% larger than the others), and
that is the server's choice to keep. A setting of 1.0 leaves everything
exactly as it was.

The scale is set on Collections, not on the window itself. All three
windows and the tab row are its children, and it is the only one of them
with mouse enabled -- it is what the player drags. Scaling a window
directly left the drag target at full size, so a shrunk window had to be
grabbed by clicking outside itself, and the tab row, not being a child of
any window, kept whichever size the first-opened one gave it. Scaling the
container moves its hit area with it and the tabs follow as children.
2026-08-17 22:37:53 +02:00
Narcasung 766e561839 Share one skinning path across the three CoA frames
The talent, vanity and wardrobe frames are built from the same handful of
widget templates, but each module carried its own copy of the handling:
close buttons, panels, dropdown pills, tabs and the ADDON_LOADED loader
were all written out three times. The copies had drifted -- tab backdrop
insets of 3 against 2, tabs grown in one frame and not the other, the
wardrobe clearing only three of the six tab textures -- and that drift is
what reads in-game as the same control looking different depending on which
tab you're on.

Modules/Skinning.lua now owns each of those behaviours once, and the three
modules call into it. Where the copies disagreed the values are unified;
tab growth is gated on the tab actually having a label, since growing an
icon-only tab just pushes its icon off the layout, and the SetChecked hook
is guarded because hooksecurefunc errors outright on a missing method.

Titles are pinned to Arial Narrow at 13: the vanity store titled in Friz
Quadrata while the other two used Arial Narrow. Frame scale is left as the
server set it, so the vanity title still renders slightly larger than the
others.

Two things deliberately not shared: the vanity frame still skips
StripTextures, since its currency counters are regions of the frame itself,
and the talent frame's bottom-bar dropdowns stay on their own path -- they
are a different widget, a native dropdown plus a separate caret button.
2026-08-17 22:19:50 +02:00
Narcasung 7862871e3f 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.
2026-08-17 21:24:42 +02:00
Narcasung 62a6a2cc57 Restyle CoA talent frame tabs to match ElvUI's flat tab rows
Bigger font and plate, tighter gap between tabs, taller tabs for real
vertical padding around the label. Selection now relies on Blizzard's
own white-text-on-checked behaviour instead of an ElvUI border/fill
swap, matching the Friends/Character tab rows elsewhere in ElvUI.

Growth and frame-level bump are self-healing from the tabs' OnUpdate
hook rather than one-shot: the tab's native height isn't settled yet
when it's first skinned, and something outside our control resets the
frame level on a plain tab switch, so both need to keep correcting
themselves rather than being set once behind a guard.
2026-08-17 20:59:11 +02:00
Narcasung 0b9b0c5afa Skin the CoA talent frame's Character Advancement tab
Flattens the custom talent window into the ElvUI look: frame chrome, close
buttons, tabs, bottom bar, dropdowns and their popups, the list scrollbars,
and the spec choice cards' action buttons. The talent tree itself is left
alone -- its node borders encode rank and availability.

Most of the work is in keeping the skin applied. The frames restore their
native art on refresh in three different ways, and none of them fire an
event to hook:

- The scroll arrows re-show a region StripTextures had hidden, so the arrow
  buttons re-hide it from their own update, matching by file so ElvUI's
  panel pieces (which are regions of the same button on this client) survive.
- The pooled menu rows put their hover and selection art back when a scroll
  refills them, without firing OnShow, so the strip runs from the row update
  behind a cheap texture compare.
- The talent frame's close button keeps its absolute frame level while
  TreeView rides along when the frame is raised, so the level bump re-applies
  on every show.

Row fills and the selection edge are textures on the row rather than an ElvUI
backdrop: the backdrop is a child frame and covered the row's icon and text at
every frame level available to it.
2026-08-17 17:15:29 +02:00