fix(ui): disabled scrollbars keep retail hover hot-tracking
Owner report + live [ui-hover] probe (2026-08-24): hovering a
content-fits scrollbar did nothing because IsModelDisabled made the
whole bar hit-TRANSPARENT — every hover over it reported
widget=<none>. Retail's arrows and thumb are real child elements whose
Normal_rollover hot-tracking keeps running while the scrollbar is
disabled (UpdateLayout @0x004710d0 only hides the page-click regions,
children 4-7, and — with attribute 0x79 — the whole bar); scrolling
stays inert through geometry, not an input gate: a full-track thumb has
zero travel and the line/page steps clamp against nothing.
OnHitTest and the input path now gate on presentation visibility only.
A visible disabled bar hover-highlights and consumes clicks without
scrolling; a HideWhenDisabled bar stays inert. New root-level hover
tests drive real UiRoot hit-test dispatch (bare widget + the mounted
production character fixture) so this class of "state machine green,
pointer never arrives" bug fails loudly.
User-verified live 2026-08-24 ("bar works now").
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
8fd3d1a9f0
commit
2d6333f84c
3 changed files with 141 additions and 2 deletions
|
|
@ -1953,6 +1953,51 @@ public class CharacterStatControllerTests
|
|||
Assert.Equal(RetailScrollbarChrome.ThumbMidRollover, scrollbar.ThumbRolloverSprite);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 2026-08-24 owner report: "hovering the scrollbar and arrows does
|
||||
/// nothing" — root-level hover through the REAL mounted character
|
||||
/// fixture (production element tree, overlays and z-order included).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void ProductionFixture_HoveringTheSkillScrollbar_SelectsRolloverMedia()
|
||||
{
|
||||
var layout = FixtureLoader.LoadCharacter();
|
||||
CharacterStatController.Bind(
|
||||
layout,
|
||||
SampleData.SampleCharacter,
|
||||
spriteResolve: id => (id, 16, 16));
|
||||
var root = new UiRoot { Width = 800f, Height = 600f };
|
||||
root.AddChild(layout.Root);
|
||||
ApplyLayoutPass(layout.Root);
|
||||
|
||||
ClickTab(layout, left: 92f);
|
||||
var page = layout.Root.Children.Single(
|
||||
e => e.DatElementId == CharacterStatController.AttributesPageId);
|
||||
var list = Descendants(page).Single(
|
||||
e => e.DatElementId == CharacterStatController.ListBoxId);
|
||||
var scrollbar = list.Parent!.Children.OfType<UiScrollbar>().Single(
|
||||
e => e.DatElementId == CharacterStatController.ListScrollbarId);
|
||||
Assert.True(scrollbar.Visible);
|
||||
// The headless harness never measures row content; give the REAL
|
||||
// bound model an overflowing extent so the bar is enabled the way
|
||||
// a populated skills list is in production.
|
||||
Assert.NotNull(scrollbar.Model);
|
||||
scrollbar.Model!.ContentHeight = 800;
|
||||
scrollbar.Model.ViewHeight = 398;
|
||||
Assert.False(scrollbar.IsModelDisabled);
|
||||
|
||||
var screen = scrollbar.ScreenPosition;
|
||||
// Over the up arrow (5px into the 16px top button).
|
||||
root.OnMouseMove((int)(screen.X + 8f), (int)(screen.Y + 5f));
|
||||
Assert.Equal(
|
||||
RetailScrollbarChrome.UpRollover, scrollbar.ActiveStartSpriteForTest);
|
||||
|
||||
// Over the thumb (just below the up button; thumb starts at track top).
|
||||
root.OnMouseMove((int)(screen.X + 8f), (int)(screen.Y + 24f));
|
||||
Assert.Equal(
|
||||
RetailScrollbarChrome.ThumbMidRollover, scrollbar.ActiveThumbSpriteForTest);
|
||||
}
|
||||
|
||||
// ── Helpers ──────────────────────────────────────────────────────────────
|
||||
|
||||
private static void ClickTab(ImportedLayout layout, float left)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue