diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index 4029782772..c0ea14037c 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -468,7 +468,16 @@ namespace Avalonia.Controls.Primitives.PopupPositioning { if (target == null) throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null"); - var matrix = target.TransformToVisual(topLevel); + Matrix? matrix; + if (TryGetAdorner(target, out var adorned, out var adornerLayer)) + { + matrix = adorned!.TransformToVisual(topLevel) * target.TransformToVisual(adornerLayer!); + } + else + { + matrix = target.TransformToVisual(topLevel); + } + if (matrix == null) { if (target.GetVisualRoot() == null) @@ -529,6 +538,25 @@ namespace Avalonia.Controls.Primitives.PopupPositioning } } } + + private static bool TryGetAdorner(Visual target, out Visual? adorned, out Visual? adornerLayer) + { + var element = target; + while (element != null) + { + if (AdornerLayer.GetAdornedElement(element) is { } adornedElement) + { + adorned = adornedElement; + adornerLayer = AdornerLayer.GetAdornerLayer(adorned); + return true; + } + element = element.VisualParent; + } + + adorned = null; + adornerLayer = null; + return false; + } } } diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs index 5a0c60dff6..8398da59a5 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs @@ -1143,6 +1143,53 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Popup_Attached_To_Adorner_Respects_Adorner_Position() + { + using (CreateServices()) + { + var popupTarget = new Border() { Height = 30, Background = Brushes.Red, [DockPanel.DockProperty] = Dock.Top }; + var popupContent = new Border() { Height = 30, Width = 50, Background = Brushes.Yellow }; + var popup = new Popup + { + Child = popupContent, + Placement = PlacementMode.AnchorAndGravity, + PlacementTarget = popupTarget, + PlacementAnchor = PopupAnchor.BottomRight, + PlacementGravity = PopupGravity.BottomRight + }; + var adorner = new DockPanel() { Children = { popupTarget, popup }, + HorizontalAlignment = HorizontalAlignment.Left, + Width = 40, + Margin = new Thickness(50, 5, 0, 0) }; + + var adorned = new Border() { + Width = 100, + Height = 100, + Background = Brushes.Blue, + [Canvas.LeftProperty] = 20, + [Canvas.TopProperty] = 40 + }; + var windowContent = new Canvas(); + windowContent.Children.Add(adorned); + + var root = PreparedWindow(windowContent); + + var adornerLayer = AdornerLayer.GetAdornerLayer(adorned); + adornerLayer.Children.Add(adorner); + AdornerLayer.SetAdornedElement(adorner, adorned); + + root.LayoutManager.ExecuteInitialLayoutPass(); + popup.Open(); + Dispatcher.UIThread.RunJobs(DispatcherPriority.AfterRender); + + // X: Adorned Canvas.Left + Adorner Margin Left + Adorner Width + // Y: Adorned Canvas.Top + Adorner Margin Top + Adorner Height + Assert.Equal(new PixelPoint(110, 75), popupContent.PointToScreen(new Point(0, 0))); + } + } + + private static PopupRoot CreateRoot(TopLevel popupParent, IPopupImpl impl = null) { impl ??= popupParent.PlatformImpl.CreatePopup();