fix(ui): retail scrollbar parity — button seating, full-track thumb, hover/pressed states
All checks were successful
CI / linux-portable (push) Successful in 3m32s
CI / windows-gate (push) Successful in 6m15s
CI / release (push) Successful in 2m16s

Owner report (2026-08-24): our scrollbar arrows pointed the wrong way,
the thumb vanished when there was nothing to scroll, and neither the
thumb nor the arrow buttons reacted to hover/press.

All three are one retail mechanism we had not ported:

1. Seating: UIElement_Scrollbar::UpdateScrollingArea @0x00470AA0 moves
   the INCREMENT designee (attribute 0x77) to the top/left corner and
   the DECREMENT designee (0x78) to the bottom/right, ignoring authored
   positions. The vertical base skin (0x10000455 in layout 0x2100003E)
   authors the DOWN-arrow decrement at Y=0 and the UP-arrow increment
   at Y=32 (live-DAT probed; sprite art visually verified from decoded
   PNGs), so our authored-Y ordering drew both arrows upside down.
   DatWidgetFactory now seats by designation; the hand-wired sites
   (CharacterStatController, ExternalContainerController, the
   Config/Vendor menu chrome) share the new RetailScrollbarChrome
   catalog instead of local constants.

2. Full-track thumb: UpdateLayout @0x004710d0 sizes the thumb from
   proportion attribute 0x88, which DEFAULTS to 1.0 — a content-fits
   bar shows a thumb filling the whole track; disabled only removes
   input and the page regions. Our draw skipped the thumb entirely on
   !HasOverflow.

3. States: every arrow button and thumb slice authors Normal (red gem /
   dark navy), Normal_rollover (amber gem / bright blue) and
   Normal_pressed (gold highlight / dark) media. The widget now tracks
   thumb hover and selects rollover media on hover and pressed media
   while dragging; the factory extracts the thumb-state media for both
   the 3-slice and single-sprite thumb shapes.

ScrollbarSkinLiveDatTests pins the designations and state media against
the installed DAT so a revision or importer regression fails loudly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-24 19:20:43 +02:00
parent 35abbe1d0d
commit 8fd3d1a9f0
12 changed files with 541 additions and 64 deletions

View file

@ -181,8 +181,8 @@ public sealed class VendorUiController : IRetainedPanelController, IItemListDrag
/// list, with a real thumb/up/down-button subtree matching
/// <see cref="UiScrollbar"/>'s own shape exactly: thumb caps
/// <c>0x06004C60</c>/<c>63</c>/<c>66</c>, up button (element
/// <c>0x10000071</c>) <c>0x06004C69</c>/<c>6A</c>/<c>6B</c>, down button
/// (element <c>0x10000072</c>) <c>0x06004C6C</c>/<c>6D</c>/<c>6E</c>,
/// <c>0x10000072</c>, retail-seated on top) <c>0x06004C6C</c>/<c>6D</c>/<c>6E</c>, down button
/// (element <c>0x10000071</c>, retail-seated on the bottom) <c>0x06004C69</c>/<c>6A</c>/<c>6B</c>,
/// track <c>0x06004C5F</c>). With 18 authored categories and only 6
/// visible rows, retail's actual rendering is a single scrolling column
/// (matching the user's reference screenshot: ~visible rows + scrollbar +
@ -288,8 +288,10 @@ public sealed class VendorUiController : IRetainedPanelController, IItemListDrag
private const uint TypeMenuScrollThumbTopSprite = 0x06004C60u;
private const uint TypeMenuScrollThumbSprite = 0x06004C63u;
private const uint TypeMenuScrollThumbBottomSprite = 0x06004C66u;
private const uint TypeMenuScrollUpSprite = 0x06004C69u;
private const uint TypeMenuScrollDownSprite = 0x06004C6Cu;
// Retail seating (UpdateScrollingArea @0x00470AA0): top = the INCREMENT
// designee's UP-arrow art, bottom = the DECREMENT designee's DOWN-arrow.
private const uint TypeMenuScrollUpSprite = RetailScrollbarChrome.UpNormal;
private const uint TypeMenuScrollDownSprite = RetailScrollbarChrome.DownNormal;
/// <summary>
/// Retail's ordered category table, transcribed verbatim from