Browse Source

rework gesture routing #11522

Emmanuel Hansen 2 years ago
parent
commit
8c0dfbaf08

+ 13 - 37
src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs

@@ -10,8 +10,6 @@ namespace Avalonia.Input.GestureRecognizers
     {
         private readonly IInputElement _inputElement;
         private List<IGestureRecognizer>? _recognizers;
-        private Dictionary<IPointer, IGestureRecognizer>? _pointerGrabs;
-
 
         public GestureRecognizerCollection(IInputElement inputElement)
         {
@@ -24,7 +22,6 @@ namespace Avalonia.Input.GestureRecognizers
             {
                 // We initialize the collection when the first recognizer is added
                 _recognizers = new List<IGestureRecognizer>();
-                _pointerGrabs = new Dictionary<IPointer, IGestureRecognizer>();
             }
 
             _recognizers.Add(recognizer);
@@ -57,8 +54,6 @@ namespace Avalonia.Input.GestureRecognizers
                 return false;
             foreach (var r in _recognizers)
             {
-                if (e.Handled)
-                    break;
                 r.PointerPressed(e);
             }
 
@@ -69,17 +64,15 @@ namespace Avalonia.Input.GestureRecognizers
         {
             if (_recognizers == null)
                 return false;
-            if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
+            var pointer = e.Pointer as Pointer;
+
+            foreach (var r in _recognizers)
             {
-                capture.PointerReleased(e);
+                if (pointer?.CapturedGestureRecognizer != null)
+                    break;
+
+                r.PointerReleased(e);
             }
-            else
-                foreach (var r in _recognizers)
-                {
-                    if (e.Handled)
-                        break;
-                    r.PointerReleased(e);
-                }
             return e.Handled;
         }
 
@@ -87,41 +80,24 @@ namespace Avalonia.Input.GestureRecognizers
         {
             if (_recognizers == null)
                 return false;
-            if (_pointerGrabs!.TryGetValue(e.Pointer, out var capture))
-            {
-                capture.PointerMoved(e);
-            }
-            else
-                foreach (var r in _recognizers)
-                {
-                    if (e.Handled)
-                        break;
-                    r.PointerMoved(e);
-                }
-            return e.Handled;
-        }
+            var pointer = e.Pointer as Pointer;
 
-        internal void HandlePointerCaptureLost(PointerCaptureLostEventArgs e)
-        {
-            if (_recognizers == null)
-                return;
-            _pointerGrabs!.Remove(e.Pointer);
             foreach (var r in _recognizers)
             {
-                r.PointerCaptureLost(e.Pointer);
+                if (pointer?.CapturedGestureRecognizer != null)
+                    break;
+
+                r.PointerMoved(e);
             }
+            return e.Handled;
         }
 
         void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer)
         {
             var p = pointer as Pointer;
-            if (p != null && p.CapturedGestureRecognizer != null && recognizer != p.CapturedGestureRecognizer)
-                    return;
 
-            pointer.Capture(_inputElement);
             p?.CaptureGestureRecognizer(recognizer);
 
-            _pointerGrabs![pointer] = recognizer;
             foreach (var r in _recognizers!)
             {
                 if (r != recognizer)

+ 1 - 0
src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs

@@ -2,6 +2,7 @@ namespace Avalonia.Input.GestureRecognizers
 {
     public interface IGestureRecognizer
     {
+        IInputElement? Target { get; }
         void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions);
         void PointerPressed(PointerPressedEventArgs e);
         void PointerReleased(PointerReleasedEventArgs e);

+ 3 - 1
src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs

@@ -2,7 +2,7 @@
 
 namespace Avalonia.Input
 {
-    public class PinchGestureRecognizer : StyledElement, IGestureRecognizer
+    public class PinchGestureRecognizer : AvaloniaObject, IGestureRecognizer
     {
         private IInputElement? _target;
         private IGestureRecognizerActionsDispatcher? _actions;
@@ -13,6 +13,8 @@ namespace Avalonia.Input
         private Point _secondPoint;
         private Point _origin;
 
+        public IInputElement? Target => _target;
+
         public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
         {
             _target = target;

+ 4 - 2
src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs

@@ -1,10 +1,9 @@
 using System;
-using System.Diagnostics;
 using Avalonia.Input.GestureRecognizers;
 
 namespace Avalonia.Input
 {
-    public class PullGestureRecognizer : StyledElement, IGestureRecognizer
+    public class PullGestureRecognizer : AvaloniaObject, IGestureRecognizer
     {
         internal static int MinPullDetectionSize = 50;
 
@@ -27,6 +26,8 @@ namespace Avalonia.Input
             set => SetValue(PullDirectionProperty, value);
         }
 
+        public IInputElement? Target => _target;
+
         public PullGestureRecognizer(PullDirection pullDirection)
         {
             PullDirection = pullDirection;
@@ -54,6 +55,7 @@ namespace Avalonia.Input
             {
                 var currentPosition = e.GetPosition(visual);
                 _actions!.Capture(e.Pointer, this);
+                e.PreventGestureRecognition();
 
                 Vector delta = default;
                 switch (PullDirection)

+ 5 - 1
src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs

@@ -91,7 +91,9 @@ namespace Avalonia.Input.GestureRecognizers
         {
             get => _scrollStartDistance;
             set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value);
-        }        
+        }
+
+        public IInputElement? Target => _target;
 
         public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions)
         {
@@ -132,6 +134,8 @@ namespace Avalonia.Input.GestureRecognizers
                             _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance));
 
                         _actions!.Capture(e.Pointer, this);
+
+                        e.PreventGestureRecognition();
                     }
                 }
 

+ 33 - 13
src/Avalonia.Base/Input/InputElement.cs

@@ -225,6 +225,11 @@ namespace Avalonia.Input
             PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
             PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
             PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
+
+            // Gesture only handlers
+            PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true);
+            PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true);
+            PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true);
         }
 
         public InputElement()
@@ -583,10 +588,6 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnPointerMoved(PointerEventArgs e)
         {
-            if (_gestureRecognizers?.HandlePointerMoved(e) == true)
-            {
-                e.Handled = true;
-            }
         }
 
         /// <summary>
@@ -595,10 +596,6 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnPointerPressed(PointerPressedEventArgs e)
         {
-            if (_gestureRecognizers?.HandlePointerPressed(e) == true)
-            {
-                e.Handled = true;
-            }
         }
 
         /// <summary>
@@ -607,10 +604,33 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnPointerReleased(PointerReleasedEventArgs e)
         {
-            if (_gestureRecognizers?.HandlePointerReleased(e) == true)
-            {
-                e.Handled = true;
-            }
+        }
+
+        private void OnGesturePointerReleased(PointerReleasedEventArgs e)
+        {
+            if (!e.IsGestureRecognitionSkipped)
+                if (_gestureRecognizers?.HandlePointerReleased(e) == true)
+                {
+                    e.Handled = true;
+                }
+        }
+
+        private void OnGesturePointerPressed(PointerPressedEventArgs e)
+        {
+            if (!e.IsGestureRecognitionSkipped)
+                if (_gestureRecognizers?.HandlePointerPressed(e) == true)
+                {
+                    e.Handled = true;
+                }
+        }
+
+        private void OnGesturePointerMoved(PointerEventArgs e)
+        {
+            if (!e.IsGestureRecognitionSkipped)
+                if (_gestureRecognizers?.HandlePointerMoved(e) == true)
+                {
+                    e.Handled = true;
+                }
         }
 
         /// <summary>
@@ -619,7 +639,7 @@ namespace Avalonia.Input
         /// <param name="e">The event args.</param>
         protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e)
         {
-            _gestureRecognizers?.HandlePointerCaptureLost(e);
+
         }
 
         /// <summary>

+ 34 - 2
src/Avalonia.Base/Input/MouseDevice.cs

@@ -163,7 +163,22 @@ namespace Avalonia.Input
             device = device ?? throw new ArgumentNullException(nameof(device));
             root = root ?? throw new ArgumentNullException(nameof(root));
 
-            var source = _pointer.Captured ?? hitTest;
+            IInputElement source;
+            if (_pointer.CapturedGestureRecognizer is { } gestureRecognizer)
+            {
+                source = gestureRecognizer.Target ?? hitTest;
+
+                if(source != null)
+                {
+                    var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root,
+                        p, timestamp, properties, inputModifiers, intermediatePoints);
+                    gestureRecognizer.PointerMoved(e);
+
+                    return e.Handled;
+                }
+            }
+
+            source = _pointer.Captured ?? hitTest;
 
             if (source is object)
             {
@@ -174,6 +189,7 @@ namespace Avalonia.Input
                 return e.Handled;
             }
 
+
             return false;
         }
 
@@ -183,7 +199,22 @@ namespace Avalonia.Input
             device = device ?? throw new ArgumentNullException(nameof(device));
             root = root ?? throw new ArgumentNullException(nameof(root));
 
-            var source = _pointer.Captured ?? hitTest;
+            IInputElement source;
+            if (_pointer.CapturedGestureRecognizer is { } gestureRecognizer)
+            {
+                source = gestureRecognizer.Target ?? hitTest;
+
+                if (source != null)
+                {
+                    var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers,
+                    _lastMouseDownButton);
+                    gestureRecognizer.PointerReleased(e);
+
+                    return e.Handled;
+                }
+            }
+
+            source = _pointer.Captured ?? hitTest;
 
             if (source is not null)
             {
@@ -192,6 +223,7 @@ namespace Avalonia.Input
 
                 source?.RaiseEvent(e);
                 _pointer.Capture(null);
+                _pointer.CaptureGestureRecognizer(null);
                 _lastMouseDownButton = default;
                 return e.Handled;
             }

+ 33 - 2
src/Avalonia.Base/Input/PenDevice.cs

@@ -114,7 +114,22 @@ namespace Avalonia.Input
             KeyModifiers inputModifiers, IInputElement? hitTest,
             Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints)
         {
-            var source = pointer.Captured ?? hitTest;
+            IInputElement source;
+            if (pointer.CapturedGestureRecognizer is { } gestureRecognizer)
+            {
+                source = gestureRecognizer.Target ?? hitTest;
+
+                if (source != null)
+                {
+                    var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root,
+                        p, timestamp, properties, inputModifiers, intermediatePoints);
+                    gestureRecognizer.PointerMoved(e);
+
+                    return e.Handled;
+                }
+            }
+
+            source = pointer.Captured ?? hitTest;
 
             if (source is not null)
             {
@@ -132,7 +147,22 @@ namespace Avalonia.Input
             IInputElement root, Point p, PointerPointProperties properties,
             KeyModifiers inputModifiers, IInputElement? hitTest)
         {
-            var source = pointer.Captured ?? hitTest;
+            IInputElement source;
+            if (pointer.CapturedGestureRecognizer is { } gestureRecognizer)
+            {
+                source = gestureRecognizer.Target ?? hitTest;
+
+                if (source != null)
+                {
+                    var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers,
+                    _lastMouseDownButton);
+                    gestureRecognizer.PointerReleased(e);
+
+                    return e.Handled;
+                }
+            }
+
+            source = pointer.Captured ?? hitTest;
 
             if (source is not null)
             {
@@ -141,6 +171,7 @@ namespace Avalonia.Input
 
                 source.RaiseEvent(e);
                 pointer.Capture(null);
+                pointer.CaptureGestureRecognizer(null);
                 _lastMouseDownButton = default;
                 return e.Handled;
             }

+ 4 - 1
src/Avalonia.Base/Input/Pointer.cs

@@ -54,7 +54,7 @@ namespace Avalonia.Input
             if (Captured is Visual v3)
                 v3.DetachedFromVisualTree += OnCaptureDetached;
 
-            if (Captured == null)
+            if (Captured != null)
                 CaptureGestureRecognizer(null);
         }
 
@@ -94,6 +94,9 @@ namespace Avalonia.Input
             if (CapturedGestureRecognizer != gestureRecognizer)
                 CapturedGestureRecognizer?.PointerCaptureLost(this);
 
+            if (gestureRecognizer != null)
+                Capture(null);
+
             CapturedGestureRecognizer = gestureRecognizer;
         }
     }

+ 10 - 0
src/Avalonia.Base/Input/PointerEventArgs.cs

@@ -58,6 +58,8 @@ namespace Avalonia.Input
         /// </summary>
         public ulong Timestamp { get; }
 
+        internal bool IsGestureRecognitionSkipped { get; private set; }
+
         /// <summary>
         /// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated.
         /// </summary>
@@ -121,6 +123,14 @@ namespace Avalonia.Input
             return points;
         }
 
+        /// <summary>
+        /// Prevents this event from being handled by other gesture recognizers in the route
+        /// </summary>
+        public void PreventGestureRecognition()
+        {
+            IsGestureRecognitionSkipped = true;
+        }
+
         /// <summary>
         /// Returns the current pointer point properties
         /// </summary>

+ 29 - 6
src/Avalonia.Base/Input/TouchDevice.cs

@@ -52,6 +52,7 @@ namespace Avalonia.Input
             }
 
             var target = pointer.Captured ?? args.Root;
+            var gestureTarget = pointer.CapturedGestureRecognizer?.Target;
             var updateKind = args.Type.ToUpdateKind();
             var keyModifier = args.InputModifiers.ToKeyModifiers();
 
@@ -95,10 +96,19 @@ namespace Avalonia.Input
                 _pointers.Remove(args.RawPointerId);
                 using (pointer)
                 {
-                    target.RaiseEvent(new PointerReleasedEventArgs(target, pointer,
-                        (Visual)args.Root, args.Position, ev.Timestamp,
-                        new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
-                        keyModifier, MouseButton.Left));
+                    target = gestureTarget ?? target;
+                    var e = new PointerReleasedEventArgs(target, pointer,
+                            (Visual)args.Root, args.Position, ev.Timestamp,
+                            new PointerPointProperties(GetModifiers(args.InputModifiers, false), updateKind),
+                            keyModifier, MouseButton.Left);
+                    if (gestureTarget != null)
+                    {
+                        pointer?.CapturedGestureRecognizer?.PointerReleased(e);
+                    }
+                    else
+                    {
+                        target.RaiseEvent(e);
+                    }
                 }
             }
 
@@ -106,15 +116,28 @@ namespace Avalonia.Input
             {
                 _pointers.Remove(args.RawPointerId);
                 using (pointer)
+                {
                     pointer.Capture(null);
+                    pointer.CaptureGestureRecognizer(null);
+                }
             }
 
             if (args.Type == RawPointerEventType.TouchUpdate)
             {
-                target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root,
+                target = gestureTarget ?? target;
+                var e = new PointerEventArgs(InputElement.PointerMovedEvent, target, pointer, (Visual)args.Root,
                     args.Position, ev.Timestamp,
                     new PointerPointProperties(GetModifiers(args.InputModifiers, true), updateKind),
-                    keyModifier, args.IntermediatePoints));
+                    keyModifier, args.IntermediatePoints);
+
+                if (gestureTarget != null)
+                {
+                    pointer?.CapturedGestureRecognizer?.PointerMoved(e);
+                }
+                else
+                {
+                    target.RaiseEvent(e);
+                }
             }
         }
 

+ 2 - 0
src/Avalonia.Controls/Primitives/Thumb.cs

@@ -113,6 +113,8 @@ namespace Avalonia.Controls.Primitives
 
             PseudoClasses.Add(":pressed");
 
+            e.PreventGestureRecognition();
+
             RaiseEvent(ev);
         }