11using System ;
22using System . Diagnostics ;
3+ using System . Security . Cryptography ;
4+ using Avalonia . Media ;
35using Avalonia . Platform ;
46using Avalonia . Threading ;
57
@@ -25,14 +27,18 @@ public class ScrollGestureRecognizer : GestureRecognizer
2527 private VelocityTracker ? _velocityTracker ;
2628
2729 // Movement per second
28- private Vector _inertia ;
30+ private Vector ? _inertia ;
2931 private ulong ? _lastMoveTimestamp ;
32+ private TimeSpan _lastTime ;
33+ private TimeSpan _inertiaStartTime ;
34+ private int _currentInertiaGestureId ;
35+ private DispatcherOperation ? _inertiaDispatchOperation ;
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,7 +154,8 @@ 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 ( )
@@ -161,7 +169,9 @@ void EndGesture()
161169 _gestureId = 0 ;
162170 _lastMoveTimestamp = null ;
163171 }
164-
172+
173+ _inertiaDispatchOperation = null ;
174+
165175 }
166176
167177
@@ -181,53 +191,75 @@ 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+
195+ MediaContext . Instance . RequestAnimationFrame ( StartInertia ) ;
229196 }
230197 }
231198 }
199+
200+ private void StartInertia ( TimeSpan timeSpan )
201+ {
202+ if ( _inertia is { } inertia )
203+ {
204+ _lastTime = timeSpan ;
205+ _inertiaStartTime = timeSpan ;
206+ _currentInertiaGestureId = _gestureId ;
207+ Target ! . RaiseEvent ( new ScrollGestureInertiaStartingEventArgs ( _gestureId , inertia ) ) ;
208+
209+ MediaContext . Instance . RequestAnimationFrame ( OnAnimationRequested ) ;
210+ }
211+ }
212+
213+ private void OnAnimationRequested ( TimeSpan timeSpan )
214+ {
215+ // Another gesture has started, finish the current one
216+ if ( _gestureId != _currentInertiaGestureId || _inertia == null )
217+ {
218+ _inertia = null ;
219+ return ;
220+ }
221+
222+ var elapsedSinceLastTick = timeSpan - _lastTime ;
223+ _lastTime = timeSpan ;
224+
225+ var speed = _inertia * Math . Pow ( InertialResistance , ( _lastTime - _inertiaStartTime ) . TotalSeconds ) ;
226+ var distance = speed * elapsedSinceLastTick . TotalSeconds ;
227+ _inertiaDispatchOperation = Dispatcher . UIThread . InvokeAsync ( ( ) =>
228+ {
229+ if ( speed is not { } && distance is not { } )
230+ {
231+ EndGesture ( ) ;
232+ return ;
233+ }
234+ var scrollGestureEventArgs = new ScrollGestureEventArgs ( _gestureId , distance ! . Value ) ;
235+ Target ! . RaiseEvent ( scrollGestureEventArgs ) ;
236+
237+ if ( ! scrollGestureEventArgs . Handled || scrollGestureEventArgs . ShouldEndScrollGesture )
238+ {
239+ EndGesture ( ) ;
240+ return ;
241+ }
242+
243+ var sp = speed ! . Value ;
244+
245+ // EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
246+ if ( CanVerticallyScroll && CanHorizontallyScroll && Math . Abs ( sp . X ) < InertialScrollSpeedEnd && Math . Abs ( sp . Y ) <= InertialScrollSpeedEnd )
247+ {
248+ return ;
249+ }
250+ else if ( CanVerticallyScroll && Math . Abs ( sp . Y ) <= InertialScrollSpeedEnd )
251+ {
252+ EndGesture ( ) ;
253+ return ;
254+ }
255+ else if ( CanHorizontallyScroll && Math . Abs ( sp . X ) < InertialScrollSpeedEnd )
256+ {
257+ EndGesture ( ) ;
258+ return ;
259+ }
260+ } , DispatcherPriority . Input ) ;
261+
262+ MediaContext . Instance . RequestAnimationFrame ( OnAnimationRequested ) ;
263+ }
232264 }
233265}
0 commit comments