From d4da441e53f51ae7a15f0b564fbaba56d60f4673 Mon Sep 17 00:00:00 2001 From: Kate Lovett Date: Mon, 21 Dec 2020 18:24:03 -0600 Subject: [PATCH] Apply physics boundary to scrollbar dragging (#72741) --- .../flutter/lib/src/widgets/scrollbar.dart | 12 +++-- .../flutter/test/widgets/scrollbar_test.dart | 52 +++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart index 55637cb549..98d9334375 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -20,6 +20,7 @@ import 'primary_scroll_controller.dart'; import 'scroll_controller.dart'; import 'scroll_metrics.dart'; import 'scroll_notification.dart'; +import 'scroll_position.dart'; import 'scrollable.dart'; import 'ticker_provider.dart'; @@ -879,13 +880,18 @@ class RawScrollbarState extends State with TickerProv void _updateScrollPosition(double primaryDelta) { assert(_currentController != null); + final ScrollPosition position = _currentController!.position; // Convert primaryDelta, the amount that the scrollbar moved since the last - // time _dragScrollbar was called, into the coordinate space of the scroll + // time _updateScrollPosition was called, into the coordinate space of the scroll // position, and jump to that position. final double scrollOffsetLocal = scrollbarPainter.getTrackToScroll(primaryDelta); - final double scrollOffsetGlobal = scrollOffsetLocal + _currentController!.position.pixels; - _currentController!.position.jumpTo(scrollOffsetGlobal); + final double scrollOffsetGlobal = scrollOffsetLocal + position.pixels; + if (scrollOffsetGlobal != position.pixels) { + // Ensure we don't drag into overscroll if the physics do not allow it. + final double physicsAdjustment = position.physics.applyBoundaryConditions(position, scrollOffsetGlobal); + position.jumpTo(scrollOffsetGlobal - physicsAdjustment); + } } void _maybeStartFadeoutTimer() { diff --git a/packages/flutter/test/widgets/scrollbar_test.dart b/packages/flutter/test/widgets/scrollbar_test.dart index 4a6551767d..0b33c60f35 100644 --- a/packages/flutter/test/widgets/scrollbar_test.dart +++ b/packages/flutter/test/widgets/scrollbar_test.dart @@ -752,4 +752,56 @@ void main() { ), ); }); + + testWidgets('Scrollbar thumb cannot be dragged into overscroll if the physics do not allow', (WidgetTester tester) async { + final ScrollController scrollController = ScrollController(); + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData(), + child: PrimaryScrollController( + controller: scrollController, + child: RawScrollbar( + isAlwaysShown: true, + controller: scrollController, + child: const SingleChildScrollView( + child: SizedBox(width: 4000.0, height: 4000.0) + ), + ), + ), + ), + ), + ); + await tester.pumpAndSettle(); + expect(scrollController.offset, 0.0); + expect( + find.byType(RawScrollbar), + paints + ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0)) + ..rect( + rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 90.0), + color: const Color(0x66BCBCBC), + ), + ); + + // Try to drag the thumb into overscroll. + const double scrollAmount = -10.0; + final TestGesture dragScrollbarGesture = await tester.startGesture(const Offset(797.0, 45.0)); + await tester.pumpAndSettle(); + await dragScrollbarGesture.moveBy(const Offset(0.0, scrollAmount)); + await tester.pumpAndSettle(); + + // The physics should not have allowed us to enter overscroll. + expect(scrollController.offset, 0.0); + expect( + find.byType(RawScrollbar), + paints + ..rect(rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 600.0)) + ..rect( + rect: const Rect.fromLTRB(794.0, 0.0, 800.0, 90.0), + color: const Color(0x66BCBCBC), + ), + ); + }); }