Add ScopeAwareAdornerLayer

This commit is contained in:
chinboy
2025-02-23 15:24:07 +08:00
parent e2ddc80628
commit 1a4da9ff2a
@@ -0,0 +1,428 @@
using System.Collections.Specialized;
using System.Diagnostics;
using System.Reflection;
using AtomUI.Controls.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace AtomUI.Controls.Primitives;
public class ScopeAwareAdornerLayer : Canvas
{
private const int ScopeAwareAdornerLayerZIndex = int.MaxValue - 1000;
#region 公共属性
public static readonly AttachedProperty<Control?> AdornedElementProperty =
AvaloniaProperty.RegisterAttached<ScopeAwareAdornerLayer, Visual, Control?>("AdornedElement");
public static readonly AttachedProperty<Control?> AdornerProperty =
AvaloniaProperty.RegisterAttached<ScopeAwareAdornerLayer, Visual, Control?>("Adorner");
public static readonly StyledProperty<Visual?> LayerHostProperty = AvaloniaProperty
.Register<ScopeAwareAdornerLayer, Visual?>(nameof(LayerHost));
public Visual? LayerHost
{
get => GetValue(LayerHostProperty);
set => SetValue(LayerHostProperty, value);
}
#endregion
#region 内部属性定义
private static readonly AttachedProperty<AdornedElementInfo?> AdornedElementInfoProperty =
AvaloniaProperty.RegisterAttached<ScopeAwareAdornerLayer, Visual, AdornedElementInfo?>("AdornedElementInfo");
private static readonly AttachedProperty<ScopeAwareAdornerLayer?> SavedAdornerLayerProperty =
AvaloniaProperty.RegisterAttached<Visual, Visual, ScopeAwareAdornerLayer?>("SavedAdornerLayer");
private static readonly MethodInfo AddLayerMethodInfo;
#endregion
static ScopeAwareAdornerLayer()
{
AdornedElementProperty.Changed.Subscribe(HandleAdornedElementChanged);
AdornerProperty.Changed.Subscribe(HandleAdornerChanged);
AddLayerMethodInfo =
typeof(VisualLayerManager).GetMethod("AddLayer", BindingFlags.Instance | BindingFlags.NonPublic)!;
}
public ScopeAwareAdornerLayer()
{
Children.CollectionChanged += ChildrenCollectionChanged;
}
public static Visual? GetAdornedElement(Visual control)
{
return control.GetValue(AdornedElementProperty);
}
public static void SetAdornedElement(Visual control, Visual? adorned)
{
control.SetValue(AdornedElementProperty, adorned);
}
public static ScopeAwareAdornerLayer? GetDrawerLayer(Visual visual)
{
Layoutable? layerHost = visual.FindAncestorOfType<ScrollContentPresenter>(true);
var adorned = GetAdornedElement(visual);
if (layerHost != null && adorned != null)
{
while (layerHost != null && layerHost.IsVisualAncestorOf(adorned) == false)
{
layerHost = layerHost.FindAncestorOfType<ScrollContentPresenter>();
}
}
layerHost ??= visual.FindAncestorOfType<VisualLayerManager>();
layerHost ??= TopLevel.GetTopLevel(visual);
if (layerHost == null)
{
return null;
}
var layer =
layerHost.GetVisualChildren().FirstOrDefault(c => c is ScopeAwareAdornerLayer) as ScopeAwareAdornerLayer;
layer ??= InjectLayer(layerHost);
return layer;
}
public static Control? GetAdorner(Visual visual)
{
return visual.GetValue(AdornerProperty);
}
public static void SetAdorner(Visual visual, Control? adorner)
{
visual.SetValue(AdornerProperty, adorner);
}
protected override Size MeasureOverride(Size availableSize)
{
foreach (var child in Children)
{
if (child is AvaloniaObject ao)
{
var info = ao.GetValue(AdornedElementInfoProperty);
if (info != null && info.Bounds.HasValue)
{
child.Measure(info.Bounds.Value.Bounds.Size);
}
else
{
child.Measure(availableSize);
}
}
}
return default;
}
protected override Size ArrangeOverride(Size finalSize)
{
foreach (var child in Children)
{
if (child is AvaloniaObject ao)
{
var info = ao.GetValue(AdornedElementInfoProperty);
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);
child.Arrange(info.Bounds.Value.Bounds);
}
else
{
ArrangeChild(child, finalSize);
}
}
}
return finalSize;
}
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, Control? adorned)
{
var info = adorner.GetValue(AdornedElementInfoProperty);
if (info != null)
{
info.Subscription!.Dispose();
if (adorned == null)
{
adorner.ClearValue(AdornedElementInfoProperty);
}
}
if (adorned != null)
{
if (info == null)
{
info = new AdornedElementInfo();
adorner.SetValue(AdornedElementInfoProperty, info);
}
info.Subscription = adorned.GetObservable(BoundsProperty).Subscribe(x =>
{
Debug.Assert(LayerHost != null);
double offsetX = 0d;
double offsetY = 0d;
var relateToHostPoint = adorned.TranslatePoint(new Point(0, 0), LayerHost) ?? new Point(0, 0);
if (LayerHost is ScrollContentPresenter scrollContentPresenter)
{
offsetX = scrollContentPresenter.Offset.X;
offsetY = scrollContentPresenter.Offset.Y;
}
offsetX += relateToHostPoint.X;
offsetY += relateToHostPoint.Y;
var translationMatrix = LayerHost != null
? Matrix.CreateTranslation(offsetX, offsetY)
: Matrix.Identity;
info.Bounds = new TransformedBounds(new Rect(adorned.DesiredSize),
new Rect(adorned.DesiredSize),
translationMatrix);
InvalidateMeasure();
});
}
}
private void UpdateClip(Control control, TransformedBounds bounds)
{
if (!(control.Clip is RectangleGeometry clip))
{
clip = new RectangleGeometry();
control.Clip = clip;
}
var clipBounds = bounds.Bounds;
clip.Rect = clipBounds;
}
private static void HandleAdornedElementChanged(AvaloniaPropertyChangedEventArgs<Control?> e)
{
var adorner = (Visual)e.Sender;
var adorned = e.NewValue.GetValueOrDefault();
var layer = adorner.GetVisualParent<ScopeAwareAdornerLayer>();
layer?.UpdateAdornedElement(adorner, adorned);
}
private static void HandleAdornerChanged(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 Attach(Visual visual, Control adorner)
{
var layer = ScopeAwareAdornerLayer.GetDrawerLayer(visual);
AddVisualAdorner(visual, adorner, layer);
visual.SetValue(SavedAdornerLayerProperty, layer);
}
private static void Detach(Visual visual, Control adorner)
{
var layer = visual.GetValue(SavedAdornerLayerProperty);
RemoveVisualAdorner(visual, adorner, layer);
visual.ClearValue(SavedAdornerLayerProperty);
}
private static void AddVisualAdorner(Visual visual, Control? adorner, ScopeAwareAdornerLayer? 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, ScopeAwareAdornerLayer? layer)
{
if (adorner is null || layer is null || !layer.Children.Contains(adorner))
{
return;
}
layer.Children.Remove(adorner);
((ISetLogicalParent)adorner).SetParent(null);
}
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 AddLayerToVisualLayerManager(VisualLayerManager visualLayerManager, Control layer, int zindex)
{
AddLayerMethodInfo.Invoke(visualLayerManager, [layer, zindex]);
}
private static ScopeAwareAdornerLayer InjectLayer(Layoutable layerHost)
{
var layer = FindAdornerLayer(layerHost);
if (layer != null)
{
return layer;
}
layer = new ScopeAwareAdornerLayer
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
ZIndex = ScopeAwareAdornerLayerZIndex
};
layer.LayerHost = layerHost;
if (layerHost is VisualLayerManager visualLayerManager)
{
AddLayerToVisualLayerManager(visualLayerManager, layer, visualLayerManager.ZIndex);
}
else if (layerHost is ScrollContentPresenter scrollContentPresenter)
{
if (scrollContentPresenter.Content is Control controlContent)
{
var oldOffset = scrollContentPresenter.Offset;
// 直接内容控件
scrollContentPresenter.Content = null;
scrollContentPresenter.UpdateChild();
var panel = new Panel
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
};
panel.Children.Add(controlContent);
panel.Children.Add(layer);
scrollContentPresenter.Content = panel;
var scrollViewer = scrollContentPresenter.FindAncestorOfType<ScrollViewer>();
if (scrollViewer != null)
{
scrollViewer.Offset = oldOffset;
}
}
else if (scrollContentPresenter.Content != null && scrollContentPresenter.ContentTemplate != null)
{
// 模版处理
var injectTemplate = new FuncDataTemplate<object?>((o, scope) =>
{
var originControl = scrollContentPresenter.ContentTemplate.Build(o);
var panel = new Panel
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch,
};
Debug.Assert(originControl != null);
panel.Children.Add(originControl);
panel.Children.Add(layer);
return panel;
});
scrollContentPresenter.ContentTemplate = injectTemplate;
}
}
return layer;
}
private static ScopeAwareAdornerLayer? FindAdornerLayer(Layoutable layerHost)
{
if (layerHost is ScrollContentPresenter scrollContentPresenter)
{
// 在 Panel 下面
var panel = scrollContentPresenter.FindChildOfType<Panel>();
if (panel is not null)
{
return panel.FindChildOfType<ScopeAwareAdornerLayer>();
}
}
else if (layerHost is VisualLayerManager visualLayerManager)
{
// 直接就在下面
visualLayerManager.FindChildOfType<ScopeAwareAdornerLayer>();
}
return null;
}
private class AdornedElementInfo
{
public IDisposable? Subscription { get; set; }
public TransformedBounds? Bounds { get; set; }
}
}