diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs index b989def335..e79e0c3591 100644 --- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs +++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs @@ -79,7 +79,9 @@ namespace Avalonia.Input else if (pointerDevice.TryGetPointer(args) is { } pointer && 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, new PointerPointProperties(args.InputModifiers, args.Type.ToUpdateKind()), @@ -96,7 +98,10 @@ namespace Avalonia.Input 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); } 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() { if (_currentPointer is (var pointer, var position)) diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs index 4f9531cd63..0c1e790bac 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs @@ -120,7 +120,7 @@ namespace Avalonia.Base.UnitTests.Input } [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())); @@ -145,8 +145,19 @@ namespace Avalonia.Base.UnitTests.Input } }, renderer.Object); - SetHit(renderer, canvas); 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)); Assert.True(decorator.IsPointerOver);