diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs index fa03cc7be8..3a71b32685 100644 --- a/src/Avalonia.Controls/TopLevel.cs +++ b/src/Avalonia.Controls/TopLevel.cs @@ -866,7 +866,7 @@ namespace Avalonia.Controls var candidate = hitTestElement; while (candidate?.IsEffectivelyEnabled == false) { - candidate = (candidate as Visual)?.Parent as IInputElement; + candidate = (candidate as Visual)?.VisualParent as IInputElement; } return candidate; diff --git a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs index 45277ca75f..4f9531cd63 100644 --- a/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/PointerOverTests.cs @@ -6,6 +6,7 @@ using Avalonia.Controls; using Avalonia.Headless; using Avalonia.Input; using Avalonia.Input.Raw; +using Avalonia.Media; using Avalonia.Rendering; using Avalonia.UnitTests; @@ -494,6 +495,60 @@ namespace Avalonia.Base.UnitTests.Input result); } + [Fact] + public void Disabled_Element_Should_Set_PointerOver_On_Visual_Parent() + { + using var app = UnitTestApplication.Start(new TestServices(inputManager: new InputManager())); + + var renderer = new Mock(); + var deviceMock = CreatePointerDeviceMock(); + var impl = CreateTopLevelImplMock(); + + var disabledChild = new Border + { + Background = Brushes.Red, + Width = 100, + Height = 100, + IsEnabled = false + }; + + var visualParent = new Border + { + Background = Brushes.Black, + Width = 100, + Height = 100, + Child = disabledChild + }; + + var logicalParent = new Border + { + Background = Brushes.Blue, + Width = 100, + Height = 100 + }; + + // Change the logical parent and check that we're correctly hit testing on the visual tree. + // This scenario is made up because it's easy to test. + // In the real world, this happens with nested Popups from MenuItems (but that's very cumbersome to test). + ((ISetLogicalParent) disabledChild).SetParent(null); + ((ISetLogicalParent) disabledChild).SetParent(logicalParent); + + var root = CreateInputRoot( + impl.Object, + new Panel + { + Children = { visualParent } + }, + renderer.Object); + + Assert.False(visualParent.IsPointerOver); + SetHit(renderer, disabledChild); + + impl.Object.Input!(CreateRawPointerMovedArgs(deviceMock.Object, root, new Point(50, 50))); + Assert.True(visualParent.IsPointerOver); + Assert.False(logicalParent.IsPointerOver); + } + private static void AddEnteredExitedHandlers( EventHandler handler, params IInputElement[] controls)