|
|
|
@ -1,5 +1,7 @@ |
|
|
|
using System; |
|
|
|
using System.Diagnostics; |
|
|
|
using System.Security.Cryptography; |
|
|
|
using Avalonia.Media; |
|
|
|
using Avalonia.Platform; |
|
|
|
using Avalonia.Threading; |
|
|
|
|
|
|
|
@ -20,19 +22,23 @@ namespace Avalonia.Input.GestureRecognizers |
|
|
|
private bool _scrolling; |
|
|
|
private Point _trackedRootPoint; |
|
|
|
private IPointer? _tracking; |
|
|
|
private Stopwatch? _stopWatch; |
|
|
|
private int _gestureId; |
|
|
|
private Point _pointerPressedPoint; |
|
|
|
private VelocityTracker? _velocityTracker; |
|
|
|
|
|
|
|
// Movement per second
|
|
|
|
private Vector _inertia; |
|
|
|
private Vector? _inertia; |
|
|
|
private ulong? _lastMoveTimestamp; |
|
|
|
private TimeSpan _lastTime; |
|
|
|
private TimeSpan _inertiaStartTime; |
|
|
|
private int _currentInertiaGestureId; |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="CanHorizontallyScroll"/> property.
|
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<ScrollGestureRecognizer, bool> CanHorizontallyScrollProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>(nameof(CanHorizontallyScroll), |
|
|
|
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>(nameof(CanHorizontallyScroll), |
|
|
|
o => o.CanHorizontallyScroll, (o, v) => o.CanHorizontallyScroll = v); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
@ -47,7 +53,7 @@ namespace Avalonia.Input.GestureRecognizers |
|
|
|
/// </summary>
|
|
|
|
public static readonly DirectProperty<ScrollGestureRecognizer, bool> IsScrollInertiaEnabledProperty = |
|
|
|
AvaloniaProperty.RegisterDirect<ScrollGestureRecognizer, bool>(nameof(IsScrollInertiaEnabled), |
|
|
|
o => o.IsScrollInertiaEnabled, (o,v) => o.IsScrollInertiaEnabled = v); |
|
|
|
o => o.IsScrollInertiaEnabled, (o, v) => o.IsScrollInertiaEnabled = v); |
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Defines the <see cref="ScrollStartDistance"/> property.
|
|
|
|
@ -102,6 +108,7 @@ namespace Avalonia.Input.GestureRecognizers |
|
|
|
{ |
|
|
|
EndGesture(); |
|
|
|
_tracking = e.Pointer; |
|
|
|
_inertia = null; |
|
|
|
_gestureId = ScrollGestureEventArgs.GetNextFreeId(); |
|
|
|
_trackedRootPoint = _pointerPressedPoint = point.Position; |
|
|
|
_velocityTracker = new VelocityTracker(); |
|
|
|
@ -121,7 +128,7 @@ namespace Avalonia.Input.GestureRecognizers |
|
|
|
if (CanVerticallyScroll && Math.Abs(_trackedRootPoint.Y - rootPoint.Y) > ScrollStartDistance) |
|
|
|
_scrolling = true; |
|
|
|
if (_scrolling) |
|
|
|
{ |
|
|
|
{ |
|
|
|
// Correct _trackedRootPoint with ScrollStartDistance, so scrolling does not start with a skip of ScrollStartDistance
|
|
|
|
_trackedRootPoint = new Point( |
|
|
|
_trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance), |
|
|
|
@ -147,7 +154,8 @@ namespace Avalonia.Input.GestureRecognizers |
|
|
|
|
|
|
|
protected override void PointerCaptureLost(IPointer pointer) |
|
|
|
{ |
|
|
|
if (pointer == _tracking) EndGesture(); |
|
|
|
if (pointer == _tracking) |
|
|
|
EndGesture(); |
|
|
|
} |
|
|
|
|
|
|
|
void EndGesture() |
|
|
|
@ -155,15 +163,16 @@ namespace Avalonia.Input.GestureRecognizers |
|
|
|
_tracking = null; |
|
|
|
if (_scrolling) |
|
|
|
{ |
|
|
|
_stopWatch?.Stop(); |
|
|
|
_stopWatch = null; |
|
|
|
_inertia = default; |
|
|
|
_scrolling = false; |
|
|
|
Target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); |
|
|
|
_gestureId = 0; |
|
|
|
_lastMoveTimestamp = null; |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
protected override void PointerReleased(PointerReleasedEventArgs e) |
|
|
|
{ |
|
|
|
@ -172,7 +181,8 @@ namespace Avalonia.Input.GestureRecognizers |
|
|
|
_inertia = _velocityTracker?.GetFlingVelocity().PixelsPerSecond ?? Vector.Zero; |
|
|
|
|
|
|
|
e.Handled = true; |
|
|
|
if (_inertia == default |
|
|
|
if (_inertia == null |
|
|
|
|| _inertia == Vector.Zero |
|
|
|
|| e.Timestamp == 0 |
|
|
|
|| _lastMoveTimestamp == 0 |
|
|
|
|| e.Timestamp - _lastMoveTimestamp > 200 |
|
|
|
@ -181,53 +191,63 @@ namespace Avalonia.Input.GestureRecognizers |
|
|
|
else |
|
|
|
{ |
|
|
|
_tracking = null; |
|
|
|
var savedGestureId = _gestureId; |
|
|
|
var st = Stopwatch.StartNew(); |
|
|
|
var lastTime = TimeSpan.Zero; |
|
|
|
Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia)); |
|
|
|
DispatcherTimer.Run(() => |
|
|
|
{ |
|
|
|
// Another gesture has started, finish the current one
|
|
|
|
if (_gestureId != savedGestureId) |
|
|
|
{ |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
var elapsedSinceLastTick = st.Elapsed - lastTime; |
|
|
|
lastTime = st.Elapsed; |
|
|
|
|
|
|
|
var speed = _inertia * Math.Pow(InertialResistance, st.Elapsed.TotalSeconds); |
|
|
|
var distance = speed * elapsedSinceLastTick.TotalSeconds; |
|
|
|
var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance); |
|
|
|
Target!.RaiseEvent(scrollGestureEventArgs); |
|
|
|
|
|
|
|
if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture) |
|
|
|
{ |
|
|
|
EndGesture(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
// EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
|
|
|
|
if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) |
|
|
|
{ |
|
|
|
EndGesture(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) |
|
|
|
{ |
|
|
|
EndGesture(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd) |
|
|
|
{ |
|
|
|
EndGesture(); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
}, TimeSpan.FromMilliseconds(16), DispatcherPriority.Background); |
|
|
|
_stopWatch = Stopwatch.StartNew(); |
|
|
|
_lastTime = _stopWatch.Elapsed; |
|
|
|
_inertiaStartTime = _lastTime; |
|
|
|
_currentInertiaGestureId = _gestureId; |
|
|
|
Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia.Value)); |
|
|
|
MediaContext.Instance.RequestAnimationFrame(OnAnimationRequested); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void OnAnimationRequested(TimeSpan _) |
|
|
|
{ |
|
|
|
// Calculate the current speed and dispatch the next inertia event. This is done asynchronously so we have run the events
|
|
|
|
// with Input priority
|
|
|
|
Dispatcher.UIThread.InvokeAsync(() => |
|
|
|
{ |
|
|
|
// Another gesture has started, finish the current one
|
|
|
|
if (_gestureId != _currentInertiaGestureId || _stopWatch == null || _inertia is not Vector inertia) |
|
|
|
{ |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
var timeSpan = _stopWatch.Elapsed; |
|
|
|
var elapsedSinceLastTick = timeSpan - _lastTime; |
|
|
|
_lastTime = timeSpan; |
|
|
|
|
|
|
|
var speed = inertia * Math.Pow(InertialResistance, (_lastTime - _inertiaStartTime).TotalSeconds); |
|
|
|
var distance = speed * elapsedSinceLastTick.TotalSeconds; |
|
|
|
var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance); |
|
|
|
Target!.RaiseEvent(scrollGestureEventArgs); |
|
|
|
|
|
|
|
if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture) |
|
|
|
{ |
|
|
|
EndGesture(); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// EndGesture using InertialScrollSpeedEnd only in the direction of scrolling
|
|
|
|
if (CanVerticallyScroll && CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) |
|
|
|
{ |
|
|
|
// NO-OP
|
|
|
|
} |
|
|
|
else if (CanVerticallyScroll && Math.Abs(speed.Y) <= InertialScrollSpeedEnd) |
|
|
|
{ |
|
|
|
EndGesture(); |
|
|
|
return; |
|
|
|
} |
|
|
|
else if (CanHorizontallyScroll && Math.Abs(speed.X) < InertialScrollSpeedEnd) |
|
|
|
{ |
|
|
|
EndGesture(); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Reschedule on the next animation frame. TopLevel.RequestAnimationFrame isn't available on the Base project, so we use the global MediaContext
|
|
|
|
MediaContext.Instance.RequestAnimationFrame(OnAnimationRequested); |
|
|
|
}, DispatcherPriority.Input); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|