Show scrollbar thumb when hovering the track area

When the cursor was on the scrollbar track (above/below the thumb) but
not directly on the thumb, the scrollbar would immediately schedule
auto-hide because update_hovered_thumb fell through to ThumbState::Inactive.
This caused the thumb to disappear while the user was trying to interact
with the track.

Two fixes:
- update_hovered_thumb: add a second branch that checks if any
  cursor_hitbox is hovered (cursor on track but not thumb) and keeps
  ThumbState::Hover so the scrollbar stays visible.
- parent_hovered: also check cursor_hitbox.is_hovered in addition to
  parent_bounds_hitbox, since BlockMouseExceptScroll behavior can prevent
  the parent hitbox from being considered hovered when the cursor is over
  the scrollbar strip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Aaron Ang 2026-05-20 14:45:05 -07:00
parent e3d5bcb0df
commit cc22aaadd6

View file

@ -785,21 +785,26 @@ impl<T: ScrollableHandle> ScrollbarState<T> {
window: &mut Window,
cx: &mut Context<Self>,
) {
self.set_thumb_state(
if let Some(&ScrollbarLayout { axis, .. }) =
self.last_prepaint_state.as_ref().and_then(|state| {
state
.thumb_for_position(position)
.filter(|thumb| thumb.cursor_hitbox.is_hovered(window))
})
{
ThumbState::Hover(axis)
} else {
ThumbState::Inactive
},
window,
cx,
);
let thumb_state = if let Some(&ScrollbarLayout { axis, .. }) =
self.last_prepaint_state.as_ref().and_then(|state| {
state
.thumb_for_position(position)
.filter(|thumb| thumb.cursor_hitbox.is_hovered(window))
}) {
ThumbState::Hover(axis)
} else if let Some(&ScrollbarLayout { axis, .. }) =
self.last_prepaint_state.as_ref().and_then(|state| {
state
.thumbs
.iter()
.find(|thumb| thumb.cursor_hitbox.is_hovered(window))
})
{
ThumbState::Hover(axis)
} else {
ThumbState::Inactive
};
self.set_thumb_state(thumb_state, window, cx);
}
fn set_thumb_state(&mut self, state: ThumbState, window: &mut Window, cx: &mut Context<Self>) {
@ -835,9 +840,13 @@ impl<T: ScrollableHandle> ScrollbarState<T> {
}
fn parent_hovered(&self, window: &Window) -> bool {
self.last_prepaint_state
.as_ref()
.is_some_and(|state| state.parent_bounds_hitbox.is_hovered(window))
self.last_prepaint_state.as_ref().is_some_and(|state| {
state.parent_bounds_hitbox.is_hovered(window)
|| state
.thumbs
.iter()
.any(|thumb| thumb.cursor_hitbox.is_hovered(window))
})
}
fn hit_for_position(&self, position: &Point<Pixels>) -> Option<&ScrollbarLayout> {