diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs
index 6bb5733a19..340a006ed1 100644
--- a/src/Avalonia.Base/Input/MouseDevice.cs
+++ b/src/Avalonia.Base/Input/MouseDevice.cs
@@ -220,9 +220,7 @@ namespace Avalonia.Input
}
finally
{
- _pointer.Capture(null, CaptureSource.Implicit);
- _pointer.CaptureGestureRecognizer(null);
- _pointer.IsGestureRecognitionSkipped = false;
+ _pointer.CaptureLost(CaptureSource.Implicit);
_lastMouseDownButton = default;
}
return e.Handled;
diff --git a/src/Avalonia.Base/Input/PenDevice.cs b/src/Avalonia.Base/Input/PenDevice.cs
index f7211ea351..b7dba977d5 100644
--- a/src/Avalonia.Base/Input/PenDevice.cs
+++ b/src/Avalonia.Base/Input/PenDevice.cs
@@ -63,6 +63,9 @@ namespace Avalonia.Input
case RawPointerEventType.LeaveWindow:
shouldReleasePointer = true;
break;
+ case RawPointerEventType.CancelCapture:
+ pointer.PlatformCaptureLost();
+ break;
case RawPointerEventType.LeftButtonDown:
case RawPointerEventType.RightButtonDown:
case RawPointerEventType.MiddleButtonDown:
@@ -90,8 +93,8 @@ namespace Avalonia.Input
{
if (shouldReleasePointer)
{
- pointer.Dispose();
_pointers.Remove(e.RawPointerId);
+ pointer.Dispose();
}
}
}
@@ -173,9 +176,7 @@ namespace Avalonia.Input
}
finally
{
- pointer.Capture(null, CaptureSource.Implicit);
- pointer.CaptureGestureRecognizer(null);
- pointer.IsGestureRecognitionSkipped = false;
+ pointer.CaptureLost(CaptureSource.Implicit);
_lastMouseDownButton = default;
}
diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs
index 9b751baf12..47aab499ce 100644
--- a/src/Avalonia.Base/Input/Pointer.cs
+++ b/src/Avalonia.Base/Input/Pointer.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using Avalonia.Input.GestureRecognizers;
using Avalonia.VisualTree;
@@ -18,6 +19,8 @@ namespace Avalonia.Input
private static int s_NextFreePointerId = 1000;
public static int GetNextFreeId() => s_NextFreePointerId++;
+ private bool _disposed;
+
public Pointer(int id, PointerType type, bool isPrimary)
{
Id = id;
@@ -40,10 +43,25 @@ namespace Avalonia.Input
}
- internal void PlatformCaptureLost()
+ internal void PlatformCaptureLost() => CaptureLost(CaptureSource.Platform);
+
+ ///
+ /// Ends every capture the pointer holds, on the element and on a gesture recognizer.
+ ///
+ internal void CaptureLost(CaptureSource source)
+ {
+ if (_disposed)
+ return;
+
+ CaptureLostCore(source);
+ }
+
+ private void CaptureLostCore(CaptureSource source)
{
if (Captured != null)
- Capture(null, CaptureSource.Platform);
+ CaptureCore(null, source);
+ CaptureGestureRecognizerCore(null);
+ IsGestureRecognitionSkipped = false;
}
public void Capture(IInputElement? control)
@@ -52,6 +70,17 @@ namespace Avalonia.Input
}
internal void Capture(IInputElement? control, CaptureSource source)
+ {
+ if (_disposed)
+ {
+ Debug.Assert(control is null, "Capturing a pointer that no longer exists.");
+ return;
+ }
+
+ CaptureCore(control, source);
+ }
+
+ private void CaptureCore(IInputElement? control, CaptureSource source)
{
var oldCapture = Captured;
var oldSource = CaptureSource;
@@ -148,7 +177,14 @@ namespace Avalonia.Input
public void Dispose()
{
- // callers are responsible for calling Capture(null, source) with an appropriate source
+ if (_disposed)
+ return;
+
+ // Mark the pointer gone first, so a capture lost handler can't capture it again.
+ // It no longer exists, so the platform is the only source the release can come from.
+ _disposed = true;
+
+ CaptureLostCore(CaptureSource.Platform);
}
///
@@ -156,6 +192,17 @@ namespace Avalonia.Input
///
/// The gesture recognizer.
internal void CaptureGestureRecognizer(GestureRecognizer? gestureRecognizer)
+ {
+ if (_disposed)
+ {
+ Debug.Assert(gestureRecognizer is null, "Capturing a pointer that no longer exists to a gesture recognizer.");
+ return;
+ }
+
+ CaptureGestureRecognizerCore(gestureRecognizer);
+ }
+
+ private void CaptureGestureRecognizerCore(GestureRecognizer? gestureRecognizer)
{
if (CapturedGestureRecognizer != gestureRecognizer)
{
diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs
index 6429d9c0fc..dac12dedd8 100644
--- a/src/Avalonia.Base/Input/TouchDevice.cs
+++ b/src/Avalonia.Base/Input/TouchDevice.cs
@@ -109,20 +109,14 @@ namespace Avalonia.Input
{
target.RaiseEvent(e);
}
- pointer?.Capture(null, CaptureSource.Implicit);
+ pointer?.CaptureLost(CaptureSource.Implicit);
}
}
if (args.Type == RawPointerEventType.TouchCancel)
{
_pointers.Remove(args.RawPointerId);
- using (pointer)
- {
- pointer?.Capture(null, CaptureSource.Platform);
- pointer?.CaptureGestureRecognizer(null);
- if (pointer != null)
- pointer.IsGestureRecognitionSkipped = false;
- }
+ pointer?.Dispose();
}
if (args.Type == RawPointerEventType.TouchUpdate)
@@ -165,7 +159,7 @@ namespace Avalonia.Input
internal void PlatformCaptureLost()
{
foreach (var pointer in _pointers.Values)
- pointer.Capture(null);
+ pointer.PlatformCaptureLost();
}
}
}
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 18aa162608..0d3ed14bdc 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -987,8 +987,8 @@ namespace Avalonia.Win32.Interop
POINTER_FLAG_FOURTHBUTTON = 0x00000080,
POINTER_FLAG_FIFTHBUTTON = 0x00000100,
POINTER_FLAG_PRIMARY = 0x00002000,
- POINTER_FLAG_CONFIDENCE = 0x00000400,
- POINTER_FLAG_CANCELED = 0x00000800,
+ POINTER_FLAG_CONFIDENCE = 0x00004000,
+ POINTER_FLAG_CANCELED = 0x00008000,
POINTER_FLAG_DOWN = 0x00010000,
POINTER_FLAG_UPDATE = 0x00020000,
POINTER_FLAG_UP = 0x00040000,
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 711481d3d5..a8dbe219d2 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -583,7 +583,6 @@ namespace Avalonia.Win32
break;
}
case WindowsMessage.WM_POINTERLEAVE:
- case WindowsMessage.WM_POINTERCAPTURECHANGED:
{
if (!_wmPointerEnabled)
{
@@ -594,6 +593,17 @@ namespace Avalonia.Win32
e = CreatePointerArgs(device, timestamp, eventType, point, modifiers, info.pointerId);
break;
}
+ case WindowsMessage.WM_POINTERCAPTURECHANGED:
+ {
+ if (!_wmPointerEnabled)
+ {
+ break;
+ }
+ GetDevicePointerInfo(wParam, out var device, out var info, out var point, out var modifiers, ref timestamp);
+ var eventType = device is TouchDevice ? RawPointerEventType.TouchCancel : RawPointerEventType.CancelCapture;
+ e = CreatePointerArgs(device, timestamp, eventType, point, modifiers, info.pointerId);
+ break;
+ }
case WindowsMessage.WM_POINTERWHEEL:
case WindowsMessage.WM_POINTERHWHEEL:
{
@@ -1300,7 +1310,7 @@ namespace Avalonia.Win32
var isTouch = info.pointerType == PointerInputType.PT_TOUCH;
if (info.pointerFlags.HasFlag(PointerFlags.POINTER_FLAG_CANCELED))
{
- return isTouch ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow;
+ return isTouch ? RawPointerEventType.TouchCancel : RawPointerEventType.CancelCapture;
}
var eventType = ToEventType(info.ButtonChangeType, isTouch);
diff --git a/tests/Avalonia.UnitTests/MouseTestHelper.cs b/tests/Avalonia.UnitTests/MouseTestHelper.cs
index 082ae718f3..b8eeb2aa91 100644
--- a/tests/Avalonia.UnitTests/MouseTestHelper.cs
+++ b/tests/Avalonia.UnitTests/MouseTestHelper.cs
@@ -101,9 +101,7 @@ namespace Avalonia.UnitTests
else
target.RaiseEvent(e);
- _pointer.Capture(null);
- _pointer.CaptureGestureRecognizer(null);
- _pointer.IsGestureRecognitionSkipped = false;
+ _pointer.CaptureLost(CaptureSource.Explicit);
}
else
Move(target, source, position ?? default);
diff --git a/tests/Avalonia.UnitTests/TouchTestHelper.cs b/tests/Avalonia.UnitTests/TouchTestHelper.cs
index 7a13425997..73909cdd79 100644
--- a/tests/Avalonia.UnitTests/TouchTestHelper.cs
+++ b/tests/Avalonia.UnitTests/TouchTestHelper.cs
@@ -64,9 +64,7 @@ namespace Avalonia.UnitTests
public void Cancel()
{
- _pointer.Capture(null);
- _pointer.CaptureGestureRecognizer(null);
- _pointer.IsGestureRecognitionSkipped = false;
+ _pointer.CaptureLost(CaptureSource.Platform);
}
}
}