Browse Source

Avoid NullReferenceException in Gestures.PointerReleased (#15117)

* refs #14249 Avoid NullReferenceException in Gestures.PointerReleased

* refs #14249 Track current gesture state with single readonly struct

* Update from review

* Moved the last click location from the GestureState record to a separate field. (#14249)

---------

Co-authored-by: Заболотнев Юрий <zabolotnev@promit-ek.ru>
pull/15258/head
zabolotnev 2 years ago
committed by GitHub
parent
commit
04c62859f5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 58
      src/Avalonia.Base/Input/Gestures.cs

58
src/Avalonia.Base/Input/Gestures.cs

@ -10,8 +10,17 @@ namespace Avalonia.Input
{ {
public static class Gestures public static class Gestures
{ {
private static bool s_isDoubleTapped = false; private record struct GestureState(GestureStateType Type, IPointer Pointer);
private static bool s_isHolding; private enum GestureStateType
{
Pending,
Holding,
DoubleTapped
}
private static GestureState? s_gestureState = null;
private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null);
private static Point s_lastPressPoint;
private static CancellationTokenSource? s_holdCancellationToken; private static CancellationTokenSource? s_holdCancellationToken;
/// <summary> /// <summary>
@ -65,10 +74,6 @@ namespace Avalonia.Input
RoutedEvent.Register<PointerDeltaEventArgs>( RoutedEvent.Register<PointerDeltaEventArgs>(
"PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures)); "PointerSwipeGesture", RoutingStrategies.Bubble, typeof(Gestures));
private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null);
private static Point s_lastPressPoint;
private static IPointer? s_lastHeldPointer;
public static readonly RoutedEvent<PinchEventArgs> PinchEvent = public static readonly RoutedEvent<PinchEventArgs> PinchEvent =
RoutedEvent.Register<PinchEventArgs>( RoutedEvent.Register<PinchEventArgs>(
"PinchEvent", RoutingStrategies.Bubble, typeof(Gestures)); "PinchEvent", RoutingStrategies.Bubble, typeof(Gestures));
@ -225,26 +230,23 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev; var e = (PointerPressedEventArgs)ev;
var visual = (Visual)ev.Source; var visual = (Visual)ev.Source;
if(s_lastHeldPointer != null) if(s_gestureState != null)
{ {
if(s_isHolding && ev.Source is Interactive i) if(s_gestureState.Value.Type == GestureStateType.Holding && ev.Source is Interactive i)
{ {
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastHeldPointer.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
} }
s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose(); s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null; s_holdCancellationToken = null;
s_lastHeldPointer = null; s_gestureState = null;
} }
s_isHolding = false;
if (e.ClickCount % 2 == 1) if (e.ClickCount % 2 == 1)
{ {
s_isDoubleTapped = false; s_gestureState = new GestureState(GestureStateType.Pending, e.Pointer);
s_lastPress.SetTarget(ev.Source); s_lastPress.SetTarget(ev.Source);
s_lastHeldPointer = e.Pointer;
s_lastPressPoint = e.GetPosition((Visual)ev.Source); s_lastPressPoint = e.GetPosition((Visual)ev.Source);
s_holdCancellationToken = new CancellationTokenSource(); s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token; var token = s_holdCancellationToken.Token;
@ -254,10 +256,10 @@ namespace Avalonia.Input
{ {
DispatcherTimer.RunOnce(() => DispatcherTimer.RunOnce(() =>
{ {
if (!token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i))) if (s_gestureState != null && !token.IsCancellationRequested && e.Source is InputElement i && GetIsHoldingEnabled(i) && (e.Pointer.Type != PointerType.Mouse || GetIsHoldWithMouseEnabled(i)))
{ {
s_isHolding = true; s_gestureState = new GestureState(GestureStateType.Holding, s_gestureState.Value.Pointer);
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_lastHeldPointer.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
} }
}, settings.HoldWaitDuration); }, settings.HoldWaitDuration);
} }
@ -268,7 +270,7 @@ namespace Avalonia.Input
target == e.Source && target == e.Source &&
e.Source is Interactive i) e.Source is Interactive i)
{ {
s_isDoubleTapped = true; s_gestureState = new GestureState(GestureStateType.DoubleTapped, e.Pointer);
i.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e)); i.RaiseEvent(new TappedEventArgs(DoubleTappedEvent, e));
} }
} }
@ -294,23 +296,22 @@ namespace Avalonia.Input
if (tapRect.ContainsExclusive(point.Position)) if (tapRect.ContainsExclusive(point.Position))
{ {
if (s_isHolding) if (s_gestureState?.Type == GestureStateType.Holding)
{ {
s_isHolding = false; i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed, s_lastPressPoint, s_lastHeldPointer!.Type, e));
} }
else if (e.InitialPressMouseButton == MouseButton.Right) else if (e.InitialPressMouseButton == MouseButton.Right)
{ {
i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e)); i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
} }
//s_isDoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called. //GestureStateType.DoubleTapped needed here to prevent invoking Tapped event when DoubleTapped is called.
//This behaviour matches UWP behaviour. //This behaviour matches UWP behaviour.
else if (s_isDoubleTapped == false) else if (s_gestureState?.Type != GestureStateType.DoubleTapped)
{ {
i.RaiseEvent(new TappedEventArgs(TappedEvent, e)); i.RaiseEvent(new TappedEventArgs(TappedEvent, e));
} }
} }
s_lastHeldPointer = null; s_gestureState = null;
} }
s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Cancel();
@ -326,7 +327,7 @@ namespace Avalonia.Input
var e = (PointerEventArgs)ev; var e = (PointerEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target)) if (s_lastPress.TryGetTarget(out var target))
{ {
if (e.Pointer == s_lastHeldPointer && ev.Source is Interactive i) if (e.Pointer == s_gestureState?.Pointer && ev.Source is Interactive i)
{ {
var point = e.GetCurrentPoint((Visual)target); var point = e.GetCurrentPoint((Visual)target);
var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings; var settings = ((IInputRoot?)i.GetVisualRoot())?.PlatformSettings;
@ -339,10 +340,9 @@ namespace Avalonia.Input
return; return;
} }
if (s_isHolding) if (s_gestureState.Value.Type == GestureStateType.Holding)
{ {
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_lastHeldPointer!.Type, e)); i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled, s_lastPressPoint, s_gestureState.Value.Pointer.Type, e));
s_lastHeldPointer = null;
} }
} }
} }
@ -350,7 +350,7 @@ namespace Avalonia.Input
s_holdCancellationToken?.Cancel(); s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose(); s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null; s_holdCancellationToken = null;
s_isHolding = false; s_gestureState = null;
} }
} }
} }

Loading…
Cancel
Save