Skip to content

Commit bf3e4d4

Browse files
emmaussmaxbrousseau
authored andcommitted
Switch to using animation request for scroll inertia dispatch (AvaloniaUI#18997)
* switch to using animation request for scroll inertia dispatch * run inertia calcs in dispatcher operation
1 parent 37fbd96 commit bf3e4d4

1 file changed

Lines changed: 73 additions & 53 deletions

File tree

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

Lines changed: 73 additions & 53 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

@@ -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

Comments
 (0)