11using System ;
22using System . Diagnostics ;
3+ using System . Security . Cryptography ;
4+ using Avalonia . Media ;
35using Avalonia . Platform ;
46using Avalonia . Threading ;
57
@@ -20,19 +22,23 @@ public class ScrollGestureRecognizer : GestureRecognizer
2022 private bool _scrolling ;
2123 private Point _trackedRootPoint ;
2224 private IPointer ? _tracking ;
25+ private Stopwatch ? _stopWatch ;
2326 private int _gestureId ;
2427 private Point _pointerPressedPoint ;
2528 private VelocityTracker ? _velocityTracker ;
2629
2730 // Movement per second
28- private Vector _inertia ;
31+ private Vector ? _inertia ;
2932 private ulong ? _lastMoveTimestamp ;
33+ private TimeSpan _lastTime ;
34+ private TimeSpan _inertiaStartTime ;
35+ private int _currentInertiaGestureId ;
3036
3137 /// <summary>
3238 /// Defines the <see cref="CanHorizontallyScroll"/> property.
3339 /// </summary>
3440 public static readonly DirectProperty < ScrollGestureRecognizer , bool > CanHorizontallyScrollProperty =
35- AvaloniaProperty . RegisterDirect < ScrollGestureRecognizer , bool > ( nameof ( CanHorizontallyScroll ) ,
41+ AvaloniaProperty . RegisterDirect < ScrollGestureRecognizer , bool > ( nameof ( CanHorizontallyScroll ) ,
3642 o => o . CanHorizontallyScroll , ( o , v ) => o . CanHorizontallyScroll = v ) ;
3743
3844 /// <summary>
@@ -47,7 +53,7 @@ public class ScrollGestureRecognizer : GestureRecognizer
4753 /// </summary>
4854 public static readonly DirectProperty < ScrollGestureRecognizer , bool > IsScrollInertiaEnabledProperty =
4955 AvaloniaProperty . RegisterDirect < ScrollGestureRecognizer , bool > ( nameof ( IsScrollInertiaEnabled ) ,
50- o => o . IsScrollInertiaEnabled , ( o , v ) => o . IsScrollInertiaEnabled = v ) ;
56+ o => o . IsScrollInertiaEnabled , ( o , v ) => o . IsScrollInertiaEnabled = v ) ;
5157
5258 /// <summary>
5359 /// Defines the <see cref="ScrollStartDistance"/> property.
@@ -102,6 +108,7 @@ protected override void PointerPressed(PointerPressedEventArgs e)
102108 {
103109 EndGesture ( ) ;
104110 _tracking = e . Pointer ;
111+ _inertia = null ;
105112 _gestureId = ScrollGestureEventArgs . GetNextFreeId ( ) ;
106113 _trackedRootPoint = _pointerPressedPoint = point . Position ;
107114 _velocityTracker = new VelocityTracker ( ) ;
@@ -121,7 +128,7 @@ protected override void PointerMoved(PointerEventArgs e)
121128 if ( CanVerticallyScroll && Math . Abs ( _trackedRootPoint . Y - rootPoint . Y ) > ScrollStartDistance )
122129 _scrolling = true ;
123130 if ( _scrolling )
124- {
131+ {
125132 // Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance
126133 _trackedRootPoint = new Point (
127134 _trackedRootPoint . X - ( _trackedRootPoint . X >= rootPoint . X ? ScrollStartDistance : - ScrollStartDistance ) ,
@@ -147,23 +154,25 @@ protected override void PointerMoved(PointerEventArgs e)
147154
148155 protected override void PointerCaptureLost ( IPointer pointer )
149156 {
150- if ( pointer == _tracking ) EndGesture ( ) ;
157+ if ( pointer == _tracking )
158+ EndGesture ( ) ;
151159 }
152160
153161 void EndGesture ( )
154162 {
155163 _tracking = null ;
156164 if ( _scrolling )
157165 {
166+ _stopWatch ? . Stop ( ) ;
167+ _stopWatch = null ;
158168 _inertia = default ;
159169 _scrolling = false ;
160170 Target ! . RaiseEvent ( new ScrollGestureEndedEventArgs ( _gestureId ) ) ;
161171 _gestureId = 0 ;
162172 _lastMoveTimestamp = null ;
163173 }
164-
165- }
166174
175+ }
167176
168177 protected override void PointerReleased ( PointerReleasedEventArgs e )
169178 {
@@ -172,7 +181,8 @@ protected override void PointerReleased(PointerReleasedEventArgs e)
172181 _inertia = _velocityTracker ? . GetFlingVelocity ( ) . PixelsPerSecond ?? Vector . Zero ;
173182
174183 e . Handled = true ;
175- if ( _inertia == default
184+ if ( _inertia == null
185+ || _inertia == Vector . Zero
176186 || e . Timestamp == 0
177187 || _lastMoveTimestamp == 0
178188 || e . Timestamp - _lastMoveTimestamp > 200
@@ -181,53 +191,63 @@ protected override void PointerReleased(PointerReleasedEventArgs e)
181191 else
182192 {
183193 _tracking = null ;
184- var savedGestureId = _gestureId ;
185- var st = Stopwatch . StartNew ( ) ;
186- var lastTime = TimeSpan . Zero ;
187- Target ! . RaiseEvent ( new ScrollGestureInertiaStartingEventArgs ( _gestureId , _inertia ) ) ;
188- DispatcherTimer . Run ( ( ) =>
189- {
190- // Another gesture has started, finish the current one
191- if ( _gestureId != savedGestureId )
192- {
193- return false ;
194- }
195-
196- var elapsedSinceLastTick = st . Elapsed - lastTime ;
197- lastTime = st . Elapsed ;
198-
199- var speed = _inertia * Math . Pow ( InertialResistance , st . Elapsed . TotalSeconds ) ;
200- var distance = speed * elapsedSinceLastTick . TotalSeconds ;
201- var scrollGestureEventArgs = new ScrollGestureEventArgs ( _gestureId , distance ) ;
202- Target ! . RaiseEvent ( scrollGestureEventArgs ) ;
203-
204- if ( ! scrollGestureEventArgs . Handled || scrollGestureEventArgs . ShouldEndScrollGesture )
205- {
206- EndGesture ( ) ;
207- return false ;
208- }
209-
210- // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
211- if ( CanVerticallyScroll && CanHorizontallyScroll && Math . Abs ( speed . X ) < InertialScrollSpeedEnd && Math . Abs ( speed . Y ) <= InertialScrollSpeedEnd )
212- {
213- EndGesture ( ) ;
214- return false ;
215- }
216- else if ( CanVerticallyScroll && Math . Abs ( speed . Y ) <= InertialScrollSpeedEnd )
217- {
218- EndGesture ( ) ;
219- return false ;
220- }
221- else if ( CanHorizontallyScroll && Math . Abs ( speed . X ) < InertialScrollSpeedEnd )
222- {
223- EndGesture ( ) ;
224- return false ;
225- }
226-
227- return true ;
228- } , TimeSpan . FromMilliseconds ( 16 ) , DispatcherPriority . Background ) ;
194+ _stopWatch = Stopwatch . StartNew ( ) ;
195+ _lastTime = _stopWatch . Elapsed ;
196+ _inertiaStartTime = _lastTime ;
197+ _currentInertiaGestureId = _gestureId ;
198+ Target ! . RaiseEvent ( new ScrollGestureInertiaStartingEventArgs ( _gestureId , _inertia . Value ) ) ;
199+ MediaContext . Instance . RequestAnimationFrame ( OnAnimationRequested ) ;
229200 }
230201 }
231202 }
203+
204+ private void OnAnimationRequested ( TimeSpan _ )
205+ {
206+ // Calculate the current speed and dispatch the next inertia event. This is done asynchronously so we have run the events
207+ // with Input priority
208+ Dispatcher . UIThread . InvokeAsync ( ( ) =>
209+ {
210+ // Another gesture has started, finish the current one
211+ if ( _gestureId != _currentInertiaGestureId || _stopWatch == null || _inertia is not Vector inertia )
212+ {
213+ return ;
214+ }
215+
216+ var timeSpan = _stopWatch . Elapsed ;
217+ var elapsedSinceLastTick = timeSpan - _lastTime ;
218+ _lastTime = timeSpan ;
219+
220+ var speed = inertia * Math . Pow ( InertialResistance , ( _lastTime - _inertiaStartTime ) . TotalSeconds ) ;
221+ var distance = speed * elapsedSinceLastTick . TotalSeconds ;
222+ var scrollGestureEventArgs = new ScrollGestureEventArgs ( _gestureId , distance ) ;
223+ Target ! . RaiseEvent ( scrollGestureEventArgs ) ;
224+
225+ if ( ! scrollGestureEventArgs . Handled || scrollGestureEventArgs . ShouldEndScrollGesture )
226+ {
227+ EndGesture ( ) ;
228+ return ;
229+ }
230+
231+ // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
232+ if ( CanVerticallyScroll && CanHorizontallyScroll && Math . Abs ( speed . X ) < InertialScrollSpeedEnd && Math . Abs ( speed . Y ) <= InertialScrollSpeedEnd )
233+ {
234+ // NO-OP
235+ }
236+ else if ( CanVerticallyScroll && Math . Abs ( speed . Y ) <= InertialScrollSpeedEnd )
237+ {
238+ EndGesture ( ) ;
239+ return ;
240+ }
241+ else if ( CanHorizontallyScroll && Math . Abs ( speed . X ) < InertialScrollSpeedEnd )
242+ {
243+ EndGesture ( ) ;
244+ return ;
245+ }
246+
247+ // Reschedule on the next animation frame. TopLevel.RequestAnimationFrame isn't available on the Base project, so we use the global MediaContext
248+ MediaContext . Instance . RequestAnimationFrame ( OnAnimationRequested ) ;
249+ } , DispatcherPriority . Input ) ;
250+
251+ }
232252 }
233253}
0 commit comments