* 调整AtomLayer,对Adorner提供Bounds;

This commit is contained in:
zeroone 2024-09-07 13:32:40 +08:00
parent 1d70e4535b
commit 0e379e0d3c
5 changed files with 219 additions and 112 deletions

22
docs/Code Style.md Normal file
View File

@ -0,0 +1,22 @@
# Code Styles
## C# Naming Style
- Type, NameSpace, Method, Property, Event: **UpperCamelCase**
- Interface: **IUpperCamelCase**
- Type parameter: **TUpperCamelCase**
- Local variable, Parameter: **lowerCamelCase**
- Public Field (基本不用): **UpperCamelCase**
- Private Field: **_lowerCamelCase**
- Control Template Part Name: **PART_UpperCamelCase**
- Control Pseudo Class Name: **STATE_UpperCamelCase**
## 其他
- 使用Rider配置尽量和Rider的建议保持一致即可。
- 不要在一行中声明多个变量;
## Xaml
- 使用 XamlStyler插件添加配置。
- 不超过两个属性的共用一行。
- NameUpperCamelCase。
- 结构复杂增加注释。

View File

@ -1,76 +1,60 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Presenters; using Avalonia.Threading;
using Avalonia.Layout;
using Avalonia.VisualTree;
// ReSharper disable SuggestBaseTypeForParameter // ReSharper disable SuggestBaseTypeForParameter
namespace AtomUI.Controls.Primitives namespace AtomUI.Controls.Primitives
{ {
public class AtomLayer : Panel public class AtomLayer : Canvas
{ {
public static Control? GetTarget(Visual element) #region Static
{
return element.GetValue(TargetProperty);
}
public static void SetTarget(Visual element, Control? value)
{
element.SetValue(TargetProperty, value);
}
public static readonly AttachedProperty<Control?> TargetProperty = AvaloniaProperty
.RegisterAttached<AtomLayer, Visual, Control?>("Target");
public static AtomLayer? GetLayer(Visual target)
{
return target.GetLayer();
}
public static Visual GetTarget(Control adorner)
{
return adorner.GetValue(TargetProperty);
}
private static void SetTarget(Control adorner, Visual value)
{
adorner.SetValue(TargetProperty, value);
}
public static readonly AttachedProperty<Visual> TargetProperty = AvaloniaProperty
.RegisterAttached<AtomLayer, Control, Visual>("Target");
public static Rect GetTargetRect(Control adorner)
{
return adorner.GetValue(TargetRectProperty);
}
private static void SetTargetRect(Control adorner, Rect value)
{
adorner.SetValue(TargetRectProperty, value);
}
public static readonly AttachedProperty<Rect> TargetRectProperty = AvaloniaProperty
.RegisterAttached<AtomLayer, Control, Rect>("TargetRect");
public static AtomLayer? GetLayer(Visual visual) #endregion
private readonly IList<WeakReference<Control>> _detachedAdorners = new List<WeakReference<Control>>();
internal Visual? ParentHost { get; set; }
internal AtomLayer() { }
public T? GetAdorner<T>(Visual target) where T : Control
{ {
var host = visual.FindAncestorOfType<ScrollContentPresenter>(true)?.Content as Control return this.Children.OfType<T>().FirstOrDefault(a => GetTarget(a) == target);
?? TopLevel.GetTopLevel(visual);
if (host == null)
{
return null;
}
var layer = host.GetVisualChildren().FirstOrDefault(c => c is AtomLayer) as AtomLayer;
layer ??= TryInject(host);
return layer;
} }
private static AtomLayer TryInject(Control control) public IEnumerable<Control> GetAdorners(Visual target)
{ {
var layer = new AtomLayer(); return this.Children.Where(v => GetTarget(v) == target).ToList();
InjectCore(control, layer);
return layer;
}
private static void InjectCore(Control control, AtomLayer layer)
{
if (control.GetVisualChildren() is not IList<Visual> visualChildren)
{
return;
}
if (visualChildren.Any(c => c is AtomLayer))
{
return;
}
layer.HorizontalAlignment = HorizontalAlignment.Stretch;
layer.VerticalAlignment = VerticalAlignment.Stretch;
layer.InheritanceParent = control;
visualChildren.Add(layer);
control.InvalidateMeasure();
}
private AtomLayer()
{
} }
public void AddAdorner(Visual target, Control adorner) public void AddAdorner(Visual target, Control adorner)
@ -80,58 +64,117 @@ namespace AtomUI.Controls.Primitives
return; return;
} }
SetTarget(target, adorner); target.PropertyChanged -= TargetOnPropertyChanged;
Children.Add(adorner); target.PropertyChanged += TargetOnPropertyChanged;
target.AttachedToVisualTree -= OnTargetOnAttachedToVisualTree; target.AttachedToVisualTree -= OnTargetOnAttachedToVisualTree;
target.DetachedFromVisualTree -= OnTargetOnDetachedFromVisualTree; target.DetachedFromVisualTree -= OnTargetOnDetachedFromVisualTree;
target.AttachedToVisualTree += OnTargetOnAttachedToVisualTree; target.AttachedToVisualTree += OnTargetOnAttachedToVisualTree;
target.DetachedFromVisualTree += OnTargetOnDetachedFromVisualTree; target.DetachedFromVisualTree += OnTargetOnDetachedFromVisualTree;
if (target is Control c) SetTarget(adorner, target);
{ Children.Add(adorner);
c.SizeChanged -= OnTargetOnSizeChanged; Locate(target, adorner);
c.SizeChanged += OnTargetOnSizeChanged;
}
} }
private void OnTargetOnSizeChanged(object? sender, SizeChangedEventArgs e) private void TargetOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{ {
if (sender is not Visual visual) if (e.Property != BoundsProperty)
{ {
return; return;
} }
foreach (var adorner in GetAdorners(visual))
if (sender is not Visual target)
{ {
adorner.InvalidateMeasure(); return;
adorner.InvalidateVisual();
} }
// Child element's bounds will be updated first.
Dispatcher.UIThread.Post(() =>
{
foreach (var adorner in GetAdorners(target))
{
Locate(target, adorner);
}
}, DispatcherPriority.Send);
}
private void Locate(Visual target, Control adorner)
{
if (this.ParentHost is Control { IsLoaded: false })
{
this.ParentHost.PropertyChanged -= ParentHostOnPropertyChanged;
this.ParentHost.PropertyChanged += ParentHostOnPropertyChanged;
}
else
{
LocateCore(target, adorner);
}
return;
void ParentHostOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
this.ParentHost.PropertyChanged -= ParentHostOnPropertyChanged;
LocateCore(target, adorner);
}
}
private void LocateCore(Visual target, Control adorner)
{
var matrix = target.TransformToVisual(this)!;
var x = matrix.Value.M31;
var y = matrix.Value.M32;
var rect = new Rect(x, y, target.Bounds.Width, target.Bounds.Height);
SetTargetRect(adorner, rect);
SetLeft(adorner, x);
SetTop(adorner, y);
adorner.Width = target.Bounds.Width;
adorner.Height = target.Bounds.Height;
adorner.Measure(target.Bounds.Size);
adorner.Arrange(rect);
} }
private void OnTargetOnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs args) private void OnTargetOnAttachedToVisualTree(object? sender, VisualTreeAttachmentEventArgs args)
{ {
if (Children.Contains(this)) if (sender is not Visual target)
{ {
return; return;
} }
Children.Add(this); var adorners = new List<Control>();
for (var i = 0; i < _detachedAdorners.Count; i++)
{
var w = _detachedAdorners[i];
if (w.TryGetTarget(out var t) && Children.Contains(t) == false && GetTarget(t) == target)
{
adorners.Add(t);
_detachedAdorners.RemoveAt(i--);
}
}
adorners = adorners.Where(a => Children.Contains(a) == false && GetTarget(a) == target).ToList();
foreach (var adorner in adorners)
{
Children.Add(adorner);
}
} }
private void OnTargetOnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs args) private void OnTargetOnDetachedFromVisualTree(object? sender, VisualTreeAttachmentEventArgs args)
{ {
Children.Remove(this); if (sender is not Visual target)
} {
return;
public Control? GetAdorner<T>(Visual target) where T : Control, IAtomAdorner }
{
var adorner = this.Children.OfType<T>().FirstOrDefault(a => a.Target == target); var adorners = target.GetAdorners();
return adorner; foreach (var adorner in adorners)
} {
Children.Remove(adorner);
public IEnumerable<Control> GetAdorners(Visual target) _detachedAdorners.Add(new WeakReference<Control>(adorner));
{ }
return this.Children.OfType<IAtomAdorner>().Where(c => c.Target == target).OfType<Control>();
} }
} }
} }

View File

@ -1,4 +1,8 @@
using Avalonia; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace AtomUI.Controls.Primitives; namespace AtomUI.Controls.Primitives;
@ -6,6 +10,67 @@ public static class AtomLayerExtension
{ {
public static AtomLayer? GetLayer(this Visual? visual) public static AtomLayer? GetLayer(this Visual? visual)
{ {
return visual == null ? null : AtomLayer.GetLayer(visual); if (visual == null)
{
return null;
}
var host = visual.FindAncestorOfType<ScrollContentPresenter>(true)?.Content as Visual
?? TopLevel.GetTopLevel(visual);
if (host == null)
{
return null;
}
var layer = host.GetVisualChildren().FirstOrDefault(c => c is AtomLayer) as AtomLayer
?? TryInject(host);
return layer;
}
public static T? GetAdorner<T>(this Visual target) where T : Control
{
return target.GetLayer()?.GetAdorner<T>(target);
}
public static IEnumerable<Control> GetAdorners(this Visual target)
{
return target.GetLayer()?.GetAdorners(target) ?? [];
}
public static void AddAdorner(this Visual target, Control adorner)
{
target.GetLayer()?.AddAdorner(target, adorner);
}
private static AtomLayer? TryInject(Visual host)
{
var layer = new AtomLayer
{
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
return InjectCore(host, layer) ? layer : null;
}
private static bool InjectCore(Visual host, AtomLayer layer)
{
if (host.GetVisualChildren() is not IList<Visual> visualChildren)
{
return false;
}
if (visualChildren.Any(c => c is AtomLayer))
{
return false;
}
visualChildren.Add(layer);
layer.ParentHost = host;
return true;
} }
} }

View File

@ -1,8 +0,0 @@
using Avalonia;
namespace AtomUI.Controls.Primitives;
public interface IAtomAdorner
{
public Visual Target { get; }
}

View File

@ -9,7 +9,7 @@ using Avalonia.VisualTree;
namespace AtomUI.Controls; namespace AtomUI.Controls;
public sealed class Watermark : Control, IAtomAdorner public sealed class Watermark : Control
{ {
public static WatermarkGlyph? GetGlyph(Visual element) public static WatermarkGlyph? GetGlyph(Visual element)
{ {
@ -27,8 +27,6 @@ public sealed class Watermark : Control, IAtomAdorner
public Visual Target { get; } public Visual Target { get; }
private WatermarkGlyph? Glyph { get; } private WatermarkGlyph? Glyph { get; }
private AtomLayer Layer { get; }
@ -38,11 +36,10 @@ public sealed class Watermark : Control, IAtomAdorner
GlyphProperty.Changed.AddClassHandler<Visual>(OnGlyphChanged); GlyphProperty.Changed.AddClassHandler<Visual>(OnGlyphChanged);
} }
private Watermark(AtomLayer layer, Visual target, WatermarkGlyph? glyph) private Watermark(Visual target, WatermarkGlyph? glyph)
{ {
Target = target; Target = target;
Glyph = glyph; Glyph = glyph;
Layer = layer;
if (glyph != null) if (glyph != null)
{ {
@ -90,7 +87,7 @@ public sealed class Watermark : Control, IAtomAdorner
return; return;
} }
watermark = new Watermark(layer, target, GetGlyph(target)); watermark = new Watermark(target, GetGlyph(target));
layer.AddAdorner(target ,watermark); layer.AddAdorner(target ,watermark);
} }
@ -114,24 +111,12 @@ public sealed class Watermark : Control, IAtomAdorner
return; return;
} }
if (CheckLayer(Target, out var layer) == false)
{
return;
}
var matrix = Target.TransformToVisual(layer);
if (matrix == null)
{
return;
}
var size = Glyph.GetDesiredSize(); var size = Glyph.GetDesiredSize();
if (size.Width == 0 || size.Height == 0) if (size.Width == 0 || size.Height == 0)
{ {
return; return;
} }
using (context.PushTransform(matrix.Value))
using (context.PushClip(new Rect(Target.Bounds.Size))) using (context.PushClip(new Rect(Target.Bounds.Size)))
using (context.PushOpacity(Glyph.Opacity)) using (context.PushOpacity(Glyph.Opacity))
{ {