diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 05dce8214b..74e8061292 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -1,3 +1,4 @@ +using System; using System.Collections; using System.Collections.Generic; using Avalonia.Controls; @@ -59,6 +60,20 @@ namespace Avalonia.Input.GestureRecognizers return e.Handled; } + internal void HandleCaptureLost(IPointer pointer) + { + if (_recognizers == null || pointer is not Pointer p) + return; + + foreach (var r in _recognizers) + { + if (p.CapturedGestureRecognizer == r) + continue; + + r.PointerCaptureLostInternal(pointer); + } + } + internal bool HandlePointerReleased(PointerReleasedEventArgs e) { if (_recognizers == null) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index b02c82a066..5a22ba7a14 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -1,4 +1,5 @@ -using Avalonia.Input.GestureRecognizers; +using System.Diagnostics; +using Avalonia.Input.GestureRecognizers; namespace Avalonia.Input { @@ -110,6 +111,7 @@ namespace Avalonia.Input _secondContact = null; } + Target?.RaiseEvent(new PinchEndedEventArgs()); } } diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs index 46f543d25b..91dae88dbb 100644 --- a/src/Avalonia.Base/Input/InputElement.cs +++ b/src/Avalonia.Base/Input/InputElement.cs @@ -230,6 +230,7 @@ namespace Avalonia.Input 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); + PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnGesturePointerCaptureLost(e), handledEventsToo: true); } public InputElement() @@ -615,6 +616,11 @@ namespace Avalonia.Input } } + private void OnGesturePointerCaptureLost(PointerCaptureLostEventArgs e) + { + _gestureRecognizers?.HandleCaptureLost(e.Pointer); + } + private void OnGesturePointerPressed(PointerPressedEventArgs e) { if (!e.IsGestureRecognitionSkipped) diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index 5744ddf963..e82432a00d 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -91,12 +91,16 @@ namespace Avalonia.Input internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer) { if (CapturedGestureRecognizer != gestureRecognizer) + { CapturedGestureRecognizer?.PointerCaptureLostInternal(this); + } + + CapturedGestureRecognizer = gestureRecognizer; if (gestureRecognizer != null) + { Capture(null); - - CapturedGestureRecognizer = gestureRecognizer; + } } } } diff --git a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs index 14fa242146..a2afdd0af2 100644 --- a/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/GesturesTests.cs @@ -487,6 +487,40 @@ namespace Avalonia.Base.UnitTests.Input Assert.True(raised); } + [Fact] + public void Gestures_Should_Be_Cancelled_When_Pointer_Capture_Is_Lost() + { + Border border = new Border() + { + Width = 100, + Height = 100, + Background = new SolidColorBrush(Colors.Red) + }; + border.GestureRecognizers.Add(new PinchGestureRecognizer()); + var root = new TestRoot + { + Child = border + }; + var raised = false; + + root.AddHandler(Gestures.PinchEvent, (_, _) => raised = true); + + var firstPoint = new Point(5, 5); + var secondPoint = new Point(10, 10); + + var firstTouch = new TouchTestHelper(); + var secondTouch = new TouchTestHelper(); + + firstTouch.Down(border, position: firstPoint); + + firstTouch.Cancel(); + + secondTouch.Down(border, position: secondPoint); + secondTouch.Move(border, position: new Point(20, 20)); + + Assert.False(raised); + } + [Fact] public void Scrolling_Should_Start_After_Start_Distance_Is_Exceeded() { diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs index 574599d1ad..120b0c0670 100644 --- a/tests/Avalonia.UnitTests/TouchTestHelper.cs +++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs @@ -61,5 +61,11 @@ namespace Avalonia.UnitTests Down(target, source, position, modifiers); Up(target, source, position, modifiers); } + + public void Cancel() + { + _pointer.Capture(null); + _pointer.CaptureGestureRecognizer(null); + } } }