Repository navigation
Add mouseCursor to RawScrollbar - #185750
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request introduces a mouseCursor property to Scrollbar, RawScrollbar, and ScrollbarThemeData, allowing customization of the mouse cursor during hover and drag interactions. The review feedback identifies several improvement opportunities, including handling drag cancellations to properly reset internal state, optimizing performance by reducing redundant setState calls, and ensuring the cursor remains consistent during an active drag even if the pointer moves outside the scrollbar track. Documentation updates were also suggested to align with these proposed behavioral changes.
15914d1 to
f286ac9
Compare
|
Updated this PR after the The framework-side fix is enough to resolve the original I-beam-over- |
26425ff to
d769e29
Compare
Introduce a WidgetStateProperty<MouseCursor?>-based override on RawScrollbar so callers can drive the cursor over the scrollbar's thumb/track from hovered/dragged WidgetStates. The override is gated to apply only while the pointer is over the painted scrollbar or while a thumb drag is in progress, so ancestor cursors (e.g. a TextField's I-beam) remain in effect over non-scrollbar regions. Keeping the override applied while a drag is active prevents the cursor from flickering back to the ancestor cursor if the pointer briefly leaves the painted track during a drag, and matches the typical native behavior of grab/grabbing cursors. To avoid spurious rebuilds, only call updateMouseCursor() when the hover-active state actually changes, and reset _dragIsActive (and re-resolve the cursor) when the thumb drag is cancelled by another gesture taking over. The Scrollbar/ScrollbarThemeData surfacing of this property in the material library is omitted here due to the active material/cupertino code freeze (flutter#184093) and will be ported to material_ui in flutter/packages once that repo opens for changes, per the freeze guidance. Refs flutter#185733
d769e29 to
bc80937
Compare
|
I guess this is a partial fix for #185813 then because of the code freeze? |
|
Yes, that's right. This PR is the framework-side |
dkwingsmt
left a comment
There was a problem hiding this comment.
LGTM except for a doc improvement. Thank you!
Piinks
left a comment
There was a problem hiding this comment.
I have to finish review here, there are a few things to address. Just blocking merging while I finish
Piinks
left a comment
There was a problem hiding this comment.
Thanks for working on this! The overall mechanism of using MouseRegion with MouseCursor.defer to resolve #185733 and prevent swallowing ancestor cursors is the right approach.
First, I want to acknowledge that a previous review comment suggested removing WidgetState.hovered tracking because typical Flutter widgets don't need manual hover state for mouse cursors, and I recognize it can be frustrating to receive conflicting reviewer direction after following that feedback.
However, after taking a closer look at the architecture, RawScrollbar is fundamentally different from typical widgets like IconButton or ElevatedButton:
- Standard widgets have their own
RenderBoxmatching their visual geometry, so a wrappingMouseRegionnaturally applies only to that widget. RawScrollbaris aCustomPaint(foregroundPainter: scrollbarPainter) that wraps the entire scrollable child viewport (which could be an 800x600SingleChildScrollView,ListView, orTextField). The painted scrollbar thumb and track are only a thin strip on that canvas.- Because
RawScrollbar'sMouseRegioncovers the entire viewport, we must determine if the pointer is actually hovering over the painted scrollbar/track area (viaisPointerOverScrollbar) and defer to ancestors when it isn't. - Furthermore, from an API consumer's standpoint, when the pointer is over the painted scrollbar track or thumb, the scrollbar is in the
WidgetState.hoveredstate. RemovingWidgetState.hoveredbreaks standardWidgetStatePropertyconstructs (such asWidgetStateProperty.fromMap({WidgetState.hovered: ...})), because callers receive<WidgetState>{}(the empty set, which semantically denotes unhovered/idle).
There is also a cursor-leak bug when releasing a drag outside the track.
Detailed inline comments with explanations and suggested fixes are below.
| /// | ||
| /// Subclasses can override this to add additional states. | ||
| @protected | ||
| Set<WidgetState> get cursorStates => <WidgetState>{if (_dragIsActive) WidgetState.dragged}; |
There was a problem hiding this comment.
I know earlier feedback suggested removing hover detection for mouse cursors, but in RawScrollbar's case, keeping WidgetState.hovered is necessary for the WidgetStateProperty contract.
Why RawScrollbar is different:
- For standard widgets (like
IconButtonorElevatedButton), the widget'sRenderBoxbounds match its painted bounds, so a staticMouseRegiononly triggers when the pointer is over the widget. - In contrast,
RawScrollbaris aCustomPaintwrapping the entire scroll view child. Because the scrollbar painter paints onto the larger viewport canvas,_hoverIsActive(viaisPointerOverScrollbar) is needed to distinguish when the mouse is over the painted scrollbar vs when it's hovering the rest of the child content. - Semantically, when the pointer is hovering over the painted scrollbar track or thumb, the scrollbar is hovered. If
cursorStatesreturns<WidgetState>{}during hover, standard FlutterWidgetStatePropertyusages fail silently:BecauseRawScrollbar( mouseCursor: WidgetStateProperty.fromMap(<WidgetStatesConstraint, MouseCursor?>{ WidgetState.dragged: SystemMouseCursors.grabbing, WidgetState.hovered: SystemMouseCursors.grab, }), ... )
statesis{},fromMapcannot matchWidgetState.hovered, returningnulland leaving the cursor asdefer. Callers usingresolveWithalso cannot distinguish idle/unhovered from hovered.
Suggested Fix:
- Include
WidgetState.hoveredincursorStateswhenever_hoverIsActiveis true:
@protected
Set<WidgetState> get cursorStates => <WidgetState>{
if (_dragIsActive) WidgetState.dragged,
if (_hoverIsActive) WidgetState.hovered,
if (!enableGestures) WidgetState.disabled,
};- Update the DartDoc on
RawScrollbar.mouseCursorto state that it resolves with:WidgetState.hoveredwhen the mouse pointer is over the painted scrollbar track/thumb.WidgetState.draggedwhile dragging the scrollbar thumb.
There was a problem hiding this comment.
Restored, using your body verbatim, in 1c8c90f.
One note on WidgetState.disabled so it is not a surprise later: handleHover is only wired up while enableGestures is true, so a non-interactive scrollbar never enters the hovered state and the resolved cursor is not applied. The state is therefore reported but cannot currently change the cursor. I documented that explicitly rather than dropping the line, since you asked for it. Happy to remove it if you would rather not expose a state that has no effect yet.
| /// The cursor for a mouse pointer when it enters or is hovering over the | ||
| /// scrollbar's thumb or track. | ||
| /// | ||
| /// Resolves in the following [WidgetState]s: |
There was a problem hiding this comment.
Once WidgetState.hovered is restored to cursorStates, please update this doc block:
/// Resolves in the following [WidgetState]s:
/// * [WidgetState.dragged].
/// * [WidgetState.hovered].
///
/// The [WidgetState.hovered] state is resolved when the pointer is over the
/// painted scrollbar track or thumb (using the same hit test area as hover
/// interactions). The [WidgetState.dragged] state is resolved while the
/// scrollbar thumb is being dragged.There was a problem hiding this comment.
Updated in 1c8c90f. It now lists WidgetState.dragged, WidgetState.hovered and WidgetState.disabled, and says when each one resolves.
| RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), | ||
| SystemMouseCursors.basic, | ||
| ); | ||
| expect(resolvedStates.last, isNot(contains(WidgetState.hovered))); |
There was a problem hiding this comment.
Once WidgetState.hovered is included in cursorStates, this assertion should be updated to expect contains(WidgetState.hovered) when the pointer is over the scrollbar.
Please also consider adding a test verifying that WidgetStateProperty.fromMap works with WidgetState.hovered.
There was a problem hiding this comment.
Flipped, and it now also asserts that dragged and disabled are absent while hovering, and that hovered is gone once the pointer moves back to the content.
Added RawScrollbar.mouseCursor resolves a WidgetStateProperty.fromMap, using your exact map. It covers hovering, dragging, the map ordering that makes dragged win over hovered, and the fallback back to the hovered entry on release.
| ); | ||
| }); | ||
|
|
||
| testWidgets( |
There was a problem hiding this comment.
This test uses a synthetic two-pointer setup (a touch pointer 2 dragging while a mouse pointer 1 hovers).
Please add a test covering a single-pointer desktop mouse drag:
- Mouse down on thumb -> move pointer into the content area -> verify cursor is
grabbing. - Mouse up in the content area -> verify cursor reverts immediately to ancestor cursor (
text/defer).
There was a problem hiding this comment.
Good catch, that was a real leak. Hover events are not delivered while a pointer button is down, so _hoverIsActive was still true from before the press, and releasing over the content left the hovered cursor applied until the next pointer move.
_handleThumbDragEnd now recomputes the hover state from the position the drag ended at, before handleThumbPressEnd resolves the cursor. Only mouse and trackpad drags do this, so a touch drag cannot clobber the hover state of a separate mouse pointer.
Added RawScrollbar.mouseCursor is released when a mouse drag ends off the scrollbar, following your steps exactly. It fails on the unpatched framework at the assertion after mouse up.
Auditing that path turned up two more ways the cursor could get stuck, both worse because no later event could clear them at all. Details in the summary comment below.
Restore WidgetState.hovered to cursorStates, alongside WidgetState.disabled, so WidgetStateProperty.fromMap and resolveWith can tell an idle scrollbar from a hovered one. Release the pointer state that feeds the cursor whenever no later event could clear it. Hover events are not delivered while a pointer button is down, so the hover state is recomputed when a mouse or trackpad thumb drag ends. The hovered and dragged states are also released when the scrollbar stops being interactive, or when its child stops being scrollable and the thumb drag recognizer is dropped without reporting a cancel. Without this the resolved cursor stayed applied over the whole scroll view. Refer to EditableText rather than TextField, so the doc imports stay on the widgets side of the Material split.
|
Thanks @Piinks, all six comments are addressed in 1c8c90f, with replies on each thread. On the cursor leak you spotted: hover events are not delivered while a pointer button is held down, so Auditing that path turned up two more ways the cursor could get stuck, both worse, because in these cases no later event could clear it at all:
One user-visible side effect worth flagging: after a mouse thumb drag that ends away from the scrollbar, the fade-out timer is no longer restarted by the following hover-exit, so the scrollbar can begin fading up to Also applied @navaronbracke's two nits. Locally: format and analyze clean, |
dkwingsmt
left a comment
There was a problem hiding this comment.
LGTM. Thank you for the continued polishing!
flutter/flutter@0cbd1a4...70797e1 2026-09-02 [email protected] [analysis] Upgrade package:analyzer to 14.3.0 and enable custom plugin compilation (flutter/flutter#191590) 2026-09-02 [email protected] Roll Dart SDK from d8d7869d8031 to 9a8fbde80f6c (1 revision) (flutter/flutter#192179) 2026-09-02 [email protected] Roll Skia from dec311244db4 to ae22da2e8308 (1 revision) (flutter/flutter#192177) 2026-09-02 [email protected] [tool] Migrate DoctorCommand to modular dependency injection (flutter/flutter#190758) 2026-09-02 [email protected] Roll Skia from 482b7e285244 to dec311244db4 (3 revisions) (flutter/flutter#192161) 2026-09-02 [email protected] Roll Skia from 28447e9fb8d5 to 482b7e285244 (3 revisions) (flutter/flutter#192157) 2026-09-02 [email protected] feat: Add placeholder to DecorationImage (flutter/flutter#191528) 2026-09-02 [email protected] [Android] Do not schedule engine frames on a detached FlutterJNI (flutter/flutter#191204) 2026-09-02 [email protected] Roll Skia from 3a242fb7cdb3 to 28447e9fb8d5 (1 revision) (flutter/flutter#192145) 2026-09-02 [email protected] Roll Dart SDK from 9164def35347 to d8d7869d8031 (2 revisions) (flutter/flutter#192144) 2026-09-02 [email protected] Add FadeInImageTransition.fadeInOver to fade the image in over the placeholder (flutter/flutter#186246) 2026-09-02 [email protected] Roll Skia from 63d13ec4df6d to 3a242fb7cdb3 (9 revisions) (flutter/flutter#192138) 2026-09-02 [email protected] [flutter_tools] Add --no-plugins flag and disable plugins for benchmark (flutter/flutter#192129) 2026-09-02 [email protected] [tool] Migrate ChannelCommand to modular dependency injection (flutter/flutter#190751) 2026-09-01 [email protected] Add mouseCursor to RawScrollbar (flutter/flutter#185750) 2026-09-01 [email protected] Fix self-comparison and assert typos in TestSemantics (flutter/flutter#191938) 2026-09-01 [email protected] [Linux] Wait for frames to be rendered before using them in another context (flutter/flutter#192094) 2026-09-01 [email protected] [wimp] Turn on multithreading for wimp. (flutter/flutter#191747) 2026-09-01 [email protected] Roll Skia from 3911a1fe7f7a to 63d13ec4df6d (6 revisions) (flutter/flutter#192125) 2026-09-01 [email protected] Remove the redundant flutter/shell/platform/android:robolectric_tests build target (flutter/flutter#192117) 2026-09-01 [email protected] [iOS] Preserve semantics parents after reparenting (flutter/flutter#189686) 2026-09-01 [email protected] [flutter_tools] Report preview reload timing analytics in LspPreviewDetector (flutter/flutter#192120) 2026-09-01 [email protected] [flutter_tools] Implement Templates slice and flutter create integration (flutter/flutter#191748) 2026-09-01 [email protected] Uiscene migrate add2app hosts (flutter/flutter#191847) 2026-09-01 [email protected] Roll Packages from d642322 to 7a7912f (8 revisions) (flutter/flutter#192119) 2026-09-01 [email protected] [web] Anchor flt-semantics-host at 0,0 to fix WebKit semantics offset (flutter/flutter#190486) 2026-09-01 [email protected] Replace Shell::WaitForFirstFrame with an asynchronous API that matches the Shell threading model (flutter/flutter#191841) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…r#12727) flutter/flutter@0cbd1a4...70797e1 2026-09-02 [email protected] [analysis] Upgrade package:analyzer to 14.3.0 and enable custom plugin compilation (flutter/flutter#191590) 2026-09-02 [email protected] Roll Dart SDK from d8d7869d8031 to 9a8fbde80f6c (1 revision) (flutter/flutter#192179) 2026-09-02 [email protected] Roll Skia from dec311244db4 to ae22da2e8308 (1 revision) (flutter/flutter#192177) 2026-09-02 [email protected] [tool] Migrate DoctorCommand to modular dependency injection (flutter/flutter#190758) 2026-09-02 [email protected] Roll Skia from 482b7e285244 to dec311244db4 (3 revisions) (flutter/flutter#192161) 2026-09-02 [email protected] Roll Skia from 28447e9fb8d5 to 482b7e285244 (3 revisions) (flutter/flutter#192157) 2026-09-02 [email protected] feat: Add placeholder to DecorationImage (flutter/flutter#191528) 2026-09-02 [email protected] [Android] Do not schedule engine frames on a detached FlutterJNI (flutter/flutter#191204) 2026-09-02 [email protected] Roll Skia from 3a242fb7cdb3 to 28447e9fb8d5 (1 revision) (flutter/flutter#192145) 2026-09-02 [email protected] Roll Dart SDK from 9164def35347 to d8d7869d8031 (2 revisions) (flutter/flutter#192144) 2026-09-02 [email protected] Add FadeInImageTransition.fadeInOver to fade the image in over the placeholder (flutter/flutter#186246) 2026-09-02 [email protected] Roll Skia from 63d13ec4df6d to 3a242fb7cdb3 (9 revisions) (flutter/flutter#192138) 2026-09-02 [email protected] [flutter_tools] Add --no-plugins flag and disable plugins for benchmark (flutter/flutter#192129) 2026-09-02 [email protected] [tool] Migrate ChannelCommand to modular dependency injection (flutter/flutter#190751) 2026-09-01 [email protected] Add mouseCursor to RawScrollbar (flutter/flutter#185750) 2026-09-01 [email protected] Fix self-comparison and assert typos in TestSemantics (flutter/flutter#191938) 2026-09-01 [email protected] [Linux] Wait for frames to be rendered before using them in another context (flutter/flutter#192094) 2026-09-01 [email protected] [wimp] Turn on multithreading for wimp. (flutter/flutter#191747) 2026-09-01 [email protected] Roll Skia from 3911a1fe7f7a to 63d13ec4df6d (6 revisions) (flutter/flutter#192125) 2026-09-01 [email protected] Remove the redundant flutter/shell/platform/android:robolectric_tests build target (flutter/flutter#192117) 2026-09-01 [email protected] [iOS] Preserve semantics parents after reparenting (flutter/flutter#189686) 2026-09-01 [email protected] [flutter_tools] Report preview reload timing analytics in LspPreviewDetector (flutter/flutter#192120) 2026-09-01 [email protected] [flutter_tools] Implement Templates slice and flutter create integration (flutter/flutter#191748) 2026-09-01 [email protected] Uiscene migrate add2app hosts (flutter/flutter#191847) 2026-09-01 [email protected] Roll Packages from d642322 to 7a7912f (8 revisions) (flutter/flutter#192119) 2026-09-01 [email protected] [web] Anchor flt-semantics-host at 0,0 to fix WebKit semantics offset (flutter/flutter#190486) 2026-09-01 [email protected] Replace Shell::WaitForFirstFrame with an asynchronous API that matches the Shell threading model (flutter/flutter#191841) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
…r#12727) flutter/flutter@0cbd1a4...70797e1 2026-09-02 [email protected] [analysis] Upgrade package:analyzer to 14.3.0 and enable custom plugin compilation (flutter/flutter#191590) 2026-09-02 [email protected] Roll Dart SDK from d8d7869d8031 to 9a8fbde80f6c (1 revision) (flutter/flutter#192179) 2026-09-02 [email protected] Roll Skia from dec311244db4 to ae22da2e8308 (1 revision) (flutter/flutter#192177) 2026-09-02 [email protected] [tool] Migrate DoctorCommand to modular dependency injection (flutter/flutter#190758) 2026-09-02 [email protected] Roll Skia from 482b7e285244 to dec311244db4 (3 revisions) (flutter/flutter#192161) 2026-09-02 [email protected] Roll Skia from 28447e9fb8d5 to 482b7e285244 (3 revisions) (flutter/flutter#192157) 2026-09-02 [email protected] feat: Add placeholder to DecorationImage (flutter/flutter#191528) 2026-09-02 [email protected] [Android] Do not schedule engine frames on a detached FlutterJNI (flutter/flutter#191204) 2026-09-02 [email protected] Roll Skia from 3a242fb7cdb3 to 28447e9fb8d5 (1 revision) (flutter/flutter#192145) 2026-09-02 [email protected] Roll Dart SDK from 9164def35347 to d8d7869d8031 (2 revisions) (flutter/flutter#192144) 2026-09-02 [email protected] Add FadeInImageTransition.fadeInOver to fade the image in over the placeholder (flutter/flutter#186246) 2026-09-02 [email protected] Roll Skia from 63d13ec4df6d to 3a242fb7cdb3 (9 revisions) (flutter/flutter#192138) 2026-09-02 [email protected] [flutter_tools] Add --no-plugins flag and disable plugins for benchmark (flutter/flutter#192129) 2026-09-02 [email protected] [tool] Migrate ChannelCommand to modular dependency injection (flutter/flutter#190751) 2026-09-01 [email protected] Add mouseCursor to RawScrollbar (flutter/flutter#185750) 2026-09-01 [email protected] Fix self-comparison and assert typos in TestSemantics (flutter/flutter#191938) 2026-09-01 [email protected] [Linux] Wait for frames to be rendered before using them in another context (flutter/flutter#192094) 2026-09-01 [email protected] [wimp] Turn on multithreading for wimp. (flutter/flutter#191747) 2026-09-01 [email protected] Roll Skia from 3911a1fe7f7a to 63d13ec4df6d (6 revisions) (flutter/flutter#192125) 2026-09-01 [email protected] Remove the redundant flutter/shell/platform/android:robolectric_tests build target (flutter/flutter#192117) 2026-09-01 [email protected] [iOS] Preserve semantics parents after reparenting (flutter/flutter#189686) 2026-09-01 [email protected] [flutter_tools] Report preview reload timing analytics in LspPreviewDetector (flutter/flutter#192120) 2026-09-01 [email protected] [flutter_tools] Implement Templates slice and flutter create integration (flutter/flutter#191748) 2026-09-01 [email protected] Uiscene migrate add2app hosts (flutter/flutter#191847) 2026-09-01 [email protected] Roll Packages from d642322 to 7a7912f (8 revisions) (flutter/flutter#192119) 2026-09-01 [email protected] [web] Anchor flt-semantics-host at 0,0 to fix WebKit semantics offset (flutter/flutter#190486) 2026-09-01 [email protected] Replace Shell::WaitForFirstFrame with an asynchronous API that matches the Shell threading model (flutter/flutter#191841) If this roll has caused a breakage, revert this CL and stop the roller using the controls here: https://autoroll.skia.org/r/flutter-packages Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Description
Adds a
mouseCursorproperty toRawScrollbar, allowing callers to customize the mouse cursor over the scrollbar's thumb/track via aWidgetStateProperty<MouseCursor?>. It resolves withWidgetState.hoveredwhile the pointer is over the painted scrollbar,WidgetState.draggedwhile a thumb drag is active, andWidgetState.disabledwhile the scrollbar is not interactive.The override is gated to apply only while the pointer is over the painted scrollbar, using the same hit-test region that already drives hover behavior. When the pointer is anywhere else inside the scroll view's child, cursor resolution is deferred to ancestors, so a parent widget's cursor, such as an enclosing
EditableText's I-beam, is preserved. This is the core fix for the scenario described in #185733: today, the scrollbar'sMouseRegionswallows the ancestor cursor for the entire scroll-view area. With this change, only the painted scrollbar surface participates in cursor resolution.When
mouseCursoris null, or when its resolved value is null,MouseCursor.deferis used and ancestor cursors are left unchanged everywhere.The hover callbacks are only installed while the scrollbar is interactive, and the thumb drag recognizers only exist while the child is scrollable. When either stops being true the callbacks simply stop arriving, and a dropped drag recognizer is disposed without reporting a cancel, so the pointer state that feeds the cursor is released explicitly: when a mouse or trackpad thumb drag ends, when
interactivechanges, and when the child stops being scrollable. Without that, the resolved cursor could stay applied over the whole scroll view with no event left to clear it.One user-visible side effect worth calling out: after a mouse thumb drag that ends away from the scrollbar, the fade-out timer is no longer restarted by the following hover-exit, so the scrollbar can begin fading up to
timeToFadeearlier than before. That matches the behavior a touch drag already had.Why framework-only
flutter/flutteris currently under a Material/Cupertino code freeze (#184093). Per the freeze guidance, the framework-side change can land now, and the correspondingScrollbar/ScrollbarThemeDatasurfacing requested in #185733 will be ported tomaterial_uiinflutter/packagesonce that repo opens for changes. This PR is therefore scoped tolib/src/widgets/scrollbar.dartand its widget-layer tests only.The framework API alone is enough to fix the reported scenario: an I-beam cursor over a
TextField's scrollable region remains intact when hovering content but switches over the scrollbar. See the testRawScrollbar.mouseCursor only applies while the pointer is over the scrollbar.This PR uses a non-closing
Refs #185733so the issue stays open to track the remainingScrollbar/ScrollbarThemeDatawork, which will land separately influtter/packages.Tests
Nine widget-layer tests in
test/widgets/scrollbar_test.dart:RawScrollbar.mouseCursor only applies while the pointer is over the scrollbarverifies an ancestorMouseRegion(cursor: text)keeps the I-beam over the content area, switches to the scrollbar's cursor over the thumb, yields back to the I-beam when leaving the scrollbar, and passes exactlyWidgetState.hoveredto the cursor resolver while hovering.RawScrollbar.mouseCursor resolves a WidgetStateProperty.fromMapverifies theWidgetStatePropertycontract end to end: hovered, dragged, map precedence, and the fallback back to the hovered entry on release.RawScrollbar without mouseCursor preserves the ancestor cursor over the scrollbarverifies the default behavior: with no override set, the ancestor cursor wins everywhere, including over the scrollbar.RawScrollbar.mouseCursor updates when the property changes while hoveredverifies that prop changes during hover are picked up.RawScrollbar.mouseCursor stays applied while a thumb drag is active even when the pointer leaves the scrollbarverifies that a dragged cursor remains stable during an active thumb drag.RawScrollbar.mouseCursor is released when a mouse drag ends off the scrollbarverifies a single-pointer desktop drag: pressing the thumb, dragging into the content area, and releasing there hands the cursor straight back to the ancestor.RawScrollbar.mouseCursor is released when the scrollbar stops being interactiveverifies that flippinginteractiveto false, both while hovering and mid-drag, releases the override.RawScrollbar.mouseCursor is released when the child stops being scrollableverifies that shrinking the child mid-drag releases the dragged state.RawScrollbar.mouseCursor clears dragged state when thumb drag is cancelledverifies that cancellation resets the dragged cursor state.Each of the three release paths was confirmed to fail without its fix.
Verification run locally:
./bin/dart format --output=none --set-exit-if-changed packages/flutter/lib/src/widgets/scrollbar.dart packages/flutter/test/widgets/scrollbar_test.dart./bin/flutter analyze --current-package --no-pub packages/flutter/lib/src/widgets/scrollbar.dart packages/flutter/test/widgets/scrollbar_test.dart./bin/flutter test packages/flutter/test/widgets/scrollbar_test.dart(112 passing)./bin/flutter test packages/flutter/test/widgets/(7181 passing)./bin/flutter test packages/flutter/test/material/scrollbar_test.dart packages/flutter/test/cupertino/scrollbar_test.dartand the other suites that build a scrollbargit diff --check upstream/master...HEADRefs #185733