Przeglądaj źródła

drop hold gesture recognizer

Emmanuel Hansen 2 lat temu
rodzic
commit
8945e1b77c

+ 0 - 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));
-        }
-    }
-}

+ 89 - 4
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;
             }
         }
     }

+ 0 - 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 - 0
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 - 0
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.

+ 6 - 12
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();
         }