src/Avalonia.Controls/Primitives/AdornerLayer.cs (259 lines of code) (raw):

using System; using System.Collections.Specialized; using Avalonia.Media; using Avalonia.Reactive; using Avalonia.VisualTree; namespace Avalonia.Controls.Primitives { /// <summary> /// Represents a surface for showing adorners. /// Adorners are always on top of the adorned element and are positioned to stay relative to the adorned element. /// </summary> /// <remarks> /// TODO: Need to track position of adorned elements and move the adorner if they move. /// </remarks> public class AdornerLayer : Canvas { /// <summary> /// Allows for getting and setting of the adorned element. /// </summary> public static readonly AttachedProperty<Visual?> AdornedElementProperty = AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Visual?>("AdornedElement"); /// <summary> /// Allows for controlling clipping of the adorner. /// </summary> public static readonly AttachedProperty<bool> IsClipEnabledProperty = AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, bool>("IsClipEnabled", true); /// <summary> /// Allows for getting and setting of the adorner for control. /// </summary> public static readonly AttachedProperty<Control?> AdornerProperty = AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, Control?>("Adorner"); /// <summary> /// Defines the <see cref="DefaultFocusAdorner"/> property. /// </summary> public static readonly StyledProperty<ITemplate<Control>?> DefaultFocusAdornerProperty = AvaloniaProperty.Register<AdornerLayer, ITemplate<Control>?>(nameof(DefaultFocusAdorner)); private static readonly AttachedProperty<AdornedElementInfo?> s_adornedElementInfoProperty = AvaloniaProperty.RegisterAttached<AdornerLayer, Visual, AdornedElementInfo?>("AdornedElementInfo"); private static readonly AttachedProperty<AdornerLayer?> s_savedAdornerLayerProperty = AvaloniaProperty.RegisterAttached<Visual, Visual, AdornerLayer?>("SavedAdornerLayer"); static AdornerLayer() { AdornedElementProperty.Changed.Subscribe(AdornedElementChanged); AdornerProperty.Changed.Subscribe(AdornerChanged); } public AdornerLayer() { Children.CollectionChanged += ChildrenCollectionChanged; } public static Visual? GetAdornedElement(Visual adorner) { return adorner.GetValue(AdornedElementProperty); } public static void SetAdornedElement(Visual adorner, Visual? adorned) { adorner.SetValue(AdornedElementProperty, adorned); } public static AdornerLayer? GetAdornerLayer(Visual visual) { return visual.FindAncestorOfType<VisualLayerManager>()?.AdornerLayer; } public static bool GetIsClipEnabled(Visual adorner) { return adorner.GetValue(IsClipEnabledProperty); } public static void SetIsClipEnabled(Visual adorner, bool isClipEnabled) { adorner.SetValue(IsClipEnabledProperty, isClipEnabled); } public static Control? GetAdorner(Visual visual) { return visual.GetValue(AdornerProperty); } public static void SetAdorner(Visual visual, Control? adorner) { visual.SetValue(AdornerProperty, adorner); } /// <summary> /// Gets or sets the default control's focus adorner. /// </summary> public ITemplate<Control>? DefaultFocusAdorner { get => GetValue(DefaultFocusAdornerProperty); set => SetValue(DefaultFocusAdornerProperty, value); } private static void AdornerChanged(AvaloniaPropertyChangedEventArgs<Control?> e) { if (e.Sender is Visual visual) { var oldAdorner = e.OldValue.GetValueOrDefault(); var newAdorner = e.NewValue.GetValueOrDefault(); if (Equals(oldAdorner, newAdorner)) { return; } if (oldAdorner is { }) { visual.AttachedToVisualTree -= VisualOnAttachedToVisualTree; visual.DetachedFromVisualTree -= VisualOnDetachedFromVisualTree; Detach(visual, oldAdorner); } if (newAdorner is { }) { visual.AttachedToVisualTree += VisualOnAttachedToVisualTree; visual.DetachedFromVisualTree += VisualOnDetachedFromVisualTree; Attach(visual, newAdorner); } } } private static void VisualOnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs e) { if (sender is Visual visual) { var adorner = GetAdorner(visual); if (adorner is { }) { Attach(visual, adorner); } } } private static void VisualOnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs e) { if (sender is Visual visual) { var adorner = GetAdorner(visual); if (adorner is { }) { Detach(visual, adorner); } } } private static void Attach(Visual visual, Control adorner) { var layer = AdornerLayer.GetAdornerLayer(visual); AddVisualAdorner(visual, adorner, layer); visual.SetValue(s_savedAdornerLayerProperty, layer); } private static void Detach(Visual visual, Control adorner) { var layer = visual.GetValue(s_savedAdornerLayerProperty); RemoveVisualAdorner(visual, adorner, layer); visual.ClearValue(s_savedAdornerLayerProperty); } private static void AddVisualAdorner(Visual visual, Control? adorner, AdornerLayer? layer) { if (adorner is null || layer == null || layer.Children.Contains(adorner)) { return; } SetAdornedElement(adorner, visual); ((ISetLogicalParent) adorner).SetParent(visual); layer.Children.Add(adorner); } private static void RemoveVisualAdorner(Visual visual, Control? adorner, AdornerLayer? layer) { if (adorner is null || layer is null || !layer.Children.Contains(adorner)) { return; } layer.Children.Remove(adorner); ((ISetLogicalParent) adorner).SetParent(null); } /// <inheritdoc /> protected override Size MeasureOverride(Size availableSize) { foreach (var child in Children) { if (child is AvaloniaObject ao) { var info = ao.GetValue(s_adornedElementInfoProperty); if (info != null && info.Bounds.HasValue) { child.Measure(info.Bounds.Value.Bounds.Size); } else { child.Measure(availableSize); } } } return default; } /// <inheritdoc /> protected override Size ArrangeOverride(Size finalSize) { foreach (var child in Children) { if (child is AvaloniaObject ao) { var info = ao.GetValue(s_adornedElementInfoProperty); var isClipEnabled = ao.GetValue(IsClipEnabledProperty); if (info != null && info.Bounds.HasValue) { child.RenderTransform = new MatrixTransform(info.Bounds.Value.Transform); child.RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Absolute); UpdateClip(child, info.Bounds.Value, isClipEnabled); child.Arrange(info.Bounds.Value.Bounds); } else { ArrangeChild(child, finalSize); } } } return finalSize; } private static void AdornedElementChanged(AvaloniaPropertyChangedEventArgs<Visual?> e) { var adorner = (Visual)e.Sender; var adorned = e.NewValue.GetValueOrDefault(); var layer = adorner.GetVisualParent<AdornerLayer>(); layer?.UpdateAdornedElement(adorner, adorned); } private void UpdateClip(Control control, TransformedBounds bounds, 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; } private void ChildrenCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Add: foreach (Visual i in e.NewItems!) { UpdateAdornedElement(i, i.GetValue(AdornedElementProperty)); } break; } InvalidateArrange(); } 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(); if (adorned == null) { adorner.ClearValue(s_adornedElementInfoProperty); } } if (adorned != null) { if (info == null) { info = new AdornedElementInfo(); 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(); }); } } private class AdornedElementInfo { public IDisposable? Subscription { get; set; } public TransformedBounds? Bounds { get; set; } } } }