From fad6062094f6359fa72185378b22c0ed9c5f0478 Mon Sep 17 00:00:00 2001 From: polarboy Date: Tue, 23 Jul 2024 15:17:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20PopupPositionerExtensions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Popup/PopupPositionerExtensions.cs | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/AtomUI.Controls/Popup/PopupPositionerExtensions.cs diff --git a/src/AtomUI.Controls/Popup/PopupPositionerExtensions.cs b/src/AtomUI.Controls/Popup/PopupPositionerExtensions.cs new file mode 100644 index 0000000..f62017d --- /dev/null +++ b/src/AtomUI.Controls/Popup/PopupPositionerExtensions.cs @@ -0,0 +1,113 @@ +using AtomUI.Reflection; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Primitives.PopupPositioning; +using Avalonia.Media; +using Avalonia.VisualTree; + +namespace AtomUI.Controls; + +static class PopupPositionerExtensions +{ + public static void ConfigurePosition(ref this PopupPositionerParameters positionerParameters, + TopLevel topLevel, + Visual target, PlacementMode placement, Point offset, + PopupAnchor anchor, PopupGravity gravity, + PopupPositionerConstraintAdjustment constraintAdjustment, Rect? rect, + FlowDirection flowDirection) + { + positionerParameters.Offset = offset; + positionerParameters.ConstraintAdjustment = constraintAdjustment; + if (placement == PlacementMode.Pointer) { + // We need a better way for tracking the last pointer position + topLevel.TryGetProperty("LastPointerPosition", out var lastPointerPosition); + var position = topLevel.PointToClient(lastPointerPosition ?? default); + + positionerParameters.AnchorRectangle = new Rect(position, new Size(1, 1)); + positionerParameters.Anchor = PopupAnchor.TopLeft; + positionerParameters.Gravity = PopupGravity.BottomRight; + } else { + if (target == null) { + throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null"); + } + + 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) { + throw new InvalidOperationException("Target control is not attached to the visual tree"); + } + throw new InvalidOperationException("Target control is not in the same tree as the popup parent"); + } + + var bounds = new Rect(default, target.Bounds.Size); + var anchorRect = rect ?? bounds; + positionerParameters.AnchorRectangle = anchorRect.Intersect(bounds).TransformToAABB(matrix.Value); + + var parameters = placement switch + { + PlacementMode.Bottom => (PopupAnchor.Bottom, PopupGravity.Bottom), + PlacementMode.Right => (PopupAnchor.Right, PopupGravity.Right), + PlacementMode.Left => (PopupAnchor.Left, PopupGravity.Left), + PlacementMode.Top => (PopupAnchor.Top, PopupGravity.Top), + PlacementMode.Center => (PopupAnchor.None, PopupGravity.None), + PlacementMode.AnchorAndGravity => (anchor, gravity), + PlacementMode.TopEdgeAlignedRight => (PopupAnchor.TopRight, PopupGravity.TopLeft), + PlacementMode.TopEdgeAlignedLeft => (PopupAnchor.TopLeft, PopupGravity.TopRight), + PlacementMode.BottomEdgeAlignedLeft => (PopupAnchor.BottomLeft, PopupGravity.BottomRight), + PlacementMode.BottomEdgeAlignedRight => (PopupAnchor.BottomRight, PopupGravity.BottomLeft), + PlacementMode.LeftEdgeAlignedTop => (PopupAnchor.TopLeft, PopupGravity.BottomLeft), + PlacementMode.LeftEdgeAlignedBottom => (PopupAnchor.BottomLeft, PopupGravity.TopLeft), + PlacementMode.RightEdgeAlignedTop => (PopupAnchor.TopRight, PopupGravity.BottomRight), + PlacementMode.RightEdgeAlignedBottom => (PopupAnchor.BottomRight, PopupGravity.TopRight), + _ => throw new ArgumentOutOfRangeException(nameof(placement), placement, + "Invalid value for Popup.PlacementMode") + }; + positionerParameters.Anchor = parameters.Item1; + positionerParameters.Gravity = parameters.Item2; + } + + // Invert coordinate system if FlowDirection is RTL + if (flowDirection == FlowDirection.RightToLeft) { + if ((positionerParameters.Anchor & PopupAnchor.Right) == PopupAnchor.Right) { + positionerParameters.Anchor ^= PopupAnchor.Right; + positionerParameters.Anchor |= PopupAnchor.Left; + } else if ((positionerParameters.Anchor & PopupAnchor.Left) == PopupAnchor.Left) { + positionerParameters.Anchor ^= PopupAnchor.Left; + positionerParameters.Anchor |= PopupAnchor.Right; + } + + if ((positionerParameters.Gravity & PopupGravity.Right) == PopupGravity.Right) { + positionerParameters.Gravity ^= PopupGravity.Right; + positionerParameters.Gravity |= PopupGravity.Left; + } else if ((positionerParameters.Gravity & PopupGravity.Left) == PopupGravity.Left) { + positionerParameters.Gravity ^= PopupGravity.Left; + positionerParameters.Gravity |= PopupGravity.Right; + } + } + } + + 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.GetVisualParent(); + } + + adorned = null; + adornerLayer = null; + return false; + } +} \ No newline at end of file