diff --git a/src/Avalonia.Base/Input/InputElement.cs b/src/Avalonia.Base/Input/InputElement.cs
index 8ce8e24c19..e20a6cb1af 100644
--- a/src/Avalonia.Base/Input/InputElement.cs
+++ b/src/Avalonia.Base/Input/InputElement.cs
@@ -166,6 +166,14 @@ namespace Avalonia.Input
nameof(PointerReleased),
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
+ ///
+ /// Defines the routed event.
+ ///
+ internal static readonly RoutedEvent PointerCaptureChangingEvent =
+ RoutedEvent.Register(
+ nameof(PointerCaptureChanging),
+ RoutingStrategies.Direct);
+
///
/// Defines the routed event.
///
@@ -221,6 +229,7 @@ namespace Avalonia.Input
PointerMovedEvent.AddClassHandler((x, e) => x.OnPointerMoved(e));
PointerPressedEvent.AddClassHandler((x, e) => x.OnPointerPressed(e));
PointerReleasedEvent.AddClassHandler((x, e) => x.OnPointerReleased(e));
+ PointerCaptureChangingEvent.AddClassHandler((x, e) => x.OnPointerCaptureChanging(e));
PointerCaptureLostEvent.AddClassHandler((x, e) => x.OnPointerCaptureLost(e));
PointerWheelChangedEvent.AddClassHandler((x, e) => x.OnPointerWheelChanged(e));
@@ -339,9 +348,19 @@ namespace Avalonia.Input
remove { RemoveHandler(PointerReleasedEvent, value); }
}
+ ///
+ /// Occurs when the control or its child control is about to lose capture,
+ /// event will not be triggered for a parent control if capture was transferred to another child of that parent control.
+ ///
+ internal event EventHandler? PointerCaptureChanging
+ {
+ add => AddHandler(PointerCaptureChangingEvent, value);
+ remove => RemoveHandler(PointerCaptureChangingEvent, value);
+ }
+
///
/// Occurs when the control or its child control loses the pointer capture for any reason,
- /// event will not be triggered for a parent control if capture was transferred to another child of that parent control
+ /// event will not be triggered for a parent control if capture was transferred to another child of that parent control.
///
public event EventHandler? PointerCaptureLost
{
@@ -671,6 +690,17 @@ namespace Avalonia.Input
}
}
+ ///
+ /// Invoked when an unhandled reaches an element in its
+ /// route that is derived from this class. Implement this method to add class handling
+ /// for this event.
+ ///
+ /// Data about the event.
+ internal virtual void OnPointerCaptureChanging(PointerCaptureChangingEventArgs e)
+ {
+
+ }
+
///
/// Invoked when an unhandled reaches an element in its
/// route that is derived from this class. Implement this method to add class handling
diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs
index b09f195656..49945f1e8a 100644
--- a/src/Avalonia.Base/Input/MouseDevice.cs
+++ b/src/Avalonia.Base/Input/MouseDevice.cs
@@ -131,7 +131,7 @@ namespace Avalonia.Input
if (source != null)
{
- _pointer.Capture(source);
+ _pointer.Capture(source, CaptureSource.Implicit);
var settings = ((IInputRoot?)(source as Interactive)?.GetVisualRoot())?.PlatformSettings;
if (settings is not null)
@@ -206,7 +206,7 @@ namespace Avalonia.Input
}
finally
{
- _pointer.Capture(null);
+ _pointer.Capture(null, CaptureSource.Implicit);
_pointer.CaptureGestureRecognizer(null);
_pointer.IsGestureRecognitionSkipped = false;
_lastMouseDownButton = default;
diff --git a/src/Avalonia.Base/Input/Pointer.cs b/src/Avalonia.Base/Input/Pointer.cs
index 1f6741a09e..f243a2e382 100644
--- a/src/Avalonia.Base/Input/Pointer.cs
+++ b/src/Avalonia.Base/Input/Pointer.cs
@@ -6,6 +6,13 @@ using Avalonia.VisualTree;
namespace Avalonia.Input
{
+ internal enum CaptureSource
+ {
+ Explicit,
+ Implicit,
+ Platform
+ }
+
public class Pointer : IPointer, IDisposable
{
private static int s_NextFreePointerId = 1000;
@@ -30,46 +37,60 @@ namespace Avalonia.Input
protected virtual void PlatformCapture(IInputElement? element)
{
-
+
}
internal void PlatformCaptureLost()
{
if (Captured != null)
- Capture(null, platformInitiated: true);
+ Capture(null, CaptureSource.Platform);
}
public void Capture(IInputElement? control)
{
- Capture(control, platformInitiated: false);
+ Capture(control, CaptureSource.Explicit);
}
- private void Capture(IInputElement? control, bool platformInitiated)
+ internal void Capture(IInputElement? control, CaptureSource source)
{
var oldCapture = Captured;
if (oldCapture == control)
return;
- if (oldCapture is Visual v1)
- v1.DetachedFromVisualTree -= OnCaptureDetached;
+ var oldVisual = oldCapture as Visual;
+
+ IInputElement? commonParent = null;
+ if (oldVisual != null)
+ {
+ commonParent = FindCommonParent(control, oldCapture);
+ foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType())
+ {
+ if (notifyTarget == commonParent)
+ break;
+ var args = new PointerCaptureChangingEventArgs(notifyTarget, this, control, source);
+ notifyTarget.RaiseEvent(args);
+ if (args.Handled)
+ return;
+ }
+ }
+
+ if (oldVisual != null)
+ oldVisual.DetachedFromVisualTree -= OnCaptureDetached;
Captured = control;
-
- if (!platformInitiated)
+
+ if (source != CaptureSource.Platform)
PlatformCapture(control);
- if (oldCapture is Visual v2)
- {
- var commonParent = FindCommonParent(control, oldCapture);
- foreach (var notifyTarget in v2.GetSelfAndVisualAncestors().OfType())
+ if (oldVisual != null)
+ foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType())
{
if (notifyTarget == commonParent)
break;
notifyTarget.RaiseEvent(new PointerCaptureLostEventArgs(notifyTarget, this));
}
- }
- if (Captured is Visual v3)
- v3.DetachedFromVisualTree += OnCaptureDetached;
+ if (Captured is Visual newVisual)
+ newVisual.DetachedFromVisualTree += OnCaptureDetached;
if (Captured != null)
CaptureGestureRecognizer(null);
@@ -92,7 +113,7 @@ namespace Avalonia.Input
public IInputElement? Captured { get; private set; }
-
+
public PointerType Type { get; }
public bool IsPrimary { get; }
diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs
index bbcc5cccd8..7682b0fb22 100644
--- a/src/Avalonia.Base/Input/PointerEventArgs.cs
+++ b/src/Avalonia.Base/Input/PointerEventArgs.cs
@@ -202,11 +202,26 @@ namespace Avalonia.Input
{
public IPointer Pointer { get; }
- [Unstable("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.,")]
+ [Unstable("This constructor might be removed in 12.0. If you need to remove capture, use stable methods on the IPointer instance.")]
public PointerCaptureLostEventArgs(object source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
{
Pointer = pointer;
Source = source;
}
}
+
+ internal class PointerCaptureChangingEventArgs : RoutedEventArgs
+ {
+ public IPointer Pointer { get; }
+ public CaptureSource CaptureSource { get; }
+ public IInputElement? NewValue { get; }
+
+ internal PointerCaptureChangingEventArgs(object source, IPointer pointer, IInputElement? newValue, CaptureSource captureSource) : base(InputElement.PointerCaptureChangingEvent)
+ {
+ Pointer = pointer;
+ Source = source;
+ NewValue = newValue;
+ CaptureSource = captureSource;
+ }
+ }
}