@ -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_is Holding & & 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_lastHeld Pointer . 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_lastHeld Pointer . 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_is Holding )
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_is DoubleTapped 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_lastHeld Pointer & & 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_is Holding )
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 ;
}
}
}
}
}
}