Browse Source

drop hold gesture recognizer

pull/9652/head
Emmanuel Hansen 3 years ago
parent
commit
8945e1b77c
  1. 124
      src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs
  2. 93
      src/Avalonia.Base/Input/Gestures.cs
  3. 31
      src/Avalonia.Base/Input/HoldGestureEventArgs.cs
  4. 22
      src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs
  5. 46
      src/Avalonia.Base/Input/InputElement.cs
  6. 18
      src/Avalonia.Controls/Control.cs

124
src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs

@ -1,124 +0,0 @@
using System.Timers;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Threading;
namespace Avalonia.Input
{
public class HoldGestureRecognizer : StyledElement, IGestureRecognizer
{
private const int Tolerance = 30;
private IInputElement? _target;
private IGestureRecognizerActionsDispatcher? _actions;
private int _gestureId;
private IPointer? _tracking;
private PointerPressedEventArgs? _pointerEventArgs;
private Rect _trackingBounds;
private Timer? _holdTimer;
private bool _elasped;
/// <summary>
/// Defines the <see cref="IsHoldWithMouseEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsHoldWithMouseEnabledProperty =
AvaloniaProperty.Register<HoldGestureRecognizer, bool>(
nameof(IsHoldWithMouseEnabled));
/// <summary>
/// Gets or sets whether to detect hold from the mouse
/// </summary>
public bool IsHoldWithMouseEnabled
{
get => GetValue(IsHoldWithMouseEnabledProperty);
set => SetValue(IsHoldWithMouseEnabledProperty, value);
}
public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
{
_target = target;
_actions = actions;
_target?.AddHandler(InputElement.PointerPressedEvent, OnPointerPressed, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
_target?.AddHandler(InputElement.PointerReleasedEvent, OnPointerReleased, Interactivity.RoutingStrategies.Tunnel | Interactivity.RoutingStrategies.Bubble);
_holdTimer = new Timer(300);
_holdTimer.AutoReset = false;
_holdTimer.Elapsed += HoldTimer_Elapsed;
}
private async void HoldTimer_Elapsed(object? sender, ElapsedEventArgs e)
{
_elasped = true;
_holdTimer?.Stop();
if(_tracking != null)
{
await Dispatcher.UIThread.InvokeAsync(() => _target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, HoldingState.Started)));
}
}
private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
PointerPressed(e);
}
private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
PointerReleased(e);
}
public void PointerCaptureLost(IPointer pointer)
{
if (_tracking == pointer)
{
EndHold(!_elasped);
}
}
public void PointerMoved(PointerEventArgs e)
{
if (_tracking == e.Pointer && _target is Visual visual)
{
var currentPosition = e.GetPosition(visual);
if (!_trackingBounds.Contains(currentPosition))
{
EndHold(true);
}
}
}
public void PointerPressed(PointerPressedEventArgs e)
{
if (_target != null && _target is Visual visual && (IsHoldWithMouseEnabled || e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen))
{
_elasped = false;
var position = e.GetPosition(visual);
_gestureId = HoldGestureEventArgs.GetNextFreeId();
_tracking = e.Pointer;
_pointerEventArgs = e;
_trackingBounds = new Rect(position.X - Tolerance / 2, position.Y - Tolerance / 2, Tolerance, Tolerance);
_holdTimer?.Start();
}
}
public void PointerReleased(PointerReleasedEventArgs e)
{
if (_tracking == e.Pointer)
{
EndHold(!_elasped);
}
}
private void EndHold(bool cancelled)
{
_holdTimer?.Stop();
_tracking = null;
_trackingBounds = default;
_target?.RaiseEvent(new HoldGestureEventArgs(_gestureId, _pointerEventArgs, cancelled ? HoldingState.Cancelled : HoldingState.Completed));
}
}
}

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

@ -1,6 +1,8 @@
using System;
using System.Threading;
using Avalonia.Interactivity;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Input
@ -8,6 +10,16 @@ namespace Avalonia.Input
public static class Gestures
{
private static bool s_isDoubleTapped = false;
private static bool s_isHolding;
private static CancellationTokenSource? s_holdCancellationToken;
/* /// <summary>
/// Defines the <see cref="IsHoldWithMouseEnabled"/> property.
/// </summary>
public static readonly AttachedProperty<bool> IsHoldWithMouseEnabledProperty =
AvaloniaProperty.RegisterAttached<Gestures, Interactive, bool>(
"IsHoldWithMouseEnabled");*/
public static readonly RoutedEvent<TappedEventArgs> TappedEvent = RoutedEvent.Register<TappedEventArgs>(
"Tapped",
RoutingStrategies.Bubble,
@ -45,6 +57,7 @@ namespace Avalonia.Input
private static readonly WeakReference<object?> s_lastPress = new WeakReference<object?>(null);
private static Point s_lastPressPoint;
private static IPointer? s_lastPointer;
public static readonly RoutedEvent<PinchEventArgs> PinchEvent =
RoutedEvent.Register<PinchEventArgs>(
@ -58,9 +71,9 @@ namespace Avalonia.Input
RoutedEvent.Register<PullGestureEventArgs>(
"PullGesture", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<HoldGestureEventArgs> HoldGestureEvent =
RoutedEvent.Register<HoldGestureEventArgs>(
"HoldGesture", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<HoldingRoutedEventArgs> HoldingEvent =
RoutedEvent.Register<HoldingRoutedEventArgs>(
"Holding", RoutingStrategies.Bubble, typeof(Gestures));
public static readonly RoutedEvent<PullGestureEndedEventArgs> PullGestureEndedEvent =
RoutedEvent.Register<PullGestureEndedEventArgs>(
@ -70,6 +83,7 @@ namespace Avalonia.Input
{
InputElement.PointerPressedEvent.RouteFinished.Subscribe(PointerPressed);
InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased);
InputElement.PointerMovedEvent.RouteFinished.Subscribe(PointerMoved);
}
public static void AddTappedHandler(Interactive element, EventHandler<RoutedEventArgs> handler)
@ -114,11 +128,38 @@ namespace Avalonia.Input
var e = (PointerPressedEventArgs)ev;
var visual = (Visual)ev.Source;
if(s_lastPointer != null)
{
if(s_isHolding && ev.Source is Interactive i)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled));
}
s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null;
s_lastPointer = null;
}
s_isHolding = false;
if (e.ClickCount % 2 == 1)
{
s_isDoubleTapped = false;
s_lastPress.SetTarget(ev.Source);
s_lastPointer = e.Pointer;
s_lastPressPoint = e.GetPosition((Visual)ev.Source);
s_holdCancellationToken = new CancellationTokenSource();
var token = s_holdCancellationToken.Token;
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
DispatcherTimer.RunOnce(() =>
{
if (!token.IsCancellationRequested && e.Source is InputElement i && i.IsHoldingEnabled && ( e.Pointer.Type != PointerType.Mouse || i.IsHoldWithMouseEnabled))
{
s_isHolding = true;
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Started));
}
}, TimeSpan.FromMilliseconds(300));
}
else if (e.ClickCount % 2 == 0 && e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed)
{
@ -152,7 +193,12 @@ namespace Avalonia.Input
if (tapRect.ContainsExclusive(point.Position))
{
if (e.InitialPressMouseButton == MouseButton.Right)
if(s_isHolding)
{
s_isHolding = false;
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Completed));
}
else if (e.InitialPressMouseButton == MouseButton.Right)
{
i.RaiseEvent(new TappedEventArgs(RightTappedEvent, e));
}
@ -164,6 +210,45 @@ namespace Avalonia.Input
}
}
}
s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null;
s_lastPointer = null;
}
}
private static void PointerMoved(RoutedEventArgs ev)
{
if (ev.Route == RoutingStrategies.Bubble)
{
var e = (PointerEventArgs)ev;
if (s_lastPress.TryGetTarget(out var target))
{
if (e.Pointer == s_lastPointer)
{
var point = e.GetCurrentPoint((Visual)target);
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>();
var tapSize = settings?.GetTapSize(point.Pointer.Type) ?? new Size(4, 4);
var tapRect = new Rect(s_lastPressPoint, new Size())
.Inflate(new Thickness(tapSize.Width, tapSize.Height));
if (tapRect.ContainsExclusive(point.Position))
{
return;
}
}
if (s_isHolding && ev.Source is Interactive i)
{
i.RaiseEvent(new HoldingRoutedEventArgs(HoldingState.Cancelled));
}
}
s_holdCancellationToken?.Cancel();
s_holdCancellationToken?.Dispose();
s_holdCancellationToken = null;
s_isHolding = false;
}
}
}

31
src/Avalonia.Base/Input/HoldGestureEventArgs.cs

@ -1,31 +0,0 @@
using System;
using Avalonia.Interactivity;
namespace Avalonia.Input
{
public class HoldGestureEventArgs : RoutedEventArgs
{
public int Id { get; }
public Vector Delta { get; }
public HoldingState HoldingState { get; }
public PointerEventArgs? PointerEventArgs { get; }
private static int _nextId = 1;
internal static int GetNextFreeId() => _nextId++;
public HoldGestureEventArgs(int id, PointerEventArgs? pointerEventArgs, HoldingState holdingState) : base(Gestures.HoldGestureEvent)
{
Id = id;
HoldingState = holdingState;
PointerEventArgs = pointerEventArgs;
}
}
public enum HoldingState
{
Started,
Completed,
Cancelled,
}
}

22
src/Avalonia.Base/Input/HoldingRoutedEventArgs.cs

@ -0,0 +1,22 @@
using System;
using Avalonia.Interactivity;
namespace Avalonia.Input
{
public class HoldingRoutedEventArgs : RoutedEventArgs
{
public HoldingState HoldingState { get; }
public HoldingRoutedEventArgs(HoldingState holdingState) : base(Gestures.HoldingEvent)
{
HoldingState = holdingState;
}
}
public enum HoldingState
{
Started,
Completed,
Cancelled,
}
}

46
src/Avalonia.Base/Input/InputElement.cs

@ -45,6 +45,18 @@ namespace Avalonia.Input
public static readonly StyledProperty<Cursor?> CursorProperty =
AvaloniaProperty.Register<InputElement, Cursor?>(nameof(Cursor), null, true);
/// <summary>
/// Defines the <see cref="IsHoldingEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsHoldingEnabledProperty =
AvaloniaProperty.Register<InputElement, bool>(nameof(IsHoldingEnabled), true);
/// <summary>
/// Defines the <see cref="IsHoldWithMouseEnabled"/> property.
/// </summary>
public static readonly StyledProperty<bool> IsHoldWithMouseEnabledProperty =
AvaloniaProperty.Register<InputElement, bool>(nameof(IsHoldWithMouseEnabled), false);
/// <summary>
/// Defines the <see cref="IsKeyboardFocusWithin"/> property.
/// </summary>
@ -188,6 +200,11 @@ namespace Avalonia.Input
/// </summary>
public static readonly RoutedEvent<TappedEventArgs> TappedEvent = Gestures.TappedEvent;
/// <summary>
/// Defines the <see cref="Holding"/> event.
/// </summary>
public static readonly RoutedEvent<HoldingRoutedEventArgs> HoldingEvent = Gestures.HoldingEvent;
/// <summary>
/// Defines the <see cref="DoubleTapped"/> event.
/// </summary>
@ -352,6 +369,15 @@ namespace Avalonia.Input
add { AddHandler(TappedEvent, value); }
remove { RemoveHandler(TappedEvent, value); }
}
/// <summary>
/// Occurs when a hold gesture occurs on the control.
/// </summary>
public event EventHandler<HoldingRoutedEventArgs>? Holding
{
add { AddHandler(HoldingEvent, value); }
remove { RemoveHandler(HoldingEvent, value); }
}
/// <summary>
/// Occurs when a double-tap gesture occurs on the control.
@ -388,6 +414,26 @@ namespace Avalonia.Input
get { return GetValue(CursorProperty); }
set { SetValue(CursorProperty, value); }
}
/// <summary>
/// Gets or sets a value that determines whether the Holding event can originate
/// from that element.
/// </summary>
public bool IsHoldingEnabled
{
get { return GetValue(IsHoldingEnabledProperty); }
set { SetValue(IsHoldingEnabledProperty, value); }
}
/// <summary>
/// Gets or sets a value that determines whether the Holding event can originate
/// from that element.
/// </summary>
public bool IsHoldWithMouseEnabled
{
get { return GetValue(IsHoldWithMouseEnabledProperty); }
set { SetValue(IsHoldWithMouseEnabledProperty, value); }
}
/// <summary>
/// Gets a value indicating whether keyboard focus is anywhere within the element or its visual tree child elements.

18
src/Avalonia.Controls/Control.cs

@ -216,12 +216,6 @@ namespace Avalonia.Controls
/// <inheritdoc/>
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
public Control()
{
// Add a default HoldGestureRecognizer
GestureRecognizers.Add(new HoldGestureRecognizer());
}
/// <inheritdoc/>
void ISetterValue.Initialize(ISetter setter)
{
@ -372,19 +366,19 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTreeCore(e);
AddHandler(Gestures.HoldGestureEvent, OnHoldEvent);
AddHandler(Gestures.HoldingEvent, OnHoldEvent);
InitializeIfNeeded();
ScheduleOnLoadedCore();
}
private void OnHoldEvent(object? sender, HoldGestureEventArgs e)
private void OnHoldEvent(object? sender, HoldingRoutedEventArgs e)
{
if(e.HoldingState == HoldingState.Started)
if(e.HoldingState == HoldingState.Completed)
{
// Trigger ContentRequest when hold starts
RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs!));
// Trigger ContentRequest when hold is complete
RaiseEvent(new ContextRequestedEventArgs());
}
}
@ -393,7 +387,7 @@ namespace Avalonia.Controls
{
base.OnDetachedFromVisualTreeCore(e);
RemoveHandler(Gestures.HoldGestureEvent, OnHoldEvent);
RemoveHandler(Gestures.HoldingEvent, OnHoldEvent);
OnUnloadedCore();
}

Loading…
Cancel
Save