Browse Source

add hold gesture recognizer

pull/9652/head
Emmanuel Hansen 4 years ago
parent
commit
11b0de3481
  1. 124
      src/Avalonia.Base/Input/GestureRecognizers/HoldGestureRecognizer.cs
  2. 4
      src/Avalonia.Base/Input/Gestures.cs
  3. 31
      src/Avalonia.Base/Input/HoldGestureEventArgs.cs
  4. 19
      src/Avalonia.Controls/Control.cs

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

@ -0,0 +1,124 @@
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));
}
}
}

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

@ -58,6 +58,10 @@ 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<PullGestureEndedEventArgs> PullGestureEndedEvent =
RoutedEvent.Register<PullGestureEndedEventArgs>(
"PullGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));

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

@ -0,0 +1,31 @@
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,
}
}

19
src/Avalonia.Controls/Control.cs

@ -216,6 +216,12 @@ 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)
{
@ -366,16 +372,29 @@ namespace Avalonia.Controls
{
base.OnAttachedToVisualTreeCore(e);
AddHandler(Gestures.HoldGestureEvent, OnHoldEvent);
InitializeIfNeeded();
ScheduleOnLoadedCore();
}
private void OnHoldEvent(object? sender, HoldGestureEventArgs e)
{
if(e.HoldingState == HoldingState.Started)
{
// Trigger ContentRequest when hold starts
RaiseEvent(new ContextRequestedEventArgs(e.PointerEventArgs!));
}
}
/// <inheritdoc/>
protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTreeCore(e);
RemoveHandler(Gestures.HoldGestureEvent, OnHoldEvent);
OnUnloadedCore();
}

Loading…
Cancel
Save