From 79a6da9325e77bb46ccf5c2e7047bcb8053a3da8 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 20 Dec 2025 03:52:23 +0500 Subject: [PATCH 1/3] Remove adorner handling hack from the compositor --- .../Input/TextInput/InputMethodManager.cs | 2 +- .../TextInput/TransformTrackingHelper.cs | 26 ++- .../ServerCompositionContainerVisual.cs | 13 +- .../Server/ServerCompositionTarget.cs | 9 - ...ServerCompositionVisual.DirtyProperties.cs | 2 - .../Server/ServerCompositionVisual.cs | 26 +-- src/Avalonia.Base/composition-schema.xml | 2 - .../Primitives/AdornerHelper.cs | 180 ++++++++++++++++++ .../Primitives/AdornerLayer.cs | 82 ++++---- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- 10 files changed, 258 insertions(+), 86 deletions(-) create mode 100644 src/Avalonia.Controls/Primitives/AdornerHelper.cs diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index ae3a8bc6a1..005a015644 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -10,7 +10,7 @@ namespace Avalonia.Input.TextInput private IInputElement? _focusedElement; private Interactive? _visualRoot; private TextInputMethodClient? _client; - private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper(); + private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper(true); public TextInputMethodManager() { diff --git a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs index 1ea754f0f4..418acbf42f 100644 --- a/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs +++ b/src/Avalonia.Base/Input/TextInput/TransformTrackingHelper.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.Reactive; using Avalonia.Threading; using Avalonia.VisualTree; @@ -7,13 +9,15 @@ namespace Avalonia.Input.TextInput { class TransformTrackingHelper : IDisposable { + private readonly bool _deferAfterRenderPass; private Visual? _visual; private bool _queuedForUpdate; private readonly EventHandler _propertyChangedHandler; private readonly List _propertyChangedSubscriptions = new List(); - public TransformTrackingHelper() + public TransformTrackingHelper(bool deferAfterRenderPass) { + _deferAfterRenderPass = deferAfterRenderPass; _propertyChangedHandler = PropertyChangedHandler; } @@ -91,7 +95,10 @@ namespace Avalonia.Input.TextInput if(_queuedForUpdate) return; _queuedForUpdate = true; - Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.AfterRender); + if (_deferAfterRenderPass) + Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.AfterRender); + else + MediaContext.Instance.BeginInvokeOnRender(UpdateMatrix); } private void PropertyChangedHandler(object? sender, AvaloniaPropertyChangedEventArgs e) @@ -106,12 +113,23 @@ namespace Avalonia.Input.TextInput UpdateMatrix(); } - public static IDisposable Track(Visual visual, Action cb) + public static IDisposable Track(Visual visual, bool deferAfterRenderPass, Action cb) { - var rv = new TransformTrackingHelper(); + var rv = new TransformTrackingHelper(deferAfterRenderPass); rv.MatrixChanged += () => cb(visual, rv.Matrix); rv.SetVisual(visual); return rv; } + + public static IObservable Observe(Visual visual, bool deferAfterRenderPass) + { + return Observable.Create(observer => + { + var rv = new TransformTrackingHelper(deferAfterRenderPass); + rv.MatrixChanged += () => observer.OnNext(rv.Matrix); + rv.SetVisual(visual); + return rv; + }); + } } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs index 396009841b..e9bfa8f6a0 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionContainerVisual.cs @@ -34,15 +34,10 @@ namespace Avalonia.Rendering.Composition.Server var (combinedBounds, oldInvalidated, newInvalidated) = base.Update(root, parentCombinedTransform); foreach (var child in Children) { - if (child.AdornedVisual != null) - root.EnqueueAdornerUpdate(child); - else - { - var res = child.Update(root, GlobalTransformMatrix); - oldInvalidated |= res.InvalidatedOld; - newInvalidated |= res.InvalidatedNew; - combinedBounds = LtrbRect.FullUnion(combinedBounds, res.Bounds); - } + var res = child.Update(root, GlobalTransformMatrix); + oldInvalidated |= res.InvalidatedOld; + newInvalidated |= res.InvalidatedNew; + combinedBounds = LtrbRect.FullUnion(combinedBounds, res.Bounds); } // If effect is changed, we need to clean both old and new bounds diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs index 90b973c3a8..48ce97c908 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs @@ -31,7 +31,6 @@ namespace Avalonia.Rendering.Composition.Server private bool _fullRedrawRequested; private bool _disposed; private readonly HashSet _attachedVisuals = new(); - private readonly Queue _adornerUpdateQueue = new(); public long Id { get; } public ulong Revision { get; private set; } @@ -129,12 +128,6 @@ namespace Avalonia.Rendering.Composition.Server // Update happens in a separate phase to extend dirty rect if needed Root.Update(this, transform); - while (_adornerUpdateQueue.Count > 0) - { - var adorner = _adornerUpdateQueue.Dequeue(); - adorner.Update(this, transform); - } - _updateRequested = false; Readback.CompleteWrite(Revision); @@ -263,7 +256,5 @@ namespace Avalonia.Rendering.Composition.Server if (visual.IsVisibleInFrame) AddDirtyRect(visual.TransformedOwnContentBounds); } - - public void EnqueueAdornerUpdate(ServerCompositionVisual visual) => _adornerUpdateQueue.Enqueue(visual); } } diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs index 9d17756f2b..bc4b1eb769 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.DirtyProperties.cs @@ -24,7 +24,6 @@ partial class ServerCompositionVisual | CompositionVisualChangedFields.AnchorPointAnimated | CompositionVisualChangedFields.CenterPoint | CompositionVisualChangedFields.CenterPointAnimated - | CompositionVisualChangedFields.AdornedVisual | CompositionVisualChangedFields.TransformMatrix | CompositionVisualChangedFields.Scale | CompositionVisualChangedFields.ScaleAnimated @@ -63,7 +62,6 @@ partial class ServerCompositionVisual if (offset == s_IdOfSizeProperty || offset == s_IdOfAnchorPointProperty || offset == s_IdOfCenterPointProperty - || offset == s_IdOfAdornedVisualProperty || offset == s_IdOfTransformMatrixProperty || offset == s_IdOfScaleProperty || offset == s_IdOfRotationAngleProperty diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs index c2d43f5667..8e72f23318 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionVisual.cs @@ -44,18 +44,7 @@ namespace Avalonia.Rendering.Composition.Server Root!.DebugEvents?.IncrementRenderedVisuals(); var boundsRect = new Rect(new Size(Size.X, Size.Y)); - - if (AdornedVisual != null) - { - // Adorners are currently not supported in detached rendering mode - if(context.DetachedRendering) - return; - - canvas.Transform = Matrix.Identity; - if (AdornerIsClipped) - canvas.PushClip(AdornedVisual._combinedTransformedClipBounds.ToRect()); - } - + using var _ = context.SetOrPushTransform(this); var applyRenderOptions = RenderOptions != default; @@ -81,8 +70,7 @@ namespace Avalonia.Rendering.Composition.Server canvas.PopGeometryClip(); if (ClipToBounds && !HandlesClipToBounds) canvas.PopClip(); - if (AdornedVisual != null && AdornerIsClipped) - canvas.PopClip(); + if (Opacity != 1) canvas.PopOpacity(); @@ -136,7 +124,7 @@ namespace Avalonia.Rendering.Composition.Server } } - public virtual UpdateResult Update(ServerCompositionTarget root, Matrix parentVisualTransform) + public virtual UpdateResult Update(ServerCompositionTarget root, Matrix parentTransform) { if (Parent == null && Root == null) return default; @@ -147,14 +135,11 @@ namespace Avalonia.Rendering.Composition.Server if (_combinedTransformDirty) { CombinedTransformMatrix = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, - // HACK: Ignore RenderTransform set by the adorner layer - AdornedVisual != null ? Matrix.Identity : TransformMatrix, + TransformMatrix, Scale, RotationAngle, Orientation, Offset); _combinedTransformDirty = false; } - var parentTransform = AdornedVisual?.GlobalTransformMatrix ?? parentVisualTransform; - var newTransform = CombinedTransformMatrix * parentTransform; // Check if visual was moved and recalculate face orientation @@ -221,8 +206,7 @@ namespace Avalonia.Rendering.Composition.Server } _combinedTransformedClipBounds = - (AdornerIsClipped ? AdornedVisual?._combinedTransformedClipBounds : null) - ?? (Parent?.Effect == null ? Parent?._combinedTransformedClipBounds : null) + (Parent?.Effect == null ? Parent?._combinedTransformedClipBounds : null) ?? new LtrbRect(0, 0, Root!.PixelSize.Width, Root!.PixelSize.Height); if (_transformedClipBounds != null) diff --git a/src/Avalonia.Base/composition-schema.xml b/src/Avalonia.Base/composition-schema.xml index ce989296b1..fbece60644 100644 --- a/src/Avalonia.Base/composition-schema.xml +++ b/src/Avalonia.Base/composition-schema.xml @@ -29,8 +29,6 @@ - - diff --git a/src/Avalonia.Controls/Primitives/AdornerHelper.cs b/src/Avalonia.Controls/Primitives/AdornerHelper.cs new file mode 100644 index 0000000000..312bfb80c3 --- /dev/null +++ b/src/Avalonia.Controls/Primitives/AdornerHelper.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using Avalonia.Media; +using Avalonia.VisualTree; + +namespace Avalonia.Controls.Primitives; + +class AdornerHelper +{ + + public static IDisposable SubscribeToAncestorPropertyChanges(Visual visual, + bool includeClip, Action changed) + { + return new AncestorPropertyChangesSubscription(visual, includeClip, changed); + } + + private class AncestorPropertyChangesSubscription : IDisposable + { + private readonly Visual _visual; + private readonly bool _includeClip; + private readonly Action _changed; + private readonly EventHandler _propertyChangedHandler; + private readonly List _subscriptions = new List(); + private bool _isDisposed; + + public AncestorPropertyChangesSubscription(Visual visual, bool includeClip, Action changed) + { + _visual = visual; + _includeClip = includeClip; + _changed = changed; + _propertyChangedHandler = OnPropertyChanged; + + _visual.AttachedToVisualTree += OnAttachedToVisualTree; + _visual.DetachedFromVisualTree += OnDetachedFromVisualTree; + + if (_visual.IsAttachedToVisualTree) + { + SubscribeToAncestors(); + } + } + + private void SubscribeToAncestors() + { + UnsubscribeFromAncestors(); + + // Subscribe to the visual's own Bounds property + _visual.PropertyChanged += _propertyChangedHandler; + _subscriptions.Add(_visual); + + // Walk up the ancestor chain + var ancestor = _visual.VisualParent; + while (ancestor != null) + { + if (ancestor is Visual visualAncestor) + { + visualAncestor.PropertyChanged += _propertyChangedHandler; + _subscriptions.Add(visualAncestor); + } + ancestor = ancestor.VisualParent; + } + } + + private void UnsubscribeFromAncestors() + { + foreach (var subscription in _subscriptions) + { + subscription.PropertyChanged -= _propertyChangedHandler; + } + _subscriptions.Clear(); + } + + private void OnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (!e.IsEffectiveValueChange) + return; + + bool shouldNotify = false; + + if (e.Property == Visual.RenderTransformProperty || e.Property == Visual.BoundsProperty) + { + shouldNotify = true; + } + else if (_includeClip) + { + if (e.Property == Visual.ClipToBoundsProperty || + e.Property == Visual.ClipProperty) shouldNotify = true; + } + + if (shouldNotify) + { + _changed(); + } + } + + private void OnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) + { + SubscribeToAncestors(); + _changed(); + } + + private void OnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e) + { + UnsubscribeFromAncestors(); + _changed(); + } + + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + UnsubscribeFromAncestors(); + _visual.AttachedToVisualTree -= OnAttachedToVisualTree; + _visual.DetachedFromVisualTree -= OnDetachedFromVisualTree; + } + } + + public static Geometry? CalculateAdornerClip(Visual adornedElement) + { + // Walk ancestor stack and calculate clip geometry relative to the current visual. + // If ClipToBounds = true, add extra RectangleGeometry for Bounds.Size + + Geometry? result = null; + var ancestor = adornedElement.VisualParent; + + while (ancestor != null) + { + if (ancestor is Visual visualAncestor) + { + Geometry? ancestorClip = null; + + // Check if ancestor has ClipToBounds enabled + if (visualAncestor.ClipToBounds) + { + ancestorClip = new RectangleGeometry(new Rect(visualAncestor.Bounds.Size)); + } + + // Check if ancestor has explicit Clip geometry + if (visualAncestor.Clip != null) + { + if (ancestorClip != null) + { + ancestorClip = new CombinedGeometry(GeometryCombineMode.Intersect, ancestorClip, visualAncestor.Clip); + } + else + { + ancestorClip = visualAncestor.Clip; + } + } + + // Transform the clip geometry to adorned element's coordinate space + if (ancestorClip != null) + { + var transform = visualAncestor.TransformToVisual(adornedElement); + if (transform.HasValue && !transform.Value.IsIdentity) + { + ancestorClip = ancestorClip.Clone(); + ancestorClip.Transform = new MatrixTransform(transform.Value); + } + + // Combine with existing result + if (result != null) + { + result = new CombinedGeometry(GeometryCombineMode.Intersect, result, ancestorClip); + } + else + { + result = ancestorClip; + } + } + } + + ancestor = ancestor.VisualParent; + } + + return result; + } + +} \ No newline at end of file diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 412dd236ff..17cf8a8b6d 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Specialized; +using Avalonia.Input.TextInput; using Avalonia.Media; using Avalonia.Reactive; using Avalonia.VisualTree; @@ -45,15 +46,20 @@ namespace Avalonia.Controls.Primitives private static readonly AttachedProperty s_savedAdornerLayerProperty = AvaloniaProperty.RegisterAttached("SavedAdornerLayer"); + private TransformTrackingHelper _trackingHelper = new TransformTrackingHelper(false); + static AdornerLayer() { AdornedElementProperty.Changed.Subscribe(AdornedElementChanged); AdornerProperty.Changed.Subscribe(AdornerChanged); + IsClipEnabledProperty.Changed.Subscribe(AdornerIsClipEnabledChanged); } public AdornerLayer() { Children.CollectionChanged += ChildrenCollectionChanged; + _trackingHelper.SetVisual(this); + _trackingHelper.MatrixChanged += delegate { InvalidateMeasure(); }; } public static Visual? GetAdornedElement(Visual adorner) @@ -199,9 +205,9 @@ namespace Avalonia.Controls.Primitives { var info = ao.GetValue(s_adornedElementInfoProperty); - if (info != null && info.Bounds.HasValue) + if (info is { AdornedElement: not null }) { - child.Measure(info.Bounds.Value.Bounds.Size); + child.Measure(info.AdornedElement.Bounds.Size); } else { @@ -223,12 +229,15 @@ namespace Avalonia.Controls.Primitives var info = ao.GetValue(s_adornedElementInfoProperty); var isClipEnabled = ao.GetValue(IsClipEnabledProperty); - if (info != null && info.Bounds.HasValue) + var adorned = info?.AdornedElement; + + if (adorned != null) { - child.RenderTransform = new MatrixTransform(info.Bounds.Value.Transform); + var transform = adorned.TransformToVisual(this); + child.RenderTransform = new MatrixTransform(transform ?? default); child.RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Absolute); - UpdateClip(child, info.Bounds.Value, isClipEnabled); - child.Arrange(info.Bounds.Value.Bounds); + UpdateClip(child, adorned, isClipEnabled); + child.Arrange(new(adorned.Bounds.Size)); } else { @@ -248,29 +257,22 @@ namespace Avalonia.Controls.Primitives layer?.UpdateAdornedElement(adorner, adorned); } - private void UpdateClip(Control control, TransformedBounds bounds, bool isEnabled) + private static void AdornerIsClipEnabledChanged(AvaloniaPropertyChangedEventArgs e) + { + var info = ((Visual)e.Sender).GetValue(s_adornedElementInfoProperty); + info?.UpdateSubscription(); + info?.Layer?.InvalidateMeasure(); + } + + private void UpdateClip(Control control, Visual adorned, bool isEnabled) { if (!isEnabled) { control.Clip = null; - return; } - if (!(control.Clip is RectangleGeometry clip)) - { - clip = new RectangleGeometry(); - control.Clip = clip; - } - - var clipBounds = bounds.Bounds; - - if (bounds.Transform.HasInverse) - { - clipBounds = bounds.Clip.TransformToAABB(bounds.Transform.Invert()); - } - - clip.Rect = clipBounds; + control.Clip = AdornerHelper.CalculateAdornerClip(adorned); } private void ChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) @@ -291,17 +293,12 @@ namespace Avalonia.Controls.Primitives private void UpdateAdornedElement(Visual adorner, Visual? adorned) { - if (adorner.CompositionVisual != null) - { - adorner.CompositionVisual.AdornedVisual = adorned?.CompositionVisual; - adorner.CompositionVisual.AdornerIsClipped = GetIsClipEnabled(adorner); - } - var info = adorner.GetValue(s_adornedElementInfoProperty); if (info != null) { info.Subscription!.Dispose(); + info.Subscription = null; if (adorned == null) { @@ -313,24 +310,35 @@ namespace Avalonia.Controls.Primitives { if (info == null) { - info = new AdornedElementInfo(); + info = new AdornedElementInfo(adorner); adorner.SetValue(s_adornedElementInfoProperty, info); } - if (adorner.CompositionVisual != null) - info.Subscription = adorned.GetObservable(BoundsProperty).Subscribe(x => - { - info.Bounds = new TransformedBounds(new Rect(adorned.Bounds.Size), new Rect(adorned.Bounds.Size), Matrix.Identity); - InvalidateMeasure(); - }); + info.Layer = this; + info.AdornedElement = adorned; + info.UpdateSubscription(); } } - private class AdornedElementInfo + private class AdornedElementInfo(Visual adorner) { + public AdornerLayer? Layer { get; set; } public IDisposable? Subscription { get; set; } + public Visual? AdornedElement { get; set; } - public TransformedBounds? Bounds { get; set; } + public void UpdateSubscription() + { + Subscription?.Dispose(); + Subscription = null; + if (AdornedElement != null) + { + Subscription = AdornerHelper.SubscribeToAncestorPropertyChanges(AdornedElement, + GetIsClipEnabled(adorner), () => + { + Layer?.InvalidateMeasure(); + }); + } + } } } } diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 6de5b1ea78..889f4fe397 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -470,7 +470,7 @@ namespace Avalonia.Controls.Primitives if (InheritsTransform) { - TransformTrackingHelper.Track(placementTarget, PlacementTargetTransformChanged) + TransformTrackingHelper.Track(placementTarget, true, PlacementTargetTransformChanged) .DisposeWith(handlerCleanup); } else From b4af1c1b66ed6d75857e20c56ef426e949362e93 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 20 Dec 2025 05:02:29 +0500 Subject: [PATCH 2/3] Account for adorner's margin when rotating (who have thought that adorner margins is a good idea?) --- .../Pages/AdornerLayerPage.xaml | 36 ++++++++++++------- .../Primitives/AdornerLayer.cs | 9 ++++- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/samples/ControlCatalog/Pages/AdornerLayerPage.xaml b/samples/ControlCatalog/Pages/AdornerLayerPage.xaml index 7501c80940..e9a245a8e1 100644 --- a/samples/ControlCatalog/Pages/AdornerLayerPage.xaml +++ b/samples/ControlCatalog/Pages/AdornerLayerPage.xaml @@ -44,21 +44,31 @@ VerticalContentAlignment="Center" VerticalAlignment="Stretch" Width="200" Height="42"> - - - - - - + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Controls/Primitives/AdornerLayer.cs b/src/Avalonia.Controls/Primitives/AdornerLayer.cs index 17cf8a8b6d..5fe3f16a78 100644 --- a/src/Avalonia.Controls/Primitives/AdornerLayer.cs +++ b/src/Avalonia.Controls/Primitives/AdornerLayer.cs @@ -233,11 +233,18 @@ namespace Avalonia.Controls.Primitives if (adorned != null) { + child.Arrange(new(adorned.Bounds.Size)); var transform = adorned.TransformToVisual(this); + // If somebody decides that having Margin on an adorner is a good idea, + // we need to compensate for element being positioned at non-(0,0) coords. + if (transform != null && child.Bounds.Position != default) + { + transform = Matrix.CreateTranslation(child.Bounds.Position) * transform.Value * + Matrix.CreateTranslation(-child.Bounds.Position); + } child.RenderTransform = new MatrixTransform(transform ?? default); child.RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Absolute); UpdateClip(child, adorned, isClipEnabled); - child.Arrange(new(adorned.Bounds.Size)); } else { From 2d73868200e7c4bd1c65c61b8477136ce7b17e73 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Sat, 20 Dec 2025 05:03:08 +0500 Subject: [PATCH 3/3] Removed adorner hack from IPopupPositioner.cs --- .../PopupPositioning/IPopupPositioner.cs | 31 ++----------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs index a0b853f2dc..c79efb1841 100644 --- a/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs +++ b/src/Avalonia.Controls/Primitives/PopupPositioning/IPopupPositioner.cs @@ -571,16 +571,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning var target = positionRequest.Target; 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); - } - + Matrix? matrix = target.TransformToVisual(topLevel); + if (matrix == null) { if (target.GetVisualRoot() == null) @@ -592,25 +584,6 @@ namespace Avalonia.Controls.Primitives.PopupPositioning var anchorRect = positionRequest.AnchorRect ?? bounds; return anchorRect.Intersect(bounds).TransformToAABB(matrix.Value); } - - 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; - } } }