Merge remote-tracking branch 'origin/feature/avatar' into develop

This commit is contained in:
CHINBOY
2025-07-12 22:03:46 +08:00
10 changed files with 957 additions and 0 deletions
@@ -8,6 +8,7 @@
<ResourceInclude Source="AdornerLayer/Themes/AdornerLayer.axaml" />
<ResourceInclude Source="Alert/Themes/AlertTheme.axaml" />
<ResourceInclude Source="ArrowDecoratedBox/Themes/ArrowDecoratedBoxThemes.axaml" />
<ResourceInclude Source="Avatar/Themes/AvatarTheme.axaml" />
<ResourceInclude Source="Badge/Themes/BadgeThemes.axaml" />
<ResourceInclude Source="Buttons/Themes/ButtonThemes.axaml" />
<ResourceInclude Source="ButtonSpinner/Themes/ButtonSpinnerThemes.axaml" />
+311
View File
@@ -0,0 +1,311 @@
using System.Diagnostics;
using AtomUI.Controls.Themes;
using AtomUI.IconPkg;
using AtomUI.Media;
using AtomUI.Theme;
using AtomUI.Theme.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Media.Transformation;
using Avalonia.Metadata;
namespace AtomUI.Controls;
public enum AvatarSizeType
{
Large,
Middle,
Small,
Custom
}
public enum AvatarShape
{
Circle,
Square
}
internal enum AvatarContentType
{
Icon,
BitmapImage,
SvgImage,
Text
}
public class Avatar : TemplatedControl, IControlSharedTokenResourcesHost, IMotionAwareControl
{
#region 公共属性定义
public static readonly StyledProperty<double> GapProperty =
AvaloniaProperty.Register<Avatar, double>(nameof(Gap), defaultValue: 4.0);
public static readonly StyledProperty<Icon?> IconProperty =
AvaloniaProperty.Register<Avatar, Icon?>(nameof(Icon));
public static readonly StyledProperty<IImage?> BitmapSrcProperty =
AvaloniaProperty.Register<Avatar, IImage?>(nameof(BitmapSrc));
public static readonly StyledProperty<string?> SrcProperty =
AvaloniaProperty.Register<Avatar, string?>(nameof(Src));
public static readonly StyledProperty<string?> TextProperty =
AvaloniaProperty.Register<TemplatedControl, string?>(nameof (Text));
public static readonly StyledProperty<AvatarSizeType> SizeTypeProperty =
AvaloniaProperty.Register<Avatar, AvatarSizeType>(nameof(SizeType),
AvatarSizeType.Middle);
public static readonly StyledProperty<double> SizeProperty =
AvaloniaProperty.Register<Avatar, double>(nameof(Size), Double.NaN);
public static readonly StyledProperty<AvatarShape> ShapeProperty =
AvaloniaProperty.Register<Avatar, AvatarShape>(nameof(Shape), AvatarShape.Circle);
public static readonly StyledProperty<bool> IsMotionEnabledProperty =
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<Avatar>();
public double Gap
{
get => GetValue(GapProperty);
set => SetValue(GapProperty, value);
}
public Icon? Icon
{
get => GetValue(IconProperty);
set => SetValue(IconProperty, value);
}
public IImage? BitmapSrc
{
get => GetValue(BitmapSrcProperty);
set => SetValue(BitmapSrcProperty, value);
}
public string? Src
{
get => GetValue(SrcProperty);
set => SetValue(SrcProperty, value);
}
[Content]
public string? Text
{
get => GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
public AvatarSizeType SizeType
{
get => GetValue(SizeTypeProperty);
set => SetValue(SizeTypeProperty, value);
}
public double Size
{
get => GetValue(SizeProperty);
set => SetValue(SizeProperty, value);
}
public AvatarShape Shape
{
get => GetValue(ShapeProperty);
set => SetValue(ShapeProperty, value);
}
public bool IsMotionEnabled
{
get => GetValue(IsMotionEnabledProperty);
set => SetValue(IsMotionEnabledProperty, value);
}
#endregion
#region 内部属性定义
internal static readonly StyledProperty<double> EffectiveIconSizeProperty =
AvaloniaProperty.Register<Avatar, double>(nameof(EffectiveIconSize));
internal static readonly StyledProperty<ITransform?> TextRenderTransformProperty =
AvaloniaProperty.Register<Avatar, ITransform?>(nameof(TextRenderTransform));
internal static readonly DirectProperty<Avatar, AvatarContentType> ContentTypeProperty =
AvaloniaProperty.RegisterDirect<Avatar, AvatarContentType>(
nameof(ContentType),
o => o.ContentType,
(o, v) => o.ContentType = v);
internal double EffectiveIconSize
{
get => GetValue(EffectiveIconSizeProperty);
set => SetValue(EffectiveIconSizeProperty, value);
}
public ITransform? TextRenderTransform
{
get => GetValue(TextRenderTransformProperty);
set => SetValue(TextRenderTransformProperty, value);
}
private AvatarContentType _contentType = AvatarContentType.Icon;
internal AvatarContentType ContentType
{
get => _contentType;
set => SetAndRaise(ContentTypeProperty, ref _contentType, value);
}
Control IMotionAwareControl.PropertyBindTarget => this;
Control IControlSharedTokenResourcesHost.HostControl => this;
string IControlSharedTokenResourcesHost.TokenId => ButtonToken.ID;
#endregion
private AvatarSizeType? _originSizeType;
private TextBlock? _textPresenter;
static Avatar()
{
AffectsMeasure<Avatar>(SizeTypeProperty, TextProperty);
AffectsRender<Avatar>(ShapeProperty, IconProperty, SrcProperty, GapProperty);
}
public Avatar()
{
this.RegisterResources();
this.BindMotionProperties();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SizeProperty)
{
if (!double.IsNaN(Size))
{
_originSizeType = SizeType;
SizeType = AvatarSizeType.Custom;
}
else
{
if (_originSizeType.HasValue)
{
SizeType = _originSizeType.Value;
}
}
}
else if (change.Property == SizeTypeProperty)
{
ConfigureIconSize();
}
else if (change.Property == SrcProperty ||
change.Property == IconProperty ||
change.Property == TextProperty)
{
ConfigureContentType();
}
if (change.Property == ContentTypeProperty ||
change.Property == GapProperty)
{
ConfigureTextRenderTransform();
}
else if (change.Property == ShapeProperty)
{
ConfigureShape();
}
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_textPresenter = e.NameScope.Find<TextBlock>(AvatarThemeConstants.TextPresenterPart);
if (_textPresenter != null)
{
_textPresenter.SizeChanged += (sender, args) =>
{
ConfigureTextRenderTransform();
};
}
ConfigureShape();
ConfigureIconSize();
ConfigureContentType();
}
private void ConfigureIconSize()
{
if (SizeType == AvatarSizeType.Custom)
{
Debug.Assert(!double.IsNaN(Size));
// 不影响模板设置
SetValue(WidthProperty, Size, BindingPriority.Template);
SetValue(HeightProperty, Size, BindingPriority.Template);
SetValue(EffectiveIconSizeProperty,
(Icon != null || Src != null) ? Size / 2 : 18, BindingPriority.Template);
}
}
private void ConfigureContentType()
{
if (Src != null)
{
ContentType = AvatarContentType.SvgImage;
}
else if (BitmapSrc != null)
{
ContentType = AvatarContentType.BitmapImage;
}
else if (Text != null)
{
ContentType = AvatarContentType.Text;
}
else
{
ContentType = AvatarContentType.Icon;
}
}
private void ConfigureTextRenderTransform()
{
if (ContentType != AvatarContentType.Text)
{
TextRenderTransform = null;
}
else
{
if (_textPresenter != null && Gap * 2 < Width)
{
double scale = 1;
double offsetX = 0;
var textWidth = TextUtils.CalculateTextSize(Text ?? string.Empty, FontSize, FontFamily).Width;
if (Gap * 2 < Width)
{
scale = (Width - Gap * 2) / textWidth;
scale = Math.Min(scale, 1.0);
offsetX = (scale * (textWidth - Width)) / 2;
}
var builder = new TransformOperations.Builder(2);
builder.AppendScale(scale, scale);
if (scale < 1.0)
{
builder.AppendTranslate(-offsetX, 0);
}
TextRenderTransform = builder.Build();
}
else
{
TextRenderTransform = null;
}
}
}
private void ConfigureShape()
{
if (Shape == AvatarShape.Circle)
{
SetValue(CornerRadiusProperty, new CornerRadius(Width / 2), BindingPriority.Template);
}
}
}
+409
View File
@@ -0,0 +1,409 @@
using System.Collections.Specialized;
using System.Reactive.Disposables;
using AtomUI.Data;
using AtomUI.Theme;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Metadata;
namespace AtomUI.Controls;
using ControlList = Avalonia.Controls.Controls;
public class AvatarGroup : Control,
IMotionAwareControl,
IControlSharedTokenResourcesHost,
IResourceBindingManager
{
#region 公共属性定义
public static readonly StyledProperty<IBrush?> FoldInfoAvatarBackgroundProperty =
AvaloniaProperty.Register<AvatarGroup, IBrush?>(nameof (FoldInfoAvatarBackground));
public static readonly StyledProperty<IBrush?> FoldInfoAvatarForegroundProperty =
AvaloniaProperty.Register<AvatarGroup, IBrush?>(nameof (FoldInfoAvatarForeground));
public static readonly StyledProperty<FlyoutTriggerType> FoldAvatarFlyoutTriggerTypeProperty =
AvaloniaProperty.Register<AvatarGroup, FlyoutTriggerType>(nameof(FoldAvatarFlyoutTriggerType), FlyoutTriggerType.Hover);
public static readonly StyledProperty<Thickness> BorderThicknessProperty =
Border.BorderThicknessProperty.AddOwner<AvatarGroup>();
public static readonly StyledProperty<int?> MaxDisplayCountProperty =
AvaloniaProperty.Register<AvatarGroup, int?>(nameof(MaxDisplayCount));
public static readonly StyledProperty<AvatarSizeType> SizeTypeProperty =
Avatar.SizeTypeProperty.AddOwner<AvatarGroup>();
public static readonly StyledProperty<double> SizeProperty =
Avatar.SizeProperty.AddOwner<AvatarGroup>();
public static readonly StyledProperty<AvatarShape> ShapeProperty =
Avatar.ShapeProperty.AddOwner<AvatarGroup>();
public static readonly StyledProperty<bool> IsMotionEnabledProperty =
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<AvatarGroup>();
public IBrush? FoldInfoAvatarBackground
{
get => GetValue(FoldInfoAvatarBackgroundProperty);
set => SetValue(FoldInfoAvatarBackgroundProperty, value);
}
public IBrush? FoldInfoAvatarForeground
{
get => GetValue(FoldInfoAvatarForegroundProperty);
set => SetValue(FoldInfoAvatarForegroundProperty, value);
}
public FlyoutTriggerType FoldAvatarFlyoutTriggerType
{
get => GetValue(FoldAvatarFlyoutTriggerTypeProperty);
set => SetValue(FoldAvatarFlyoutTriggerTypeProperty, value);
}
public Thickness BorderThickness
{
get => GetValue(BorderThicknessProperty);
set => SetValue(BorderThicknessProperty, value);
}
public int? MaxDisplayCount
{
get => GetValue(MaxDisplayCountProperty);
set => SetValue(MaxDisplayCountProperty, value);
}
public AvatarSizeType SizeType
{
get => GetValue(SizeTypeProperty);
set => SetValue(SizeTypeProperty, value);
}
public double Size
{
get => GetValue(SizeProperty);
set => SetValue(SizeProperty, value);
}
public AvatarShape Shape
{
get => GetValue(ShapeProperty);
set => SetValue(ShapeProperty, value);
}
public bool IsMotionEnabled
{
get => GetValue(IsMotionEnabledProperty);
set => SetValue(IsMotionEnabledProperty, value);
}
[Content] public ControlList Children { get; } = new();
#endregion
#region 内部属性定义
internal static readonly DirectProperty<AvatarGroup, double> GroupSpaceProperty =
AvaloniaProperty.RegisterDirect<AvatarGroup, double>(
nameof(GroupSpace),
o => o.GroupSpace,
(o, v) => o.GroupSpace = v);
internal static readonly DirectProperty<AvatarGroup, double> GroupOverlappingProperty =
AvaloniaProperty.RegisterDirect<AvatarGroup, double>(
nameof(GroupOverlapping),
o => o.GroupOverlapping,
(o, v) => o.GroupOverlapping = v);
private double _groupSpace;
internal double GroupSpace
{
get => _groupSpace;
set => SetAndRaise(GroupSpaceProperty, ref _groupSpace, value);
}
private double _groupOverlapping;
internal double GroupOverlapping
{
get => _groupOverlapping;
set => SetAndRaise(GroupOverlappingProperty, ref _groupOverlapping, value);
}
Control IMotionAwareControl.PropertyBindTarget => this;
Control IControlSharedTokenResourcesHost.HostControl => this;
string IControlSharedTokenResourcesHost.TokenId => ButtonToken.ID;
CompositeDisposable? IResourceBindingManager.ResourceBindingsDisposable
{
get => _resourceBindingsDisposable;
set => _resourceBindingsDisposable = value;
}
#endregion
private CompositeDisposable? _resourceBindingsDisposable;
private Avatar? _foldCountAvatar;
private FlyoutHost? _foldCountFlyout;
private StackPanel? _foldCountStackPanel;
private Dictionary<Control, CompositeDisposable> _disposables = new();
static AvatarGroup()
{
AffectsMeasure<AvatarGroup>(GroupOverlappingProperty);
}
public AvatarGroup()
{
Children.CollectionChanged += ChildrenChanged;
this.RegisterResources();
this.BindMotionProperties();
}
protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
var tobeAdded = e.NewItems!.OfType<Control>().ToList();
foreach (var item in tobeAdded)
{
if (item is Avatar avatar)
{
ConfigureAvatar(avatar);
}
}
if (!MaxDisplayCount.HasValue || LogicalChildren.Count < MaxDisplayCount.Value)
{
var startingIndex = e.NewStartingIndex;
foreach (var item in tobeAdded)
{
LogicalChildren.Insert(startingIndex, item);
VisualChildren.Insert(startingIndex, item);
startingIndex++;
}
}
break;
case NotifyCollectionChangedAction.Move:
if (!MaxDisplayCount.HasValue)
{
LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
}
else
{
if (e.OldStartingIndex < MaxDisplayCount.Value &&
e.OldStartingIndex + e.OldItems!.Count <= MaxDisplayCount.Value &&
e.NewStartingIndex <= MaxDisplayCount.Value)
{
LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
}
}
break;
case NotifyCollectionChangedAction.Remove:
var items = e.OldItems!.OfType<Control>().ToList();
LogicalChildren.RemoveAll(items);
VisualChildren.RemoveAll(items);
foreach (var child in items)
{
if (_disposables.TryGetValue(child, out var disposable))
{
disposable.Dispose();
}
_disposables.Remove(child);
}
break;
case NotifyCollectionChangedAction.Replace:
if (!MaxDisplayCount.HasValue)
{
for (var i = 0; i < e.OldItems!.Count; ++i)
{
var index = i + e.OldStartingIndex;
var child = (Control)e.NewItems![i]!;
LogicalChildren[index] = child;
VisualChildren[index] = child;
}
}
else
{
for (var i = 0; i < e.OldItems!.Count; ++i)
{
var index = i + e.OldStartingIndex;
if (index < MaxDisplayCount.Value)
{
var child = (Control)e.NewItems![i]!;
LogicalChildren[index] = child;
VisualChildren[index] = child;
}
}
}
break;
case NotifyCollectionChangedAction.Reset:
throw new NotSupportedException();
}
var zindex = 1;
foreach (var child in Children)
{
child.ZIndex = zindex++;
}
ConfigureFoldInfo();
InvalidateMeasureOnChildrenChanged();
}
private void ConfigureAvatar(Avatar avatar)
{
var disposable = new CompositeDisposable();
disposable.Add(BindUtils.RelayBind(this, BorderThicknessProperty, avatar, BorderThicknessProperty));
disposable.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, avatar, IsMotionEnabledProperty));
disposable.Add(BindUtils.RelayBind(this, ShapeProperty, avatar, ShapeProperty));
disposable.Add(BindUtils.RelayBind(this, SizeProperty, avatar, SizeProperty));
disposable.Add(BindUtils.RelayBind(this, SizeTypeProperty, avatar, SizeTypeProperty));
_disposables.Add(avatar, disposable);
}
private Avatar GetFoldCountAvatar()
{
_foldCountAvatar ??= new Avatar();
BindUtils.RelayBind(this, BorderThicknessProperty, _foldCountAvatar, BorderThicknessProperty);
BindUtils.RelayBind(this, ShapeProperty, _foldCountAvatar, ShapeProperty);
BindUtils.RelayBind(this, SizeProperty, _foldCountAvatar, SizeProperty);
BindUtils.RelayBind(this, SizeTypeProperty, _foldCountAvatar, SizeTypeProperty);
BindUtils.RelayBind(this, FoldInfoAvatarForegroundProperty, _foldCountAvatar, Avatar.ForegroundProperty);
BindUtils.RelayBind(this, FoldInfoAvatarBackgroundProperty, _foldCountAvatar, Avatar.BackgroundProperty);
return _foldCountAvatar;
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_resourceBindingsDisposable = new CompositeDisposable();
this.AddResourceBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, BorderThicknessProperty,
SharedTokenKey.BorderThickness,
BindingPriority.Template,
new RenderScaleAwareThicknessConfigure(this)));
this.AddResourceBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, GroupSpaceProperty,
AvatarTokenKey.GroupSpace));
this.AddResourceBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, GroupOverlappingProperty,
AvatarTokenKey.GroupOverlapping));
this.AddResourceBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, FoldInfoAvatarForegroundProperty,
AvatarTokenKey.AvatarColor));
this.AddResourceBindingDisposable(TokenResourceBinder.CreateTokenBinding(this, FoldInfoAvatarBackgroundProperty,
AvatarTokenKey.AvatarBg));
var foldCountAvatar = GetFoldCountAvatar();
if (_foldCountFlyout == null)
{
_foldCountFlyout = new FlyoutHost();
_foldCountFlyout.ZIndex = Int32.MaxValue;
_foldCountFlyout.AnchorTarget = foldCountAvatar;
_foldCountStackPanel = new StackPanel();
_foldCountStackPanel.Orientation = Orientation.Horizontal;
BindUtils.RelayBind(this, GroupSpaceProperty, _foldCountStackPanel, StackPanel.SpacingProperty);
BindUtils.RelayBind(this, FoldAvatarFlyoutTriggerTypeProperty, _foldCountFlyout, FlyoutHost.TriggerProperty);
_foldCountFlyout.Flyout = new Flyout()
{
Content = _foldCountStackPanel
};
}
ConfigureFoldInfo();
}
private void ConfigureFoldInfo()
{
var foldCountAvatar = GetFoldCountAvatar();
if (_foldCountFlyout != null)
{
if (MaxDisplayCount.HasValue && Children.Count > MaxDisplayCount.Value)
{
foldCountAvatar.Text = $"+{Children.Count - MaxDisplayCount.Value}";
LogicalChildren.Add(_foldCountFlyout);
VisualChildren.Add(_foldCountFlyout);
for (var i = MaxDisplayCount.Value; i < Children.Count; ++i)
{
_foldCountStackPanel?.Children.Add(Children[i]);
}
}
else
{
LogicalChildren.Remove(_foldCountFlyout);
VisualChildren.Remove(_foldCountFlyout);
_foldCountStackPanel?.Children.Clear();
}
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
this.DisposeTokenBindings();
}
private protected virtual void InvalidateMeasureOnChildrenChanged()
{
InvalidateMeasure();
}
protected override Size MeasureOverride(Size availableSize)
{
var size = base.MeasureOverride(availableSize);
// 理论上是统一的,我们的孩子都一样大
var count = LogicalChildren.Count;
var totalWidth = count * size.Width - (count - 1) * GroupOverlapping;
return size.WithWidth(totalWidth);
}
protected override Size ArrangeOverride(Size finalSize)
{
var offetX = 0.0;
for (var i = 0; i < LogicalChildren.Count; i++)
{
var child = LogicalChildren[i];
if (child is Control avatar)
{
var childSize = avatar.DesiredSize;
avatar.Arrange(new Rect(offetX, 0, childSize.Width, childSize.Height));
offetX += childSize.Width - GroupOverlapping;
}
}
return finalSize;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == FoldAvatarFlyoutTriggerTypeProperty)
{
ConfigureFoldAvatarCursor();
}
}
private void ConfigureFoldAvatarCursor()
{
var foldCountAvatar = GetFoldCountAvatar();
if (FoldAvatarFlyoutTriggerType == FlyoutTriggerType.Click)
{
foldCountAvatar.Cursor = new Cursor(StandardCursorType.Hand);
}
else
{
foldCountAvatar.Cursor = new Cursor(StandardCursorType.Arrow);
}
}
}
@@ -0,0 +1,9 @@
namespace AtomUI.Controls;
public static class AvatarPseudoClass
{
public const string Large = ":large";
public const string Middle = ":middle";
public const string Small = ":small";
public const string Custom = ":custom-size";
}
+81
View File
@@ -0,0 +1,81 @@
using AtomUI.Theme.TokenSystem;
using Avalonia.Media;
namespace AtomUI.Controls;
[ControlDesignToken]
internal class AvatarToken : AbstractControlDesignToken
{
public const string ID = "Avatar";
/// <summary>
/// 头像尺寸
/// </summary>
public double ContainerSize { get; set; }
/// <summary>
/// 大号头像尺寸
/// </summary>
public double ContainerSizeLG { get; set; }
/// <summary>
/// 小号头像尺寸
/// </summary>
public double ContainerSizeSM { get; set; }
/// <summary>
/// 头像文字大小
/// </summary>
public double TextFontSize { get; set; }
/// <summary>
/// 大号头像文字大小
/// </summary>
public double TextFontSizeLG { get; set; }
/// <summary>
/// 小号头像文字大小
/// </summary>
public double TextFontSizeSM { get; set; }
/// <summary>
/// 头像组间距
/// </summary>
public double GroupSpace { get; set; }
/// <summary>
/// 头像组重叠宽度
/// </summary>
public double GroupOverlapping { get; set; }
/// <summary>
/// 头像组边框颜色
/// </summary>
public Color GroupBorderColor { get; set; }
// 内部使用别名
public Color AvatarBg { get; set; }
public Color AvatarColor { get; set; }
public AvatarToken()
: base(ID)
{
}
protected internal override void CalculateFromAlias()
{
base.CalculateFromAlias();
ContainerSize = SharedToken.ControlHeight;
ContainerSizeLG = SharedToken.ControlHeightLG;
ContainerSizeSM = SharedToken.ControlHeightSM;
TextFontSize = Math.Round((SharedToken.FontSizeLG + SharedToken.FontSizeXL) / 2);
TextFontSizeLG = SharedToken.FontSizeHeading3;
TextFontSizeSM = SharedToken.FontSize;
GroupSpace = SharedToken.UniformlyMarginXXS;
GroupOverlapping = SharedToken.UniformlyMarginXS;
GroupBorderColor = SharedToken.ColorBorderBg;
AvatarBg = SharedToken.ColorTextPlaceholder;
AvatarColor = SharedToken.ColorTextLightSolid;
}
}
@@ -0,0 +1,118 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:atom="https://atomui.net">
<ControlTheme x:Key="{x:Type atom:Avatar}" TargetType="atom:Avatar">
<Setter Property="Template">
<ControlTemplate>
<Panel Name="RootLayout">
<Border Name="Frame"
Background="{TemplateBinding Background}"
CornerRadius="{TemplateBinding CornerRadius}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"/>
<atom:IconPresenter Name="IconPresenter"
IconWidth="{TemplateBinding EffectiveIconSize}"
IconHeight="{TemplateBinding EffectiveIconSize}"
Icon="{TemplateBinding Icon}"
NormalFilledBrush="{TemplateBinding Foreground}"
IsVisible="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
<Image Name="ImagePresenter"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
IsVisible="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="{TemplateBinding BitmapSrc}"/>
<Svg Name="SvgPresenter"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
IsVisible="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Path="{TemplateBinding Src}" />
<atom:TextBlock Name="{x:Static atom:AvatarThemeConstants.TextPresenterPart}"
Text="{TemplateBinding Text}"
RenderTransform="{TemplateBinding TextRenderTransform}"
IsVisible="False"
ClipToBounds="False"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Panel>
</ControlTemplate>
</Setter>
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Foreground" Value="{DynamicResource {x:Static atom:AvatarTokenKey.AvatarColor}}" />
<Setter Property="BorderBrush" Value="{DynamicResource {x:Static atom:AvatarTokenKey.GroupBorderColor}}" />
<Style Selector="^[ContentType=Icon]">
<Setter Property="Background" Value="{DynamicResource {x:Static atom:AvatarTokenKey.AvatarBg}}" />
<Style Selector="^ /template/ atom|IconPresenter#IconPresenter">
<Setter Property="IsVisible" Value="True" />
</Style>
</Style>
<Style Selector="^[ContentType=Text]">
<Setter Property="Background" Value="{DynamicResource {x:Static atom:AvatarTokenKey.AvatarBg}}" />
<Style Selector="^ /template/ atom|TextBlock#PART_TextPresenter">
<Setter Property="IsVisible" Value="True" />
</Style>
</Style>
<Style Selector="^[ContentType=BitmapImage]">
<Setter Property="Background" Value="Transparent" />
<Style Selector="^ /template/ Image#ImagePresenter">
<Setter Property="IsVisible" Value="True" />
</Style>
</Style>
<Style Selector="^[ContentType=SvgImage]">
<Setter Property="Background" Value="Transparent" />
<Style Selector="^ /template/ Svg#SvgPresenter">
<Setter Property="IsVisible" Value="True" />
</Style>
</Style>
<Style Selector="^[SizeType=Large]">
<Setter Property="Width" Value="{DynamicResource {x:Static atom:AvatarTokenKey.ContainerSizeLG}}" />
<Setter Property="Height" Value="{DynamicResource {x:Static atom:AvatarTokenKey.ContainerSizeLG}}" />
<Setter Property="EffectiveIconSize"
Value="{DynamicResource {x:Static atom:AvatarTokenKey.TextFontSizeLG}}" />
</Style>
<Style Selector="^[SizeType=Middle]">
<Setter Property="Width" Value="{DynamicResource {x:Static atom:AvatarTokenKey.ContainerSize}}" />
<Setter Property="Height" Value="{DynamicResource {x:Static atom:AvatarTokenKey.ContainerSize}}" />
<Setter Property="EffectiveIconSize" Value="{DynamicResource {x:Static atom:AvatarTokenKey.TextFontSize}}" />
</Style>
<Style Selector="^[SizeType=Small]">
<Setter Property="Width" Value="{DynamicResource {x:Static atom:AvatarTokenKey.ContainerSizeSM}}" />
<Setter Property="Height" Value="{DynamicResource {x:Static atom:AvatarTokenKey.ContainerSizeSM}}" />
<Setter Property="EffectiveIconSize"
Value="{DynamicResource {x:Static atom:AvatarTokenKey.TextFontSizeSM}}" />
</Style>
<Style Selector="^[Shape=Square]">
<Style Selector="^[SizeType=Large]">
<Setter Property="CornerRadius" Value="{DynamicResource {x:Static atom:SharedTokenKey.BorderRadiusLG}}" />
</Style>
<Style Selector="^[SizeType=Middle]">
<Setter Property="CornerRadius" Value="{DynamicResource {x:Static atom:SharedTokenKey.BorderRadius}}" />
</Style>
<Style Selector="^[SizeType=Small]">
<Setter Property="CornerRadius" Value="{DynamicResource {x:Static atom:SharedTokenKey.BorderRadiusSM}}" />
</Style>
<Style Selector="^[SizeType=Custom]">
<Setter Property="CornerRadius" Value="{DynamicResource {x:Static atom:SharedTokenKey.BorderRadius}}" />
</Style>
</Style>
</ControlTheme>
</ResourceDictionary>
@@ -0,0 +1,6 @@
namespace AtomUI.Controls.Themes;
internal static class AvatarThemeConstants
{
public const string TextPresenterPart = "PART_TextPresenter";
}
@@ -0,0 +1,6 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="AvatarTheme.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
@@ -12,6 +12,7 @@ namespace AtomUI.Theme
tokenTypes.Add(typeof(AtomUI.Controls.AdornerLayerToken));
tokenTypes.Add(typeof(AtomUI.Controls.AlertToken));
tokenTypes.Add(typeof(AtomUI.Controls.ArrowDecoratedBoxToken));
tokenTypes.Add(typeof(AtomUI.Controls.AvatarToken));
tokenTypes.Add(typeof(AtomUI.Controls.BadgeToken));
tokenTypes.Add(typeof(AtomUI.Controls.ButtonSpinnerToken));
tokenTypes.Add(typeof(AtomUI.Controls.ButtonToken));
@@ -47,6 +47,21 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey Padding = new TokenResourceKey("ArrowDecoratedBox.Padding");
}
public static class AvatarTokenKey
{
public static readonly TokenResourceKey AvatarBg = new TokenResourceKey("Avatar.AvatarBg");
public static readonly TokenResourceKey AvatarColor = new TokenResourceKey("Avatar.AvatarColor");
public static readonly TokenResourceKey ContainerSize = new TokenResourceKey("Avatar.ContainerSize");
public static readonly TokenResourceKey ContainerSizeLG = new TokenResourceKey("Avatar.ContainerSizeLG");
public static readonly TokenResourceKey ContainerSizeSM = new TokenResourceKey("Avatar.ContainerSizeSM");
public static readonly TokenResourceKey GroupBorderColor = new TokenResourceKey("Avatar.GroupBorderColor");
public static readonly TokenResourceKey GroupOverlapping = new TokenResourceKey("Avatar.GroupOverlapping");
public static readonly TokenResourceKey GroupSpace = new TokenResourceKey("Avatar.GroupSpace");
public static readonly TokenResourceKey TextFontSize = new TokenResourceKey("Avatar.TextFontSize");
public static readonly TokenResourceKey TextFontSizeLG = new TokenResourceKey("Avatar.TextFontSizeLG");
public static readonly TokenResourceKey TextFontSizeSM = new TokenResourceKey("Avatar.TextFontSizeSM");
}
public static class BadgeTokenKey
{
public static readonly TokenResourceKey BadgeColor = new TokenResourceKey("Badge.BadgeColor");