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:
Erik 2026-08-24 19:54:28 +02:00
parent 8fd3d1a9f0
commit 2d6333f84c
3 changed files with 141 additions and 2 deletions

View file

@ -208,8 +208,21 @@ public sealed class UiScrollbar : UiElement
internal bool IsPresentationVisible => !HideWhenDisabled || !IsModelDisabled;
/// <summary>
/// A content-fits (disabled) bar is still hit-testable and still
/// hover-highlights — retail's arrows and thumb are real child elements
/// whose Normal_rollover hot-tracking keeps running when the scrollbar
/// disables; the disabled state only hides the page-click regions
/// (UpdateLayout @0x004710d0's children 4-7) and, with attribute 0x79,
/// the whole bar. Scrolling input stays inert through geometry: with a
/// full-track thumb there is no travel and the line/page steps clamp to
/// nothing. The previous IsModelDisabled hit gate made the bar
/// hit-TRANSPARENT, which is why hovering it "did nothing" (2026-08-24
/// live probe: hovers over the bar reported widget=&lt;none&gt;).
/// Only a presentation-hidden bar (0x79 + disabled) ignores the pointer.
/// </summary>
protected override bool OnHitTest(float localX, float localY)
=> !IsModelDisabled && base.OnHitTest(localX, localY);
=> IsPresentationVisible && base.OnHitTest(localX, localY);
/// <summary>
/// Computes the thumb rectangle (local y origin and height) within the track area
@ -520,7 +533,11 @@ public sealed class UiScrollbar : UiElement
return false; // informational — never consumes
}
if (IsModelDisabled)
// Only a presentation-HIDDEN bar ignores input (see OnHitTest's own
// doc): a visible disabled bar keeps hover/pressed visuals exactly
// like retail's still-hot-tracking button/thumb children, while its
// scroll operations no-op through zero travel.
if (!IsPresentationVisible)
{
_draggingThumb = false;
_hoveredThumb = false;