Quellcode durchsuchen

Internal PointerCaptureChanging event (#19833)

* PointerCaptureChanging event

* PR feedback

* Announce implicit capture to platform

---------

Co-authored-by: Jan Kučera <[email protected]>
Co-authored-by: Max Katz <[email protected]>
Jan Kučera vor 3 Wochen
Ursprung
Commit
47470f9304

+ 31 - 1
src/Avalonia.Base/Input/InputElement.cs

@@ -178,6 +178,14 @@ namespace Avalonia.Input
                 nameof(PointerReleased),
                 RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
 
+        /// <summary>
+        /// Defines the <see cref="PointerCaptureChanging"/> routed event.
+        /// </summary>
+        internal static readonly RoutedEvent<PointerCaptureChangingEventArgs> PointerCaptureChangingEvent =
+            RoutedEvent.Register<InputElement, PointerCaptureChangingEventArgs>(
+                nameof(PointerCaptureChanging),
+                RoutingStrategies.Direct);
+
         /// <summary>
         /// Defines the <see cref="PointerCaptureLost"/> routed event.
         /// </summary>
@@ -240,6 +248,7 @@ namespace Avalonia.Input
             PointerMovedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerMoved(e));
             PointerPressedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerPressed(e));
             PointerReleasedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerReleased(e));
+            PointerCaptureChangingEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureChanging(e));
             PointerCaptureLostEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerCaptureLost(e));
             PointerWheelChangedEvent.AddClassHandler<InputElement>((x, e) => x.OnPointerWheelChanged(e));
 
@@ -381,9 +390,19 @@ namespace Avalonia.Input
             remove { RemoveHandler(PointerReleasedEvent, value); }
         }
 
+        /// <summary>
+        /// 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.
+        /// </summary>
+        internal event EventHandler<PointerCaptureChangingEventArgs>? PointerCaptureChanging
+        {
+            add => AddHandler(PointerCaptureChangingEvent, value);
+            remove => RemoveHandler(PointerCaptureChangingEvent, value);
+        }
+
         /// <summary>
         /// 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.
         /// </summary>
         public event EventHandler<PointerCaptureLostEventArgs>? PointerCaptureLost
         {
@@ -770,6 +789,17 @@ namespace Avalonia.Input
         /// <returns>Last focusable element if available/>.</returns>
         protected internal virtual InputElement? GetLastFocusableElementOverride() => null;
 
+        /// <summary>
+        /// Invoked when an unhandled <see cref="PointerCaptureChangingEvent"/> reaches an element in its 
+        /// route that is derived from this class. Implement this method to add class handling 
+        /// for this event.
+        /// </summary>
+        /// <param name="e">Data about the event.</param>
+        internal virtual void OnPointerCaptureChanging(PointerCaptureChangingEventArgs e)
+        {
+
+        }
+
         /// <summary>
         /// Invoked when an unhandled <see cref="PointerCaptureLostEvent"/> reaches an element in its 
         /// route that is derived from this class. Implement this method to add class handling 

+ 2 - 2
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;

+ 37 - 16
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<IInputElement>())
+                {
+                    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<IInputElement>())
+            if (oldVisual != null)
+                foreach (var notifyTarget in oldVisual.GetSelfAndVisualAncestors().OfType<IInputElement>())
                 {
                     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; }
 

+ 16 - 1
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;
+        }
+    }
 }