Browse Source

Raise pointer events on captured element (#18420)

Julien Lebosquain 7 months ago
parent
commit
3c7c469018

+ 12 - 2
src/Avalonia.Base/Input/PointerOverPreProcessor.cs

@@ -79,7 +79,9 @@ namespace Avalonia.Input
                 else if (pointerDevice.TryGetPointer(args) is { } pointer &&
                 else if (pointerDevice.TryGetPointer(args) is { } pointer &&
                     pointer.Type != PointerType.Touch)
                     pointer.Type != PointerType.Touch)
                 {
                 {
-                    var element = pointer.Captured ?? args.InputHitTestResult.firstEnabledAncestor;
+                    var element = GetEffectivePointerOverElement(
+                        args.InputHitTestResult.firstEnabledAncestor,
+                        pointer.Captured);
 
 
                     SetPointerOver(pointer, args.Root, element, args.Timestamp, args.Position,
                     SetPointerOver(pointer, args.Root, element, args.Timestamp, args.Position,
                         new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()),
                         new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()),
@@ -96,7 +98,10 @@ namespace Avalonia.Input
 
 
                 if (dirtyRect.Contains(clientPoint))
                 if (dirtyRect.Contains(clientPoint))
                 {
                 {
-                    var element = pointer.Captured ?? _inputRoot.InputHitTest(clientPoint);
+                    var element = GetEffectivePointerOverElement(
+                        _inputRoot.InputHitTest(clientPoint),
+                        pointer.Captured);
+
                     SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
                     SetPointerOver(pointer, _inputRoot, element, 0, clientPoint, PointerPointProperties.None, KeyModifiers.None);
                 }
                 }
                 else if (!((Visual)_inputRoot).Bounds.Contains(clientPoint))
                 else if (!((Visual)_inputRoot).Bounds.Contains(clientPoint))
@@ -106,6 +111,11 @@ namespace Avalonia.Input
             }
             }
         }
         }
 
 
+        private static IInputElement? GetEffectivePointerOverElement(IInputElement? hitTestElement, IInputElement? captured)
+            => captured is not null && hitTestElement != captured ?
+                null :
+                hitTestElement;
+
         private void ClearPointerOver()
         private void ClearPointerOver()
         {
         {
             if (_currentPointer is (var pointer, var position))
             if (_currentPointer is (var pointer, var position))

+ 13 - 2
tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs

@@ -120,7 +120,7 @@ namespace Avalonia.Base.UnitTests.Input
         }
         }
 
 
         [Fact]
         [Fact]
-        public void HitTest_Should_Be_Ignored_If_Element_Captured()
+        public void HitTest_Should_Ignore_Non_Captured_Elements()
         {
         {
             using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
             using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager()));
 
 
@@ -145,8 +145,19 @@ namespace Avalonia.Base.UnitTests.Input
                 }
                 }
             }, renderer.Object);
             }, renderer.Object);
 
 
-            SetHit(renderer, canvas);
             pointer.SetupGet(p => p.Captured).Returns(decorator);
             pointer.SetupGet(p => p.Captured).Returns(decorator);
+
+            // Move the pointer over the canvas: the captured decorator should lose the pointer over state.
+            SetHit(renderer, canvas);
+            impl.Object.Input!(CreateRawPointerMovedArgs(device, root));
+
+            Assert.False(decorator.IsPointerOver);
+            Assert.False(border.IsPointerOver);
+            Assert.False(canvas.IsPointerOver);
+            Assert.False(root.IsPointerOver);
+
+            // Move back the pointer over the decorator: raise events normally for it since it's captured.
+            SetHit(renderer, decorator);
             impl.Object.Input!(CreateRawPointerMovedArgs(device, root));
             impl.Object.Input!(CreateRawPointerMovedArgs(device, root));
 
 
             Assert.True(decorator.IsPointerOver);
             Assert.True(decorator.IsPointerOver);