From a030d29d15f175bce7f565ef30847b1ec316d1ae Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 25 Aug 2023 13:40:48 +0000 Subject: [PATCH 1/5] add failing test --- .../Input/GesturesTests.cs | 34 +++++++++++++++++++ tests/Avalonia.UnitTests/TouchTestHelper.cs | 6 ++++ 2 files changed, 40 insertions(+) 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); + } } } From c3d524d80d1de25e54819026f77ae3c28282ec20 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 25 Aug 2023 13:44:16 +0000 Subject: [PATCH 2/5] ensure gestures receive capture lost event --- .../GestureRecognizers/GestureRecognizer.cs | 1 + .../GestureRecognizerCollection.cs | 21 +++++++++++++++++++ .../PinchGestureRecognizer.cs | 7 ++++++- src/Avalonia.Base/Input/InputElement.cs | 6 ++++++ src/Avalonia.Base/Input/Pointer.cs | 9 ++++++-- 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs index 2cb97b4707..cdd7ef7cd2 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs @@ -3,6 +3,7 @@ public abstract class GestureRecognizer : StyledElement { protected internal IInputElement? Target { get; internal set; } + protected internal GestureRecognizerCollection? GestureRecognizerCollection { get; internal set; } protected abstract void PointerPressed(PointerPressedEventArgs e); protected abstract void PointerReleased(PointerReleasedEventArgs e); diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index 05dce8214b..a17c8cdcc7 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; @@ -24,8 +25,14 @@ namespace Avalonia.Input.GestureRecognizers _recognizers = new List(); } + if(recognizer.GestureRecognizerCollection != null && recognizer.GestureRecognizerCollection != this) + { + throw new InvalidOperationException("The gesture recognizer has already been added to a gesture recognizer collection"); + } + _recognizers.Add(recognizer); recognizer.Target = _inputElement; + recognizer.GestureRecognizerCollection = this; // Hacks to make bindings work @@ -59,6 +66,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..2a6dcba0f0 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 { @@ -61,6 +62,7 @@ namespace Avalonia.Input { if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { + Debug.WriteLine($"Pointer with ID: {e.Pointer.Id} pressed"); if (_firstContact == null) { _firstContact = e.Pointer; @@ -110,6 +112,9 @@ namespace Avalonia.Input _secondContact = null; } + + Debug.WriteLine($"Pointer with ID: {pointer.Id} removed"); + 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..f0e6e5bc64 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -91,12 +91,17 @@ namespace Avalonia.Input internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer) { if (CapturedGestureRecognizer != gestureRecognizer) + { CapturedGestureRecognizer?.PointerCaptureLostInternal(this); + } + + CapturedGestureRecognizer = gestureRecognizer; if (gestureRecognizer != null) + { + gestureRecognizer.GestureRecognizerCollection?.HandleCaptureLost(this); Capture(null); - - CapturedGestureRecognizer = gestureRecognizer; + } } } } From 4b685530683227dd309bfc2131f00c77512114c0 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 25 Aug 2023 13:59:14 +0000 Subject: [PATCH 3/5] remove redundant capture lost call --- src/Avalonia.Base/Input/Pointer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs index f0e6e5bc64..e82432a00d 100644 --- a/src/Avalonia.Base/Input/Pointer.cs +++ b/src/Avalonia.Base/Input/Pointer.cs @@ -99,7 +99,6 @@ namespace Avalonia.Input if (gestureRecognizer != null) { - gestureRecognizer.GestureRecognizerCollection?.HandleCaptureLost(this); Capture(null); } } From d77bbea8078a8b223620d590bcddd74d0f91f4f6 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Fri, 25 Aug 2023 14:03:48 +0000 Subject: [PATCH 4/5] removed testing property --- .../Input/GestureRecognizers/GestureRecognizer.cs | 1 - .../Input/GestureRecognizers/GestureRecognizerCollection.cs | 6 ------ 2 files changed, 7 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs index cdd7ef7cd2..2cb97b4707 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizer.cs @@ -3,7 +3,6 @@ public abstract class GestureRecognizer : StyledElement { protected internal IInputElement? Target { get; internal set; } - protected internal GestureRecognizerCollection? GestureRecognizerCollection { get; internal set; } protected abstract void PointerPressed(PointerPressedEventArgs e); protected abstract void PointerReleased(PointerReleasedEventArgs e); diff --git a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs index a17c8cdcc7..74e8061292 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/GestureRecognizerCollection.cs @@ -25,14 +25,8 @@ namespace Avalonia.Input.GestureRecognizers _recognizers = new List(); } - if(recognizer.GestureRecognizerCollection != null && recognizer.GestureRecognizerCollection != this) - { - throw new InvalidOperationException("The gesture recognizer has already been added to a gesture recognizer collection"); - } - _recognizers.Add(recognizer); recognizer.Target = _inputElement; - recognizer.GestureRecognizerCollection = this; // Hacks to make bindings work From f81b4546f801140e28d072b6d68da172fc22a8b7 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Sat, 26 Aug 2023 10:48:03 +0000 Subject: [PATCH 5/5] remove debug code --- .../Input/GestureRecognizers/PinchGestureRecognizer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs index 2a6dcba0f0..5a22ba7a14 100644 --- a/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs +++ b/src/Avalonia.Base/Input/GestureRecognizers/PinchGestureRecognizer.cs @@ -62,7 +62,6 @@ namespace Avalonia.Input { if (Target != null && Target is Visual visual && (e.Pointer.Type == PointerType.Touch || e.Pointer.Type == PointerType.Pen)) { - Debug.WriteLine($"Pointer with ID: {e.Pointer.Id} pressed"); if (_firstContact == null) { _firstContact = e.Pointer; @@ -113,8 +112,6 @@ namespace Avalonia.Input _secondContact = null; } - Debug.WriteLine($"Pointer with ID: {pointer.Id} removed"); - Target?.RaiseEvent(new PinchEndedEventArgs()); } }