Unverified Commit 1aada6fc authored by xubaolin's avatar xubaolin Committed by GitHub

[New Feature]Support mouse wheel event on the scrollbar widget (#109659)

* rebase master and add a test

* fix the test

* fix the test

* fix the test
parent 96f9ca83
...@@ -1458,13 +1458,17 @@ class RawScrollbar extends StatefulWidget { ...@@ -1458,13 +1458,17 @@ class RawScrollbar extends StatefulWidget {
/// scrollbar track. /// scrollbar track.
class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProviderStateMixin<T> { class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProviderStateMixin<T> {
Offset? _startDragScrollbarAxisOffset; Offset? _startDragScrollbarAxisOffset;
Offset? _lastDragUpdateOffset;
double? _startDragThumbOffset; double? _startDragThumbOffset;
ScrollController? _currentController; ScrollController? _cachedController;
Timer? _fadeoutTimer; Timer? _fadeoutTimer;
late AnimationController _fadeoutAnimationController; late AnimationController _fadeoutAnimationController;
late Animation<double> _fadeoutOpacityAnimation; late Animation<double> _fadeoutOpacityAnimation;
final GlobalKey _scrollbarPainterKey = GlobalKey(); final GlobalKey _scrollbarPainterKey = GlobalKey();
bool _hoverIsActive = false; bool _hoverIsActive = false;
bool _thumbDragging = false;
ScrollController? get _effectiveScrollController => widget.controller ?? PrimaryScrollController.maybeOf(context);
/// Used to paint the scrollbar. /// Used to paint the scrollbar.
/// ///
...@@ -1550,12 +1554,11 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1550,12 +1554,11 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
} }
void _validateInteractions(AnimationStatus status) { void _validateInteractions(AnimationStatus status) {
final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.maybeOf(context);
if (status == AnimationStatus.dismissed) { if (status == AnimationStatus.dismissed) {
assert(_fadeoutOpacityAnimation.value == 0.0); assert(_fadeoutOpacityAnimation.value == 0.0);
// We do not check for a valid scroll position if the scrollbar is not // We do not check for a valid scroll position if the scrollbar is not
// visible, because it cannot be interacted with. // visible, because it cannot be interacted with.
} else if (scrollController != null && enableGestures) { } else if (_effectiveScrollController != null && enableGestures) {
// Interactive scrollbars need to be properly configured. If it is visible // Interactive scrollbars need to be properly configured. If it is visible
// for interaction, ensure we are set up properly. // for interaction, ensure we are set up properly.
assert(_debugCheckHasValidScrollPosition()); assert(_debugCheckHasValidScrollPosition());
...@@ -1566,7 +1569,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1566,7 +1569,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
if (!mounted) { if (!mounted) {
return true; return true;
} }
final ScrollController? scrollController = widget.controller ?? PrimaryScrollController.maybeOf(context); final ScrollController? scrollController = _effectiveScrollController;
final bool tryPrimary = widget.controller == null; final bool tryPrimary = widget.controller == null;
final String controllerForError = tryPrimary final String controllerForError = tryPrimary
? 'PrimaryScrollController' ? 'PrimaryScrollController'
...@@ -1698,11 +1701,11 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1698,11 +1701,11 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
} }
void _updateScrollPosition(Offset updatedOffset) { void _updateScrollPosition(Offset updatedOffset) {
assert(_currentController != null); assert(_cachedController != null);
assert(_startDragScrollbarAxisOffset != null); assert(_startDragScrollbarAxisOffset != null);
assert(_startDragThumbOffset != null); assert(_startDragThumbOffset != null);
final ScrollPosition position = _currentController!.position; final ScrollPosition position = _cachedController!.position;
late double primaryDelta; late double primaryDelta;
switch (position.axisDirection) { switch (position.axisDirection) {
case AxisDirection.up: case AxisDirection.up:
...@@ -1761,9 +1764,9 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1761,9 +1764,9 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
/// current scroll controller does not have any attached positions. /// current scroll controller does not have any attached positions.
@protected @protected
Axis? getScrollbarDirection() { Axis? getScrollbarDirection() {
assert(_currentController != null); assert(_cachedController != null);
if (_currentController!.hasClients) { if (_cachedController!.hasClients) {
return _currentController!.position.axis; return _cachedController!.position.axis;
} }
return null; return null;
} }
...@@ -1788,7 +1791,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1788,7 +1791,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
@mustCallSuper @mustCallSuper
void handleThumbPressStart(Offset localPosition) { void handleThumbPressStart(Offset localPosition) {
assert(_debugCheckHasValidScrollPosition()); assert(_debugCheckHasValidScrollPosition());
_currentController = widget.controller ?? PrimaryScrollController.maybeOf(context); _cachedController = _effectiveScrollController;
final Axis? direction = getScrollbarDirection(); final Axis? direction = getScrollbarDirection();
if (direction == null) { if (direction == null) {
return; return;
...@@ -1797,6 +1800,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1797,6 +1800,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
_fadeoutAnimationController.forward(); _fadeoutAnimationController.forward();
_startDragScrollbarAxisOffset = localPosition; _startDragScrollbarAxisOffset = localPosition;
_startDragThumbOffset = scrollbarPainter.getThumbScrollOffset(); _startDragThumbOffset = scrollbarPainter.getThumbScrollOffset();
_thumbDragging = true;
} }
/// Handler called when a currently active long press gesture moves. /// Handler called when a currently active long press gesture moves.
...@@ -1806,7 +1810,11 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1806,7 +1810,11 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
@mustCallSuper @mustCallSuper
void handleThumbPressUpdate(Offset localPosition) { void handleThumbPressUpdate(Offset localPosition) {
assert(_debugCheckHasValidScrollPosition()); assert(_debugCheckHasValidScrollPosition());
final ScrollPosition position = _currentController!.position; if (_lastDragUpdateOffset == localPosition) {
return;
}
_lastDragUpdateOffset = localPosition;
final ScrollPosition position = _cachedController!.position;
if (!position.physics.shouldAcceptUserOffset(position)) { if (!position.physics.shouldAcceptUserOffset(position)) {
return; return;
} }
...@@ -1822,22 +1830,24 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1822,22 +1830,24 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
@mustCallSuper @mustCallSuper
void handleThumbPressEnd(Offset localPosition, Velocity velocity) { void handleThumbPressEnd(Offset localPosition, Velocity velocity) {
assert(_debugCheckHasValidScrollPosition()); assert(_debugCheckHasValidScrollPosition());
_thumbDragging = false;
final Axis? direction = getScrollbarDirection(); final Axis? direction = getScrollbarDirection();
if (direction == null) { if (direction == null) {
return; return;
} }
_maybeStartFadeoutTimer(); _maybeStartFadeoutTimer();
_startDragScrollbarAxisOffset = null; _startDragScrollbarAxisOffset = null;
_lastDragUpdateOffset = null;
_startDragThumbOffset = null; _startDragThumbOffset = null;
_currentController = null; _cachedController = null;
} }
void _handleTrackTapDown(TapDownDetails details) { void _handleTrackTapDown(TapDownDetails details) {
// The Scrollbar should page towards the position of the tap on the track. // The Scrollbar should page towards the position of the tap on the track.
assert(_debugCheckHasValidScrollPosition()); assert(_debugCheckHasValidScrollPosition());
_currentController = widget.controller ?? PrimaryScrollController.maybeOf(context); _cachedController = _effectiveScrollController;
final ScrollPosition position = _currentController!.position; final ScrollPosition position = _cachedController!.position;
if (!position.physics.shouldAcceptUserOffset(position)) { if (!position.physics.shouldAcceptUserOffset(position)) {
return; return;
} }
...@@ -1845,22 +1855,22 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1845,22 +1855,22 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
double scrollIncrement; double scrollIncrement;
// Is an increment calculator available? // Is an increment calculator available?
final ScrollIncrementCalculator? calculator = Scrollable.maybeOf( final ScrollIncrementCalculator? calculator = Scrollable.maybeOf(
_currentController!.position.context.notificationContext!, _cachedController!.position.context.notificationContext!,
)?.widget.incrementCalculator; )?.widget.incrementCalculator;
if (calculator != null) { if (calculator != null) {
scrollIncrement = calculator( scrollIncrement = calculator(
ScrollIncrementDetails( ScrollIncrementDetails(
type: ScrollIncrementType.page, type: ScrollIncrementType.page,
metrics: _currentController!.position, metrics: _cachedController!.position,
), ),
); );
} else { } else {
// Default page increment // Default page increment
scrollIncrement = 0.8 * _currentController!.position.viewportDimension; scrollIncrement = 0.8 * _cachedController!.position.viewportDimension;
} }
// Adjust scrollIncrement for direction // Adjust scrollIncrement for direction
switch (_currentController!.position.axisDirection) { switch (_cachedController!.position.axisDirection) {
case AxisDirection.up: case AxisDirection.up:
if (details.localPosition.dy > scrollbarPainter._thumbOffset) { if (details.localPosition.dy > scrollbarPainter._thumbOffset) {
scrollIncrement = -scrollIncrement; scrollIncrement = -scrollIncrement;
...@@ -1883,8 +1893,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1883,8 +1893,8 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
break; break;
} }
_currentController!.position.moveTo( _cachedController!.position.moveTo(
_currentController!.position.pixels + scrollIncrement, _cachedController!.position.pixels + scrollIncrement,
duration: const Duration(milliseconds: 100), duration: const Duration(milliseconds: 100),
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
...@@ -1892,8 +1902,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1892,8 +1902,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
// ScrollController takes precedence over ScrollNotification // ScrollController takes precedence over ScrollNotification
bool _shouldUpdatePainter(Axis notificationAxis) { bool _shouldUpdatePainter(Axis notificationAxis) {
final ScrollController? scrollController = widget.controller ?? final ScrollController? scrollController = _effectiveScrollController;
PrimaryScrollController.maybeOf(context);
// Only update the painter of this scrollbar if the notification // Only update the painter of this scrollbar if the notification
// metrics do not conflict with the information we have from the scroll // metrics do not conflict with the information we have from the scroll
// controller. // controller.
...@@ -1979,8 +1988,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -1979,8 +1988,7 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
Map<Type, GestureRecognizerFactory> get _gestures { Map<Type, GestureRecognizerFactory> get _gestures {
final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{}; final Map<Type, GestureRecognizerFactory> gestures = <Type, GestureRecognizerFactory>{};
final ScrollController? controller = widget.controller ?? PrimaryScrollController.maybeOf(context); if (_effectiveScrollController == null || !enableGestures) {
if (controller == null || !enableGestures) {
return gestures; return gestures;
} }
...@@ -2086,6 +2094,64 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -2086,6 +2094,64 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
_maybeStartFadeoutTimer(); _maybeStartFadeoutTimer();
} }
// Returns the delta that should result from applying [event] with axis and
// direction taken into account.
double _pointerSignalEventDelta(PointerScrollEvent event) {
assert(_cachedController != null);
double delta = _cachedController!.position.axis == Axis.horizontal
? event.scrollDelta.dx
: event.scrollDelta.dy;
if (axisDirectionIsReversed(_cachedController!.position.axisDirection)) {
delta *= -1;
}
return delta;
}
// Returns the offset that should result from applying [event] to the current
// position, taking min/max scroll extent into account.
double _targetScrollOffsetForPointerScroll(double delta) {
assert(_cachedController != null);
return math.min(
math.max(_cachedController!.position.pixels + delta, _cachedController!.position.minScrollExtent),
_cachedController!.position.maxScrollExtent,
);
}
void _handlePointerScroll(PointerEvent event) {
assert(event is PointerScrollEvent);
_cachedController = _effectiveScrollController;
final double delta = _pointerSignalEventDelta(event as PointerScrollEvent);
final double targetScrollOffset = _targetScrollOffsetForPointerScroll(delta);
if (delta != 0.0 && targetScrollOffset != _cachedController!.position.pixels) {
_cachedController!.position.pointerScroll(delta);
}
}
void _receivedPointerSignal(PointerSignalEvent event) {
_cachedController = _effectiveScrollController;
// Only try to scroll if the bar absorb the hit test.
if ((scrollbarPainter.hitTest(event.localPosition) ?? false) &&
_cachedController != null &&
_cachedController!.hasClients &&
(!_thumbDragging || kIsWeb)) {
final ScrollPosition position = _cachedController!.position;
if (event is PointerScrollEvent && position != null) {
if (!position.physics.shouldAcceptUserOffset(position)) {
return;
}
final double delta = _pointerSignalEventDelta(event);
final double targetScrollOffset = _targetScrollOffsetForPointerScroll(delta);
if (delta != 0.0 && targetScrollOffset != position.pixels) {
GestureBinding.instance.pointerSignalResolver.register(event, _handlePointerScroll);
}
} else if (event is PointerScrollInertiaCancelEvent) {
position.jumpTo(position.pixels);
// Don't use the pointer signal resolver, all hit-tested scrollables should stop.
}
}
}
@override @override
void dispose() { void dispose() {
_fadeoutAnimationController.dispose(); _fadeoutAnimationController.dispose();
...@@ -2103,43 +2169,46 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv ...@@ -2103,43 +2169,46 @@ class RawScrollbarState<T extends RawScrollbar> extends State<T> with TickerProv
child: NotificationListener<ScrollNotification>( child: NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification, onNotification: _handleScrollNotification,
child: RepaintBoundary( child: RepaintBoundary(
child: RawGestureDetector( child: Listener(
gestures: _gestures, onPointerSignal: _receivedPointerSignal,
child: MouseRegion( child: RawGestureDetector(
onExit: (PointerExitEvent event) { gestures: _gestures,
switch(event.kind) { child: MouseRegion(
case PointerDeviceKind.mouse: onExit: (PointerExitEvent event) {
case PointerDeviceKind.trackpad: switch(event.kind) {
if (enableGestures) { case PointerDeviceKind.mouse:
handleHoverExit(event); case PointerDeviceKind.trackpad:
} if (enableGestures) {
break; handleHoverExit(event);
case PointerDeviceKind.stylus: }
case PointerDeviceKind.invertedStylus: break;
case PointerDeviceKind.unknown: case PointerDeviceKind.stylus:
case PointerDeviceKind.touch: case PointerDeviceKind.invertedStylus:
break; case PointerDeviceKind.unknown:
} case PointerDeviceKind.touch:
}, break;
onHover: (PointerHoverEvent event) { }
switch(event.kind) { },
case PointerDeviceKind.mouse: onHover: (PointerHoverEvent event) {
case PointerDeviceKind.trackpad: switch(event.kind) {
if (enableGestures) { case PointerDeviceKind.mouse:
handleHover(event); case PointerDeviceKind.trackpad:
} if (enableGestures) {
break; handleHover(event);
case PointerDeviceKind.stylus: }
case PointerDeviceKind.invertedStylus: break;
case PointerDeviceKind.unknown: case PointerDeviceKind.stylus:
case PointerDeviceKind.touch: case PointerDeviceKind.invertedStylus:
break; case PointerDeviceKind.unknown:
} case PointerDeviceKind.touch:
}, break;
child: CustomPaint( }
key: _scrollbarPainterKey, },
foregroundPainter: scrollbarPainter, child: CustomPaint(
child: RepaintBoundary(child: widget.child), key: _scrollbarPainterKey,
foregroundPainter: scrollbarPainter,
child: RepaintBoundary(child: widget.child),
),
), ),
), ),
), ),
......
...@@ -1200,25 +1200,32 @@ void main() { ...@@ -1200,25 +1200,32 @@ void main() {
pointer.hover(const Offset(793.0, 15.0)); pointer.hover(const Offset(793.0, 15.0));
await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 20.0))); await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 20.0)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Scrolling while holding the drag on the scrollbar and still hovered over
// the scrollbar should not have changed the scroll offset. if (!kIsWeb) {
expect(pointer.location, const Offset(793.0, 15.0)); // Scrolling while holding the drag on the scrollbar and still hovered over
expect(scrollController.offset, previousOffset); // the scrollbar should not have changed the scroll offset.
expect( expect(pointer.location, const Offset(793.0, 15.0));
find.byType(CupertinoScrollbar), expect(scrollController.offset, previousOffset);
paints..rrect( expect(
rrect: RRect.fromRectAndRadius( find.byType(CupertinoScrollbar),
const Rect.fromLTRB(789.0, 13.0, 797.0, 102.1), paints..rrect(
const Radius.circular(4.0), rrect: RRect.fromRectAndRadius(
const Rect.fromLTRB(789.0, 13.0, 797.0, 102.1),
const Radius.circular(4.0),
),
color: _kScrollbarColor.color,
), ),
color: _kScrollbarColor.color, );
), } else {
); expect(pointer.location, const Offset(793.0, 15.0));
expect(scrollController.offset, previousOffset + 20.0);
}
// Drag is still being held, move pointer to be hovering over another area // Drag is still being held, move pointer to be hovering over another area
// of the scrollable (not over the scrollbar) and execute another pointer scroll // of the scrollable (not over the scrollbar) and execute another pointer scroll
pointer.hover(tester.getCenter(find.byType(SingleChildScrollView))); pointer.hover(tester.getCenter(find.byType(SingleChildScrollView)));
await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, -70.0))); await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, -90.0)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Scrolling while holding the drag on the scrollbar changed the offset // Scrolling while holding the drag on the scrollbar changed the offset
expect(pointer.location, const Offset(400.0, 300.0)); expect(pointer.location, const Offset(400.0, 300.0));
......
...@@ -1634,33 +1634,39 @@ void main() { ...@@ -1634,33 +1634,39 @@ void main() {
pointer.hover(const Offset(798.0, 15.0)); pointer.hover(const Offset(798.0, 15.0));
await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 20.0))); await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 20.0)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Scrolling while holding the drag on the scrollbar and still hovered over
// the scrollbar should not have changed the scroll offset. if (!kIsWeb) {
expect(pointer.location, const Offset(798.0, 15.0)); // Scrolling while holding the drag on the scrollbar and still hovered over
expect(scrollController.offset, previousOffset); // the scrollbar should not have changed the scroll offset.
expect( expect(pointer.location, const Offset(798.0, 15.0));
find.byType(Scrollbar), expect(scrollController.offset, previousOffset);
paints expect(
..rect( find.byType(Scrollbar),
rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0), paints
color: Colors.transparent, ..rect(
) rect: const Rect.fromLTRB(796.0, 0.0, 800.0, 600.0),
..line( color: Colors.transparent,
p1: const Offset(796.0, 0.0), )
p2: const Offset(796.0, 600.0), ..line(
strokeWidth: 1.0, p1: const Offset(796.0, 0.0),
color: Colors.transparent, p2: const Offset(796.0, 600.0),
) strokeWidth: 1.0,
..rect( color: Colors.transparent,
rect: const Rect.fromLTRB(796.0, 10.0, 800.0, 100.0), )
color: const Color(0x99000000), ..rect(
), rect: const Rect.fromLTRB(796.0, 10.0, 800.0, 100.0),
); color: const Color(0x99000000),
),
);
} else {
expect(pointer.location, const Offset(798.0, 15.0));
expect(scrollController.offset, previousOffset + 20.0);
}
// Drag is still being held, move pointer to be hovering over another area // Drag is still being held, move pointer to be hovering over another area
// of the scrollable (not over the scrollbar) and execute another pointer scroll // of the scrollable (not over the scrollbar) and execute another pointer scroll
pointer.hover(tester.getCenter(find.byType(SingleChildScrollView))); pointer.hover(tester.getCenter(find.byType(SingleChildScrollView)));
await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, -70.0))); await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, -90.0)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Scrolling while holding the drag on the scrollbar changed the offset // Scrolling while holding the drag on the scrollbar changed the offset
expect(pointer.location, const Offset(400.0, 300.0)); expect(pointer.location, const Offset(400.0, 300.0));
......
...@@ -1607,33 +1607,39 @@ void main() { ...@@ -1607,33 +1607,39 @@ void main() {
pointer.hover(const Offset(798.0, 15.0)); pointer.hover(const Offset(798.0, 15.0));
await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 20.0))); await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 20.0)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Scrolling while holding the drag on the scrollbar and still hovered over
// the scrollbar should not have changed the scroll offset. if (!kIsWeb) {
expect(pointer.location, const Offset(798.0, 15.0)); // Scrolling while holding the drag on the scrollbar and still hovered over
expect(scrollController.offset, previousOffset); // the scrollbar should not have changed the scroll offset.
expect( expect(pointer.location, const Offset(798.0, 15.0));
find.byType(RawScrollbar), expect(scrollController.offset, previousOffset);
paints expect(
..rect( find.byType(RawScrollbar),
rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0), paints
color: const Color(0x00000000), ..rect(
) rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0),
..line( color: const Color(0x00000000),
p1: const Offset(794.0, 0.0), )
p2: const Offset(794.0, 600.0), ..line(
strokeWidth: 1.0, p1: const Offset(794.0, 0.0),
color: const Color(0x00000000), p2: const Offset(794.0, 600.0),
) strokeWidth: 1.0,
..rect( color: const Color(0x00000000),
rect: const Rect.fromLTRB(794.0, 10.0, 800.0, 100.0), )
color: const Color(0x66bcbcbc), ..rect(
), rect: const Rect.fromLTRB(794.0, 10.0, 800.0, 100.0),
); color: const Color(0x66bcbcbc),
),
);
} else {
expect(pointer.location, const Offset(798.0, 15.0));
expect(scrollController.offset, previousOffset + 20.0);
}
// Drag is still being held, move pointer to be hovering over another area // Drag is still being held, move pointer to be hovering over another area
// of the scrollable (not over the scrollbar) and execute another pointer scroll // of the scrollable (not over the scrollbar) and execute another pointer scroll
pointer.hover(tester.getCenter(find.byType(SingleChildScrollView))); pointer.hover(tester.getCenter(find.byType(SingleChildScrollView)));
await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, -70.0))); await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, -90.0)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// Scrolling while holding the drag on the scrollbar changed the offset // Scrolling while holding the drag on the scrollbar changed the offset
expect(pointer.location, const Offset(400.0, 300.0)); expect(pointer.location, const Offset(400.0, 300.0));
...@@ -2788,4 +2794,50 @@ void main() { ...@@ -2788,4 +2794,50 @@ void main() {
), ),
); );
}); });
testWidgets('The bar support mouse wheel event', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/pull/109659
final ScrollController scrollController = ScrollController();
Widget buildFrame() {
return Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: PrimaryScrollController(
controller: scrollController,
child: RawScrollbar(
thumbVisibility: true,
controller: scrollController,
child: const SingleChildScrollView(
primary: true,
child: SizedBox(
width: double.infinity,
height: 1200.0,
),
),
),
),
),
);
}
await tester.pumpWidget(buildFrame());
await tester.pumpAndSettle();
expect(scrollController.offset, 0.0);
// Execute a pointer scroll hover on the scroll bar
final TestPointer pointer = TestPointer(1, ui.PointerDeviceKind.mouse);
pointer.hover(const Offset(798.0, 15.0));
await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 30.0)));
await tester.pumpAndSettle();
expect(scrollController.offset, 30.0);
// Execute a pointer scroll outside the scroll bar
pointer.hover(const Offset(198.0, 15.0));
await tester.sendEventToBinding(pointer.scroll(const Offset(0.0, 70.0)));
await tester.pumpAndSettle();
expect(scrollController.offset, 100.0);
}, variant: TargetPlatformVariant.all());
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment