diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs new file mode 100644 index 0000000000..2cb97b4707 --- /dev/null +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs @@ -0,0 +1,37 @@ +namespace Avalonia.Input.GestureRecognizers +{ + public abstract class GestureRecognizer : StyledElement + { + protected internal IInputElement? Target { get; internal set; } + + protected abstract void PointerPressed(PointerPressedEventArgs e); + protected abstract void PointerReleased(PointerReleasedEventArgs e); + protected abstract void PointerMoved(PointerEventArgs e); + protected abstract void PointerCaptureLost(IPointer pointer); + + internal void PointerPressedInternal(PointerPressedEventArgs e) + { + PointerPressed(e); + } + + internal void PointerReleasedInternal(PointerReleasedEventArgs e) + { + PointerReleased(e); + } + + internal void PointerMovedInternal(PointerEventArgs e) + { + PointerMoved(e); + } + + internal void PointerCaptureLostInternal(IPointer pointer) + { + PointerCaptureLost(pointer); + } + + protected void Capture(IPointer pointer) + { + (pointer as Pointer)?.CaptureGestureRecognizer(this); + } + } +} diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 3b9b2d0de6..05dce8214b 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -6,29 +6,26 @@ using Avalonia.Reactive; namespace Avalonia.Input.GestureRecognizers { - public class GestureRecognizerCollection : IReadOnlyCollection, IGestureRecognizerActionsDispatcher + public class GestureRecognizerCollection : IReadOnlyCollection { private readonly IInputElement _inputElement; - private List? _recognizers; - private Dictionary? _pointerGrabs; - + private List? _recognizers; public GestureRecognizerCollection(IInputElement inputElement) { _inputElement = inputElement; } - public void Add(IGestureRecognizer recognizer) + public void Add(GestureRecognizer recognizer) { if (_recognizers == null) { // We initialize the collection when the first recognizer is added - _recognizers = new List(); - _pointerGrabs = new Dictionary(); + _recognizers = new List(); } _recognizers.Add(recognizer); - recognizer.Initialize(_inputElement, this); + recognizer.Target = _inputElement; // Hacks to make bindings work @@ -41,25 +38,22 @@ namespace Avalonia.Input.GestureRecognizers } } - static readonly List s_Empty = new List(); + static readonly List s_Empty = new List(); - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() => _recognizers?.GetEnumerator() ?? s_Empty.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); public int Count => _recognizers?.Count ?? 0; - internal bool HandlePointerPressed(PointerPressedEventArgs e) { if (_recognizers == null) return false; foreach (var r in _recognizers) { - if (e.Handled) - break; - r.PointerPressed(e); + r.PointerPressedInternal(e); } return e.Handled; @@ -69,17 +63,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.PointerReleasedInternal(e); } - else - foreach (var r in _recognizers) - { - if (e.Handled) - break; - r.PointerReleased(e); - } return e.Handled; } @@ -87,41 +79,16 @@ 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; - void IGestureRecognizerActionsDispatcher.Capture(IPointer pointer, IGestureRecognizer recognizer) - { - pointer.Capture(_inputElement); - _pointerGrabs![pointer] = recognizer; - foreach (var r in _recognizers!) - { - if (r != recognizer) - r.PointerCaptureLost(pointer); + r.PointerMovedInternal(e); } + return e.Handled; } - } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs deleted file mode 100644 index c1d9ae5304..0000000000 --- a/src/Avalonia.Base/Input/GestureRecognizers/IGestureRecognizer.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Avalonia.Input.GestureRecognizers -{ - public interface IGestureRecognizer - { - void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions); - void PointerPressed(PointerPressedEventArgs e); - void PointerReleased(PointerReleasedEventArgs e); - void PointerMoved(PointerEventArgs e); - void PointerCaptureLost(IPointer pointer); - } - - public interface IGestureRecognizerActionsDispatcher - { - void Capture(IPointer pointer, IGestureRecognizer recognizer); - } - - public enum GestureRecognizerResult - { - None, - Capture, - ReleaseCapture - } -} diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index 3b83d0cb87..b02c82a066 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -2,10 +2,8 @@ namespace Avalonia.Input { - public class PinchGestureRecognizer : StyledElement, IGestureRecognizer + public class PinchGestureRecognizer : GestureRecognizer { - private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; private float _initialDistance; private IPointer? _firstContact; private Point _firstPoint; @@ -13,12 +11,6 @@ namespace Avalonia.Input private Point _secondPoint; private Point _origin; - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) - { - _target = target; - _actions = actions; - } - private void OnPointerPressed(object? sender, PointerPressedEventArgs e) { PointerPressed(e); @@ -29,14 +21,14 @@ namespace Avalonia.Input PointerReleased(e); } - public void PointerCaptureLost(IPointer pointer) + protected override void PointerCaptureLost(IPointer pointer) { RemoveContact(pointer); } - public void PointerMoved(PointerEventArgs e) + protected override void PointerMoved(PointerEventArgs e) { - if (_target != null && _target is Visual visual) + if (Target != null && Target is Visual visual) { if(_firstContact == e.Pointer) { @@ -58,16 +50,16 @@ namespace Avalonia.Input var scale = distance / _initialDistance; var pinchEventArgs = new PinchEventArgs(scale, _origin); - _target?.RaiseEvent(pinchEventArgs); + Target?.RaiseEvent(pinchEventArgs); e.Handled = pinchEventArgs.Handled; } } } - public void PointerPressed(PointerPressedEventArgs e) + protected override void PointerPressed(PointerPressedEventArgs e) { - if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { if (_firstContact == null) { @@ -92,13 +84,13 @@ namespace Avalonia.Input _origin = new Point((_firstPoint.X + _secondPoint.X) / 2.0f, (_firstPoint.Y + _secondPoint.Y) / 2.0f); - _actions!.Capture(_firstContact, this); - _actions!.Capture(_secondContact, this); + Capture(_firstContact); + Capture(_secondContact); } } } - public void PointerReleased(PointerReleasedEventArgs e) + protected override void PointerReleased(PointerReleasedEventArgs e) { RemoveContact(e.Pointer); } @@ -118,7 +110,7 @@ namespace Avalonia.Input _secondContact = null; } - _target?.RaiseEvent(new PinchEndedEventArgs()); + Target?.RaiseEvent(new PinchEndedEventArgs()); } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs index 6784677520..57faf5bfd8 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PullGestureRecognizer.cs @@ -1,15 +1,11 @@ using System; -using System.Diagnostics; using Avalonia.Input.GestureRecognizers; namespace Avalonia.Input { - public class PullGestureRecognizer : StyledElement, IGestureRecognizer + public class PullGestureRecognizer : GestureRecognizer { internal static int MinPullDetectionSize = 50; - - private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; private Point _initialPosition; private int _gestureId; private IPointer? _tracking; @@ -34,13 +30,7 @@ namespace Avalonia.Input public PullGestureRecognizer() { } - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) - { - _target = target; - _actions = actions; - } - - public void PointerCaptureLost(IPointer pointer) + protected override void PointerCaptureLost(IPointer pointer) { if (_tracking == pointer) { @@ -48,12 +38,13 @@ namespace Avalonia.Input } } - public void PointerMoved(PointerEventArgs e) + protected override void PointerMoved(PointerEventArgs e) { - if (_tracking == e.Pointer && _target is Visual visual) + if (_tracking == e.Pointer && Target is Visual visual) { var currentPosition = e.GetPosition(visual); - _actions!.Capture(e.Pointer, this); + Capture(e.Pointer); + e.PreventGestureRecognition(); Vector delta = default; switch (PullDirection) @@ -86,15 +77,15 @@ namespace Avalonia.Input _pullInProgress = true; var pullEventArgs = new PullGestureEventArgs(_gestureId, delta, PullDirection); - _target?.RaiseEvent(pullEventArgs); + Target?.RaiseEvent(pullEventArgs); e.Handled = pullEventArgs.Handled; } } - public void PointerPressed(PointerPressedEventArgs e) + protected override void PointerPressed(PointerPressedEventArgs e) { - if (_target != null && _target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) + if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { var position = e.GetPosition(visual); @@ -127,7 +118,7 @@ namespace Avalonia.Input } } - public void PointerReleased(PointerReleasedEventArgs e) + protected override void PointerReleased(PointerReleasedEventArgs e) { if (_tracking == e.Pointer && _pullInProgress) { @@ -141,7 +132,7 @@ namespace Avalonia.Input _initialPosition = default; _pullInProgress = false; - _target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection)); + Target?.RaiseEvent(new PullGestureEndedEventArgs(_gestureId, PullDirection)); } } } diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs index b510d44e63..beecfcc3ce 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs @@ -4,7 +4,7 @@ using Avalonia.Threading; namespace Avalonia.Input.GestureRecognizers { - public class ScrollGestureRecognizer : AvaloniaObject, IGestureRecognizer + public class ScrollGestureRecognizer : GestureRecognizer { // Pixels per second speed that is considered to be the stop of inertial scroll internal const double InertialScrollSpeedEnd = 5; @@ -18,8 +18,6 @@ namespace Avalonia.Input.GestureRecognizers private bool _scrolling; private Point _trackedRootPoint; private IPointer? _tracking; - private IInputElement? _target; - private IGestureRecognizerActionsDispatcher? _actions; private int _gestureId; private Point _pointerPressedPoint; private VelocityTracker? _velocityTracker; @@ -91,15 +89,9 @@ namespace Avalonia.Input.GestureRecognizers { get => _scrollStartDistance; set => SetAndRaise(ScrollStartDistanceProperty, ref _scrollStartDistance, value); - } - - public void Initialize(IInputElement target, IGestureRecognizerActionsDispatcher actions) - { - _target = target; - _actions = actions; } - - public void PointerPressed(PointerPressedEventArgs e) + + protected override void PointerPressed(PointerPressedEventArgs e) { if (e.Pointer.IsPrimary && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) @@ -107,15 +99,15 @@ namespace Avalonia.Input.GestureRecognizers EndGesture(); _tracking = e.Pointer; _gestureId = ScrollGestureEventArgs.GetNextFreeId(); - _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)_target); + _trackedRootPoint = _pointerPressedPoint = e.GetPosition((Visual?)Target); } } - - public void PointerMoved(PointerEventArgs e) + + protected override void PointerMoved(PointerEventArgs e) { if (e.Pointer == _tracking) { - var rootPoint = e.GetPosition((Visual?)_target); + var rootPoint = e.GetPosition((Visual?)Target); if (!_scrolling) { if (CanHorizontallyScroll && Math.Abs(_trackedRootPoint.X - rootPoint.X) > ScrollStartDistance) @@ -131,7 +123,9 @@ namespace Avalonia.Input.GestureRecognizers _trackedRootPoint.X - (_trackedRootPoint.X >= rootPoint.X ? ScrollStartDistance : -ScrollStartDistance), _trackedRootPoint.Y - (_trackedRootPoint.Y >= rootPoint.Y ? ScrollStartDistance : -ScrollStartDistance)); - _actions!.Capture(e.Pointer, this); + Capture(e.Pointer); + + e.PreventGestureRecognition(); } } @@ -143,13 +137,13 @@ namespace Avalonia.Input.GestureRecognizers _lastMoveTimestamp = e.Timestamp; _trackedRootPoint = rootPoint; - _target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); + Target!.RaiseEvent(new ScrollGestureEventArgs(_gestureId, vector)); e.Handled = true; } } } - public void PointerCaptureLost(IPointer pointer) + protected override void PointerCaptureLost(IPointer pointer) { if (pointer == _tracking) EndGesture(); } @@ -161,7 +155,7 @@ namespace Avalonia.Input.GestureRecognizers { _inertia = default; _scrolling = false; - _target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); + Target!.RaiseEvent(new ScrollGestureEndedEventArgs(_gestureId)); _gestureId = 0; _lastMoveTimestamp = null; } @@ -169,7 +163,7 @@ namespace Avalonia.Input.GestureRecognizers } - public void PointerReleased(PointerReleasedEventArgs e) + protected override void PointerReleased(PointerReleasedEventArgs e) { if (e.Pointer == _tracking && _scrolling) { @@ -188,7 +182,7 @@ namespace Avalonia.Input.GestureRecognizers var savedGestureId = _gestureId; var st = Stopwatch.StartNew(); var lastTime = TimeSpan.Zero; - _target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia)); + Target!.RaiseEvent(new ScrollGestureInertiaStartingEventArgs(_gestureId, _inertia)); DispatcherTimer.Run(() => { // Another gesture has started, finish the current one @@ -203,7 +197,7 @@ namespace Avalonia.Input.GestureRecognizers var speed = _inertia * Math.Pow(InertialResistance, st.Elapsed.TotalSeconds); var distance = speed * elapsedSinceLastTick.TotalSeconds; var scrollGestureEventArgs = new ScrollGestureEventArgs(_gestureId, distance); - _target!.RaiseEvent(scrollGestureEventArgs); + Target!.RaiseEvent(scrollGestureEventArgs); if (!scrollGestureEventArgs.Handled || scrollGestureEventArgs.ShouldEndScrollGesture) { diff --git a/src/Avalonia.Base/Input/IPointer.cs b/src/Avalonia.Base/Input/IPointer.cs index 52605bb6ae..050adbabaa 100644 --- a/src/Avalonia.Base/Input/IPointer.cs +++ b/src/Avalonia.Base/Input/IPointer.cs @@ -1,3 +1,4 @@ +using Avalonia.Input.GestureRecognizers; using Avalonia.Metadata; namespace Avalonia.Input 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..db333dbd8b 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -7,7 +7,7 @@ using Avalonia.Metadata; using Avalonia.Platform; using Avalonia.Utilities; using Avalonia.VisualTree; - +using Avalonia.Input.GestureRecognizers; #pragma warning disable CS0618 namespace Avalonia.Input @@ -163,17 +163,21 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - var source = _pointer.Captured ?? hitTest; + var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest; if (source is object) { var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)root, p, timestamp, properties, inputModifiers, intermediatePoints); - source.RaiseEvent(e); + if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) + gestureRecognizer.PointerMovedInternal(e); + else + source.RaiseEvent(e); return e.Handled; } + return false; } @@ -183,15 +187,19 @@ namespace Avalonia.Input device = device ?? throw new ArgumentNullException(nameof(device)); root = root ?? throw new ArgumentNullException(nameof(root)); - var source = _pointer.Captured ?? hitTest; + var source = _pointer.CapturedGestureRecognizer?.Target ?? _pointer.Captured ?? hitTest; if (source is not null) { var e = new PointerReleasedEventArgs(source, _pointer, (Visual)root, p, timestamp, props, inputModifiers, _lastMouseDownButton); - source?.RaiseEvent(e); + if (_pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) + gestureRecognizer.PointerReleasedInternal(e); + else + 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..09bf18d3fd 100644 --- a/src/Avalonia.Base/Input/PenDevice.cs +++ b/src/Avalonia.Base/Input/PenDevice.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Avalonia.Input.GestureRecognizers; using Avalonia.Input.Raw; using Avalonia.Interactivity; using Avalonia.Metadata; @@ -114,14 +115,17 @@ namespace Avalonia.Input KeyModifiers inputModifiers, IInputElement? hitTest, Lazy?>? intermediatePoints) { - var source = pointer.Captured ?? hitTest; + var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest; if (source is not null) { var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, intermediatePoints); - source.RaiseEvent(e); + if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) + gestureRecognizer.PointerMovedInternal(e); + else + source.RaiseEvent(e); return e.Handled; } @@ -132,15 +136,19 @@ namespace Avalonia.Input IInputElement root, Point p, PointerPointProperties properties, KeyModifiers inputModifiers, IInputElement? hitTest) { - var source = pointer.Captured ?? hitTest; + var source = pointer.CapturedGestureRecognizer?.Target ?? pointer.Captured ?? hitTest; if (source is not null) { var e = new PointerReleasedEventArgs(source, pointer, (Visual)root, p, timestamp, properties, inputModifiers, _lastMouseDownButton); - source.RaiseEvent(e); + if (pointer.CapturedGestureRecognizer is GestureRecognizer gestureRecognizer) + gestureRecognizer.PointerReleasedInternal(e); + else + 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 4713364f00..91358712a0 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Avalonia.Input.GestureRecognizers; using Avalonia.VisualTree; namespace Avalonia.Input @@ -52,6 +53,9 @@ namespace Avalonia.Input if (Captured is Visual v3) v3.DetachedFromVisualTree += OnCaptureDetached; + + if (Captured != null) + CaptureGestureRecognizer(null); } static IInputElement? GetNextCapture(Visual parent) @@ -69,6 +73,31 @@ namespace Avalonia.Input public PointerType Type { get; } public bool IsPrimary { get; } - public void Dispose() => Capture(null); + + /// + /// Gets the gesture recognizer that is currently capturing by the pointer, if any. + /// + internal GestureRecognizer? CapturedGestureRecognizer { get; private set; } + + public void Dispose() + { + Capture(null); + } + + /// + /// Captures pointer input to the specified gesture recognizer. + /// + /// The gesture recognizer. + /// + internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer) + { + if (CapturedGestureRecognizer != gestureRecognizer) + CapturedGestureRecognizer?.PointerCaptureLostInternal(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..8868e966f0 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?.PointerReleasedInternal(e); + } + else + { + target.RaiseEvent(e); + } } } @@ -106,15 +116,28 @@ namespace Avalonia.Input { _pointers.Remove(args.RawPointerId); using (pointer) - pointer.Capture(null); + { + 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?.PointerMovedInternal(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); } diff --git a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs index 5c7071a161..39ff8e3a92 100644 --- a/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs +++ b/src/Avalonia.Controls/PullToRefresh/RefreshVisualizer.cs @@ -238,7 +238,6 @@ namespace Avalonia.Controls visualizerVisual.Offset = IsPullDirectionVertical ? new Vector3(visualizerVisual.Offset.X, 0, 0) : new Vector3(0, visualizerVisual.Offset.Y, 0); - visual.Offset = default; _content.InvalidateMeasure(); break; case RefreshVisualizerState.Interacting: @@ -452,8 +451,6 @@ namespace Avalonia.Controls _interactionRatioSubscription = RefreshInfoProvider.GetObservable(RefreshInfoProvider.InteractionRatioProperty) .Subscribe(InteractionRatioObserver); - var visual = RefreshInfoProvider.Visual; - _executingRatio = RefreshInfoProvider.ExecutionRatio; } else diff --git a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs index 506a62525c..3259ec1a4c 100644 --- a/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Platform/DefaultMenuInteractionHandlerTests.cs @@ -2,6 +2,7 @@ using Avalonia.Controls.Platform; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Input.GestureRecognizers; using Avalonia.Interactivity; using Avalonia.VisualTree; using Moq; diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs index db70f570a2..574599d1ad 100644 --- a/tests/Avalonia.UnitTests/TouchTestHelper.cs +++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs @@ -27,8 +27,13 @@ namespace Avalonia.UnitTests public void Move(Interactive target, Interactive source, in Point position, KeyModifiers modifiers = default) { - target.RaiseEvent(new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position, - Timestamp(), PointerPointProperties.None, modifiers)); + var e = new PointerEventArgs(InputElement.PointerMovedEvent, source, _pointer, (Visual)target, position, + Timestamp(), PointerPointProperties.None, modifiers); + if (_pointer.CapturedGestureRecognizer != null) + _pointer.CapturedGestureRecognizer.PointerMovedInternal(e); + else + target.RaiseEvent(e); + } public void Up(Interactive target, Point position = default, KeyModifiers modifiers = default) @@ -36,9 +41,16 @@ namespace Avalonia.UnitTests public void Up(Interactive target, Interactive source, Point position = default, KeyModifiers modifiers = default) { - source.RaiseEvent(new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None, - modifiers, MouseButton.None)); + var e = new PointerReleasedEventArgs(source, _pointer, (Visual)target, position, Timestamp(), PointerPointProperties.None, + modifiers, MouseButton.None); + + if (_pointer.CapturedGestureRecognizer != null) + _pointer.CapturedGestureRecognizer.PointerReleasedInternal(e); + else + source.RaiseEvent(e); + _pointer.Capture(null); + _pointer.CaptureGestureRecognizer(null); } public void Tap(Interactive target, Point position = default, KeyModifiers modifiers = default)