From 53393e94326c4fe07267f99ec33562fedac05f17 Mon Sep 17 00:00:00 2001 From: Wizzel1 Date: Sat, 14 Jan 2023 13:38:53 +0100 Subject: [PATCH 01/13] Add scrollSpeed field --- lib/src/scroll_state.dart | 4 ++-- lib/src/source.dart | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 4da8130..a9d9d8a 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -14,7 +14,7 @@ class ScrollState with ChangeNotifier { ScrollState(this.mobilePhysics, this.durationMS); - void handleDesktopScroll(PointerSignalEvent event) { + void handleDesktopScroll(PointerSignalEvent event, int scrollSpeed) { // Ensure desktop physics is being used. if (physics == kMobilePhysics) { physics = kDesktopPhysics; @@ -32,7 +32,7 @@ class ScrollState with ChangeNotifier { if (dy > 0) return; } } - futurePosition += event.scrollDelta.dy; + futurePosition += event.scrollDelta.dy * scrollSpeed; controller.animateTo( futurePosition, diff --git a/lib/src/source.dart b/lib/src/source.dart index e49b00e..f98647b 100644 --- a/lib/src/source.dart +++ b/lib/src/source.dart @@ -6,12 +6,14 @@ import 'scroll_state.dart'; class DynMouseScroll extends StatelessWidget { final ScrollPhysics mobilePhysics; final int durationMS; + final int scrollSpeed; final Function(BuildContext, ScrollController, ScrollPhysics) builder; const DynMouseScroll({ super.key, this.mobilePhysics = kMobilePhysics, this.durationMS = 200, + this.scrollSpeed = 1, required this.builder, }); @@ -24,7 +26,8 @@ class DynMouseScroll extends StatelessWidget { final controller = scrollState.controller; final physics = context.select((ScrollState s) => s.physics); return Listener( - onPointerSignal: scrollState.handleDesktopScroll, + onPointerSignal: (signalEvent) => + scrollState.handleDesktopScroll(signalEvent, scrollSpeed), onPointerDown: scrollState.handleTouchScroll, child: builder(context, controller, physics), ); From 5192c01c2f53f335003471dd3f616e7148593e75 Mon Sep 17 00:00:00 2001 From: Wizzel1 Date: Sat, 14 Jan 2023 13:47:23 +0100 Subject: [PATCH 02/13] Add optional animation curve field --- lib/src/scroll_state.dart | 5 +++-- lib/src/source.dart | 6 ++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index a9d9d8a..d15b880 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -14,7 +14,8 @@ class ScrollState with ChangeNotifier { ScrollState(this.mobilePhysics, this.durationMS); - void handleDesktopScroll(PointerSignalEvent event, int scrollSpeed) { + void handleDesktopScroll( + PointerSignalEvent event, int scrollSpeed, Curve animationCurve) { // Ensure desktop physics is being used. if (physics == kMobilePhysics) { physics = kDesktopPhysics; @@ -37,7 +38,7 @@ class ScrollState with ChangeNotifier { controller.animateTo( futurePosition, duration: Duration(milliseconds: durationMS), - curve: Curves.linear, + curve: animationCurve, ); } } diff --git a/lib/src/source.dart b/lib/src/source.dart index f98647b..d6440de 100644 --- a/lib/src/source.dart +++ b/lib/src/source.dart @@ -7,6 +7,7 @@ class DynMouseScroll extends StatelessWidget { final ScrollPhysics mobilePhysics; final int durationMS; final int scrollSpeed; + final Curve animationCurve; final Function(BuildContext, ScrollController, ScrollPhysics) builder; const DynMouseScroll({ @@ -14,6 +15,7 @@ class DynMouseScroll extends StatelessWidget { this.mobilePhysics = kMobilePhysics, this.durationMS = 200, this.scrollSpeed = 1, + this.animationCurve = Curves.linear, required this.builder, }); @@ -26,8 +28,8 @@ class DynMouseScroll extends StatelessWidget { final controller = scrollState.controller; final physics = context.select((ScrollState s) => s.physics); return Listener( - onPointerSignal: (signalEvent) => - scrollState.handleDesktopScroll(signalEvent, scrollSpeed), + onPointerSignal: (signalEvent) => scrollState.handleDesktopScroll( + signalEvent, scrollSpeed, animationCurve), onPointerDown: scrollState.handleTouchScroll, child: builder(context, controller, physics), ); From e00e19afd917533d56c217c1b290912da7b7798c Mon Sep 17 00:00:00 2001 From: Wizzel1 Date: Sat, 14 Jan 2023 13:52:11 +0100 Subject: [PATCH 03/13] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 112b6c5..2f255e1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dyn_mouse_scroll description: A wrapper for scrollable widgets that enables smooth scrolling with a mouse on all platforms. -version: 1.0.7 +version: 1.0.8 repository: https://github.com/Bluebar1/dyn_mouse_scroll environment: From 6e46378f6922048270f5fb75271b863b4839379a Mon Sep 17 00:00:00 2001 From: alesimula Date: Mon, 26 Jun 2023 00:58:54 +0200 Subject: [PATCH 04/13] Implement abrupt scroll direction change --- lib/src/scroll_state.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index d15b880..6941855 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -12,6 +12,9 @@ class ScrollState with ChangeNotifier { final ScrollPhysics mobilePhysics; final int durationMS; + bool prevDeltaPositive = false; + int prevStartingPosition = 0; + ScrollState(this.mobilePhysics, this.durationMS); void handleDesktopScroll( @@ -33,7 +36,13 @@ class ScrollState with ChangeNotifier { if (dy > 0) return; } } - futurePosition += event.scrollDelta.dy * scrollSpeed; + bool currentDeltaPositive = event.scrollDelta.dy > 0; + int newPrevStartingPosition = futurePosition; + if (currentDeltaPositive == prevDeltaPositive) + futurePosition += event.scrollDelta.dy * scrollSpeed; + else futurePosition = prevStartingPosition + event.scrollDelta.dy * scrollSpeed; + prevDeltaPositive = event.scrollDelta.dy > 0; + prevStartingPosition = newPrevStartingPosition; controller.animateTo( futurePosition, From 8404d0513118dac1ae232cafae5cec0584dfc41b Mon Sep 17 00:00:00 2001 From: alesimula Date: Mon, 26 Jun 2023 01:13:20 +0200 Subject: [PATCH 05/13] Fix mistake + animate from current position on direction change --- lib/src/scroll_state.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 6941855..80a0e97 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -13,7 +13,6 @@ class ScrollState with ChangeNotifier { final int durationMS; bool prevDeltaPositive = false; - int prevStartingPosition = 0; ScrollState(this.mobilePhysics, this.durationMS); @@ -37,12 +36,10 @@ class ScrollState with ChangeNotifier { } } bool currentDeltaPositive = event.scrollDelta.dy > 0; - int newPrevStartingPosition = futurePosition; if (currentDeltaPositive == prevDeltaPositive) futurePosition += event.scrollDelta.dy * scrollSpeed; - else futurePosition = prevStartingPosition + event.scrollDelta.dy * scrollSpeed; + else futurePosition = controller.position.pixels + event.scrollDelta.dy * scrollSpeed; prevDeltaPositive = event.scrollDelta.dy > 0; - prevStartingPosition = newPrevStartingPosition; controller.animateTo( futurePosition, From 69281d4818ad8f9c1b3da7d598e72c8a96634932 Mon Sep 17 00:00:00 2001 From: alesimula Date: Tue, 27 Jun 2023 09:02:44 +0200 Subject: [PATCH 06/13] Natural web-like default smooth scrolling --- lib/src/source.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/source.dart b/lib/src/source.dart index d6440de..2343da4 100644 --- a/lib/src/source.dart +++ b/lib/src/source.dart @@ -13,9 +13,9 @@ class DynMouseScroll extends StatelessWidget { const DynMouseScroll({ super.key, this.mobilePhysics = kMobilePhysics, - this.durationMS = 200, - this.scrollSpeed = 1, - this.animationCurve = Curves.linear, + this.durationMS = 380, + this.scrollSpeed = 2, + this.animationCurve = Curves.easeOutQuart, required this.builder, }); From c83efa516845f22dc9ed45d8b115a160b1c78846 Mon Sep 17 00:00:00 2001 From: Alex Sim Date: Thu, 29 Jun 2023 01:35:34 +0200 Subject: [PATCH 07/13] Fix switching between trackpad / mobile and desktop scrolling --- lib/src/scroll_state.dart | 44 ++++++++++++++++++++++++++------------- lib/src/source.dart | 1 + 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 80a0e97..7806450 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -1,12 +1,13 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'dart:math' as math; const kMobilePhysics = BouncingScrollPhysics(); const kDesktopPhysics = NeverScrollableScrollPhysics(); class ScrollState with ChangeNotifier { final ScrollController controller = ScrollController(); - ScrollPhysics physics = kDesktopPhysics; + ScrollPhysics physics = kMobilePhysics; double futurePosition = 0; final ScrollPhysics mobilePhysics; @@ -14,38 +15,51 @@ class ScrollState with ChangeNotifier { bool prevDeltaPositive = false; + Future? _animationEnd; + + Function()? handlePipelinedScroll; + ScrollState(this.mobilePhysics, this.durationMS); + static double calcMaxDelta(ScrollController controller, double delta) { + return delta > 0 ? + math.min(controller.position.pixels + delta, controller.position.maxScrollExtent) - controller.position.pixels : + math.max(controller.position.pixels + delta, controller.position.minScrollExtent) - controller.position.pixels; + } + void handleDesktopScroll( - PointerSignalEvent event, int scrollSpeed, Curve animationCurve) { + PointerSignalEvent event, int scrollSpeed, Curve animationCurve, [bool readLastDirection = true]) { // Ensure desktop physics is being used. if (physics == kMobilePhysics) { physics = kDesktopPhysics; + if (event is PointerScrollEvent) { + bool outOfBounds = controller.position.pixels < controller.position.minScrollExtent || controller.position.pixels > controller.position.maxScrollExtent; + if (!outOfBounds) controller.jumpTo(controller.position.pixels - calcMaxDelta(controller, event.scrollDelta.dy)); + handlePipelinedScroll = () { + handlePipelinedScroll = null; + if (outOfBounds) controller.jumpTo(controller.position.pixels - calcMaxDelta(controller, event.scrollDelta.dy)); + handleDesktopScroll(event, scrollSpeed, animationCurve, false); + }; + } notifyListeners(); return; } if (event is PointerScrollEvent) { - // Return if limit is reached in either direction. - if (controller.position.atEdge) { - final dy = event.scrollDelta.dy; - // Return if bounds exceeded. - if (controller.position.pixels == 0) { - if (dy < 0) return; - } else { - if (dy > 0) return; - } - } bool currentDeltaPositive = event.scrollDelta.dy > 0; - if (currentDeltaPositive == prevDeltaPositive) + if (readLastDirection && currentDeltaPositive == prevDeltaPositive) futurePosition += event.scrollDelta.dy * scrollSpeed; else futurePosition = controller.position.pixels + event.scrollDelta.dy * scrollSpeed; prevDeltaPositive = event.scrollDelta.dy > 0; - - controller.animateTo( + + Future animationEnd = _animationEnd = controller.animateTo( futurePosition, duration: Duration(milliseconds: durationMS), curve: animationCurve, ); + animationEnd.whenComplete(() {if (animationEnd == _animationEnd && physics == kDesktopPhysics) { + physics = mobilePhysics; + notifyListeners(); + }}); } } diff --git a/lib/src/source.dart b/lib/src/source.dart index 2343da4..ba0f8f8 100644 --- a/lib/src/source.dart +++ b/lib/src/source.dart @@ -27,6 +27,7 @@ class DynMouseScroll extends StatelessWidget { final scrollState = context.read(); final controller = scrollState.controller; final physics = context.select((ScrollState s) => s.physics); + scrollState.handlePipelinedScroll?.call(); return Listener( onPointerSignal: (signalEvent) => scrollState.handleDesktopScroll( signalEvent, scrollSpeed, animationCurve), From 23060552c06803e18244927bfeb9e0557e564ac3 Mon Sep 17 00:00:00 2001 From: Alex Sim Date: Thu, 29 Jun 2023 02:15:10 +0200 Subject: [PATCH 08/13] Re-add check for limit reached in either direction --- lib/src/scroll_state.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 7806450..1656fba 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -31,17 +31,20 @@ class ScrollState with ChangeNotifier { PointerSignalEvent event, int scrollSpeed, Curve animationCurve, [bool readLastDirection = true]) { // Ensure desktop physics is being used. if (physics == kMobilePhysics) { - physics = kDesktopPhysics; if (event is PointerScrollEvent) { - bool outOfBounds = controller.position.pixels < controller.position.minScrollExtent || controller.position.pixels > controller.position.maxScrollExtent; - if (!outOfBounds) controller.jumpTo(controller.position.pixels - calcMaxDelta(controller, event.scrollDelta.dy)); + double posPixels = controller.position.pixels; + if ((posPixels == controller.position.minScrollExtent && event.scrollDelta.dy < 0) + || (posPixels == controller.position.maxScrollExtent && event.scrollDelta.dy > 0)) return; + else physics = kDesktopPhysics; + bool outOfBounds = posPixels < controller.position.minScrollExtent || posPixels > controller.position.maxScrollExtent; + if (!outOfBounds) controller.jumpTo(posPixels - calcMaxDelta(controller, event.scrollDelta.dy)); handlePipelinedScroll = () { handlePipelinedScroll = null; if (outOfBounds) controller.jumpTo(controller.position.pixels - calcMaxDelta(controller, event.scrollDelta.dy)); handleDesktopScroll(event, scrollSpeed, animationCurve, false); }; + notifyListeners(); } - notifyListeners(); return; } if (event is PointerScrollEvent) { From 063a5a522f14708458384a24494afe87b27630cf Mon Sep 17 00:00:00 2001 From: Alex Sim Date: Fri, 30 Jun 2023 00:02:15 +0200 Subject: [PATCH 09/13] Fix locking when holding or hovering scrollbar --- lib/src/scroll_state.dart | 35 ++++++++++++++++++++++++++++++----- lib/src/source.dart | 1 + 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 1656fba..17fea97 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -9,11 +9,13 @@ class ScrollState with ChangeNotifier { final ScrollController controller = ScrollController(); ScrollPhysics physics = kMobilePhysics; double futurePosition = 0; + bool updateState = false; final ScrollPhysics mobilePhysics; final int durationMS; bool prevDeltaPositive = false; + double? lastLock = null; Future? _animationEnd; @@ -30,24 +32,47 @@ class ScrollState with ChangeNotifier { void handleDesktopScroll( PointerSignalEvent event, int scrollSpeed, Curve animationCurve, [bool readLastDirection = true]) { // Ensure desktop physics is being used. - if (physics == kMobilePhysics) { + if (physics == kMobilePhysics || lastLock != null) { + if (lastLock != null) updateState = !updateState; if (event is PointerScrollEvent) { double posPixels = controller.position.pixels; if ((posPixels == controller.position.minScrollExtent && event.scrollDelta.dy < 0) || (posPixels == controller.position.maxScrollExtent && event.scrollDelta.dy > 0)) return; else physics = kDesktopPhysics; bool outOfBounds = posPixels < controller.position.minScrollExtent || posPixels > controller.position.maxScrollExtent; - if (!outOfBounds) controller.jumpTo(posPixels - calcMaxDelta(controller, event.scrollDelta.dy)); + double calcDelta = calcMaxDelta(controller, event.scrollDelta.dy); + if (!outOfBounds) controller.jumpTo(lastLock ?? (posPixels - calcDelta)); + double deltaDelta = calcDelta - event.scrollDelta.dy; handlePipelinedScroll = () { handlePipelinedScroll = null; - if (outOfBounds) controller.jumpTo(controller.position.pixels - calcMaxDelta(controller, event.scrollDelta.dy)); - handleDesktopScroll(event, scrollSpeed, animationCurve, false); + double currPos = controller.position.pixels; + double currDelta = event.scrollDelta.dy; + bool shouldLock = lastLock != null ? (lastLock == currPos) : (posPixels != currPos + deltaDelta && + (currPos != controller.position.maxScrollExtent || currDelta < 0) && + (currPos != controller.position.minScrollExtent || currDelta > 0)); + //bool shouldLock = lastLock != null ? (lastLock == currPos) : (currPos != posPixels); + if (!outOfBounds && shouldLock) { + print("SHOULDLOCK"); + controller.jumpTo(posPixels); + lastLock = posPixels; + controller.position.moveTo(posPixels)..whenComplete(() { + physics = kMobilePhysics; + notifyListeners(); + }); + return; + } + else { + if (lastLock != null || outOfBounds) + controller.jumpTo(lastLock ?? (currPos - calcMaxDelta(controller, currDelta))); + lastLock = null; + handleDesktopScroll(event, scrollSpeed, animationCurve, false); + } }; notifyListeners(); } return; } - if (event is PointerScrollEvent) { + else if (event is PointerScrollEvent) { bool currentDeltaPositive = event.scrollDelta.dy > 0; if (readLastDirection && currentDeltaPositive == prevDeltaPositive) futurePosition += event.scrollDelta.dy * scrollSpeed; diff --git a/lib/src/source.dart b/lib/src/source.dart index ba0f8f8..44082b0 100644 --- a/lib/src/source.dart +++ b/lib/src/source.dart @@ -27,6 +27,7 @@ class DynMouseScroll extends StatelessWidget { final scrollState = context.read(); final controller = scrollState.controller; final physics = context.select((ScrollState s) => s.physics); + final updateState = context.select((ScrollState s) => s.updateState); scrollState.handlePipelinedScroll?.call(); return Listener( onPointerSignal: (signalEvent) => scrollState.handleDesktopScroll( From 9caafea89f221a5b425f819184c11b5e5a16f0f4 Mon Sep 17 00:00:00 2001 From: Alex Sim Date: Fri, 30 Jun 2023 00:10:12 +0200 Subject: [PATCH 10/13] Remove log committed accidentally --- lib/src/scroll_state.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 17fea97..0b58fce 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -52,7 +52,6 @@ class ScrollState with ChangeNotifier { (currPos != controller.position.minScrollExtent || currDelta > 0)); //bool shouldLock = lastLock != null ? (lastLock == currPos) : (currPos != posPixels); if (!outOfBounds && shouldLock) { - print("SHOULDLOCK"); controller.jumpTo(posPixels); lastLock = posPixels; controller.position.moveTo(posPixels)..whenComplete(() { From 7c82adbb34f0117051ac8039fb595bea9f5a2765 Mon Sep 17 00:00:00 2001 From: Alex Sim Date: Fri, 30 Jun 2023 00:28:08 +0200 Subject: [PATCH 11/13] Fix small bug [FIX] If you scrolled while hovering the cursor on the scrollbar (scroll locked), then drag-n-drop the scrollbar somewhere else, then scroll with the mouse wheel, the scrollview would jump to the last locked location. --- lib/src/scroll_state.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 0b58fce..228ada2 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -62,7 +62,7 @@ class ScrollState with ChangeNotifier { } else { if (lastLock != null || outOfBounds) - controller.jumpTo(lastLock ?? (currPos - calcMaxDelta(controller, currDelta))); + controller.jumpTo(lastLock != null ? posPixels : (currPos - calcMaxDelta(controller, currDelta))); lastLock = null; handleDesktopScroll(event, scrollSpeed, animationCurve, false); } From f1f690bd3588ee63ec05881cfc8ca2716fd1d596 Mon Sep 17 00:00:00 2001 From: Alex Sim Date: Fri, 30 Jun 2023 13:37:31 +0200 Subject: [PATCH 12/13] Remove commented line --- lib/src/scroll_state.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 228ada2..5d001c6 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -50,7 +50,6 @@ class ScrollState with ChangeNotifier { bool shouldLock = lastLock != null ? (lastLock == currPos) : (posPixels != currPos + deltaDelta && (currPos != controller.position.maxScrollExtent || currDelta < 0) && (currPos != controller.position.minScrollExtent || currDelta > 0)); - //bool shouldLock = lastLock != null ? (lastLock == currPos) : (currPos != posPixels); if (!outOfBounds && shouldLock) { controller.jumpTo(posPixels); lastLock = posPixels; From a749dcc7efa6fb172959b25c9ff194e1cbd59739 Mon Sep 17 00:00:00 2001 From: Alex Sim Date: Sun, 9 Jul 2023 19:03:02 +0200 Subject: [PATCH 13/13] Allow decimals for scrollSpeed --- lib/src/scroll_state.dart | 2 +- lib/src/source.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/scroll_state.dart b/lib/src/scroll_state.dart index 5d001c6..60f4b28 100644 --- a/lib/src/scroll_state.dart +++ b/lib/src/scroll_state.dart @@ -30,7 +30,7 @@ class ScrollState with ChangeNotifier { } void handleDesktopScroll( - PointerSignalEvent event, int scrollSpeed, Curve animationCurve, [bool readLastDirection = true]) { + PointerSignalEvent event, double scrollSpeed, Curve animationCurve, [bool readLastDirection = true]) { // Ensure desktop physics is being used. if (physics == kMobilePhysics || lastLock != null) { if (lastLock != null) updateState = !updateState; diff --git a/lib/src/source.dart b/lib/src/source.dart index 44082b0..2fc054f 100644 --- a/lib/src/source.dart +++ b/lib/src/source.dart @@ -6,7 +6,7 @@ import 'scroll_state.dart'; class DynMouseScroll extends StatelessWidget { final ScrollPhysics mobilePhysics; final int durationMS; - final int scrollSpeed; + final double scrollSpeed; final Curve animationCurve; final Function(BuildContext, ScrollController, ScrollPhysics) builder;