Skip to content

Commit 6ac2653

Browse files
committed
switch to using animation request for scroll inertia dispatch
1 parent c44695a commit 6ac2653

1 file changed

Lines changed: 83 additions & 51 deletions

File tree

src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

Lines changed: 83 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Diagnostics;
3+
using System.Security.Cryptography;
4+
using Avalonia.Media;
35
using Avalonia.Platform;
46
using 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

Comments
 (0)