fix(ui): retail scrollbar parity — button seating, full-track thumb, hover/pressed states
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:
parent
35abbe1d0d
commit
8fd3d1a9f0
12 changed files with 541 additions and 64 deletions
|
|
@ -0,0 +1,67 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.App.UI.Layout;
|
||||
using DatReaderWriter;
|
||||
|
||||
namespace AcDream.App.Tests.UI.Layout;
|
||||
|
||||
/// <summary>
|
||||
/// 2026-08-24 pin: the base scrollbar skin (layout <c>0x2100003E</c>)
|
||||
/// authors the button DESIGNATIONS that drive retail's runtime seating
|
||||
/// (<c>UIElement_Scrollbar::UpdateScrollingArea @0x00470AA0</c> moves the
|
||||
/// increment designee to the top/left, the decrement designee to the
|
||||
/// bottom/right, ignoring authored positions). The vertical skin
|
||||
/// designates 0x77=0x10000072 (UP-arrow art) and 0x78=0x10000071
|
||||
/// (DOWN-arrow art) — seating by authored Y renders both arrows upside
|
||||
/// down, the owner-reported bug. Also pins the three-state media sets
|
||||
/// <see cref="RetailScrollbarChrome"/> mirrors, so a DAT revision or
|
||||
/// importer regression that loses a state fails loudly.
|
||||
/// </summary>
|
||||
[Trait("Lane", "InstalledDat")]
|
||||
public sealed class ScrollbarSkinLiveDatTests
|
||||
{
|
||||
private static string DatDirectory =>
|
||||
Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
"Documents", "Asheron's Call");
|
||||
|
||||
[InstalledDatFact]
|
||||
public void VerticalBaseSkin_DesignatesUpArrowAsIncrement_WithThreeStateMedia()
|
||||
{
|
||||
using var dats = new DatCollection(
|
||||
DatDirectory, DatReaderWriter.Options.DatAccessType.Read);
|
||||
ElementInfo? tree = LayoutImporter.ImportInfos(dats, 0x2100003Eu);
|
||||
Assert.NotNull(tree);
|
||||
|
||||
ElementInfo bar = Assert.Single(Flatten(tree!), e => e.Id == 0x10000455u);
|
||||
Assert.True(bar.TryGetEffectiveProperty(0x77u, out UiPropertyValue inc));
|
||||
Assert.True(bar.TryGetEffectiveProperty(0x78u, out UiPropertyValue dec));
|
||||
Assert.Equal(0x10000072u, inc.UnsignedValue); // increment = UP arrow (authored Y=32)
|
||||
Assert.Equal(0x10000071u, dec.UnsignedValue); // decrement = DOWN arrow (authored Y=0)
|
||||
|
||||
// Increment (top after seating): red-gem normal, amber rollover,
|
||||
// highlight pressed — the RetailScrollbarChrome Up set.
|
||||
ElementInfo up = Assert.Single(bar.Children, c => c.Id == 0x10000072u);
|
||||
Assert.Equal(RetailScrollbarChrome.UpNormal, up.StateMedia["Normal"].File);
|
||||
Assert.Equal(RetailScrollbarChrome.UpRollover, up.StateMedia["Normal_rollover"].File);
|
||||
Assert.Equal(RetailScrollbarChrome.UpPressed, up.StateMedia["Normal_pressed"].File);
|
||||
ElementInfo down = Assert.Single(bar.Children, c => c.Id == 0x10000071u);
|
||||
Assert.Equal(RetailScrollbarChrome.DownNormal, down.StateMedia["Normal"].File);
|
||||
Assert.Equal(RetailScrollbarChrome.DownRollover, down.StateMedia["Normal_rollover"].File);
|
||||
Assert.Equal(RetailScrollbarChrome.DownPressed, down.StateMedia["Normal_pressed"].File);
|
||||
|
||||
// Thumb (structural child 1) slices each author the three states.
|
||||
ElementInfo thumb = Assert.Single(bar.Children, c => c.Id == 1u);
|
||||
ElementInfo mid = Assert.Single(thumb.Children, c => c.Id == 0x10000365u);
|
||||
Assert.Equal(RetailScrollbarChrome.ThumbMidNormal, mid.StateMedia["Normal"].File);
|
||||
Assert.Equal(RetailScrollbarChrome.ThumbMidRollover, mid.StateMedia["Normal_rollover"].File);
|
||||
Assert.Equal(RetailScrollbarChrome.ThumbMidPressed, mid.StateMedia["Normal_pressed"].File);
|
||||
}
|
||||
|
||||
private static IEnumerable<ElementInfo> Flatten(ElementInfo e)
|
||||
{
|
||||
yield return e;
|
||||
foreach (var c in e.Children)
|
||||
foreach (var d in Flatten(c))
|
||||
yield return d;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue