From 8c0dfbaf08f84e1c810d1870aca47ae6e387b298 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 26 May 2023 11:32:57 +0000 Subject: [PATCH] rework gesture routing #11522 --- .../GestureRecognizerCollection.cs | 50 +++++-------------- .../GestureRecognizers/IGestureRecognizer.cs | 1 + .../PinchGestureRecognizer.cs | 4 +- .../PullGestureRecognizer.cs | 6 ++- .../ScrollGestureRecognizer.cs | 6 ++- src/Avalonia.Base/Input/InputElement.cs | 46 ++++++++++++----- src/Avalonia.Base/Input/MouseDevice.cs | 36 ++++++++++++- src/Avalonia.Base/Input/PenDevice.cs | 35 ++++++++++++- src/Avalonia.Base/Input/Pointer.cs | 5 +- src/Avalonia.Base/Input/PointerEventArgs.cs | 10 ++++ src/Avalonia.Base/Input/TouchDevice.cs | 35 ++++++++++--- src/Avalonia.Controls/Primitives/Thumb.cs | 2 + 12 files changed, 171 insertions(+), 65 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 8101e79af9..8ec9cb7301 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -10,8 +10,6 @@ namespace Avalonia.Input.GestureRecognizers { private readonly IInputElement _inputElement; private List? _recognizers; - private Dictionary? _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(); - _pointerGrabs = new Dictionary(); } _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) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs index c1d9ae5304..51c36a93de 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs +++ b/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); diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index 3b83d0cb87..4390167e98 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/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; diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index 6784677520..7391cdbaca 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/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) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index b510d44e63..3206c3903a 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/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(); } } diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 68131e5bf7..46f543d25b 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -225,6 +225,11 @@ namespace Avalonia.Input PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e)); PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e)); PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e)); + + // Gesture only handlers + PointerMovedEvent.AddClassHandler((x, e) => x.OnGesturePointerMoved(e), handledEventsToo: true); + PointerPressedEvent.AddClassHandler((x, e) => x.OnGesturePointerPressed(e), handledEventsToo: true); + PointerReleasedEvent.AddClassHandler((x, e) => x.OnGesturePointerReleased(e), handledEventsToo: true); } public InputElement() @@ -583,10 +588,6 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerMoved(PointerEventArgs e) { - if (_gestureRecognizers?.HandlePointerMoved(e) == true) - { - e.Handled = true; - } } /// @@ -595,10 +596,6 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerPressed(PointerPressedEventArgs e) { - if (_gestureRecognizers?.HandlePointerPressed(e) == true) - { - e.Handled = true; - } } /// @@ -607,10 +604,33 @@ namespace Avalonia.Input /// The event args. 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; + } } /// @@ -619,7 +639,7 @@ namespace Avalonia.Input /// The event args. protected virtual void OnPointerCaptureLost(PointerCaptureLostEventArgs e) { - _gestureRecognizers?.HandlePointerCaptureLost(e); + } /// diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 69c7a83900..bb8313106f 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/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; } diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs index d5ccfdb18d..fcf7d31e03 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -114,7 +114,22 @@ namespace Avalonia.Input KeyModifiers inputModifiers, IInputElement? hitTest, Lazy?>? 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; } diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 30c32ae48c..3abda3637e 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/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; } } diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 9c24e5c314..cd742cc4e4 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -58,6 +58,8 @@ namespace Avalonia.Input /// public ulong Timestamp { get; } + internal bool IsGestureRecognitionSkipped { get; private set; } + /// /// Gets a value that indicates which key modifiers were active at the time that the pointer event was initiated. /// @@ -121,6 +123,14 @@ namespace Avalonia.Input return points; } + /// + /// Prevents this event from being handled by other gesture recognizers in the route + /// + public void PreventGestureRecognition() + { + IsGestureRecognitionSkipped = true; + } + /// /// Returns the current pointer point properties /// diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index 74c5837b84..c5b372defa 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/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); + } } } diff --git a/src/Avalonia.Controls/Primitives/Thumb.cs b/src/Avalonia.Controls/Primitives/Thumb.cs index c5d73dc00f..7299300cc7 100644 --- a/src/Avalonia.Controls/Primitives/Thumb.cs +++ b/src/Avalonia.Controls/Primitives/Thumb.cs @@ -113,6 +113,8 @@ namespace Avalonia.Controls.Primitives PseudoClasses.Add(":pressed"); + e.PreventGestureRecognition(); + RaiseEvent(ev); }