mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-26 11:10:34 +08:00
feat(Avatar): port Avatar control and AvatarShowCase from release/5.0 with optimizations
This commit is contained in:
@@ -23,5 +23,6 @@ public sealed class ShowCaseViewModule : IViewModule
|
||||
locator.Map<SpinViewModel, SpinShowCase>(() => new SpinShowCase());
|
||||
locator.Map<FlexPanelViewModel, FlexPanelShowCase>(() => new FlexPanelShowCase());
|
||||
locator.Map<CarouselViewModel, CarouselShowCase>(() => new CarouselShowCase());
|
||||
locator.Map<AvatarViewModel, AvatarShowCase>(() => new AvatarShowCase());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
using System.Reactive;
|
||||
using System.Reactive.Disposables;
|
||||
using AtomUI.Controls;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace AtomUIGallery.ShowCases.ViewModels;
|
||||
|
||||
public class AvatarViewModel : ReactiveObject, IRoutableViewModel, IActivatableViewModel
|
||||
{
|
||||
public static EntityKey ID = "Avatar";
|
||||
|
||||
public ViewModelActivator Activator { get; }
|
||||
public IScreen HostScreen { get; }
|
||||
public string UrlPathSegment { get; } = ID.ToString();
|
||||
|
||||
private string? _avatarText;
|
||||
|
||||
public string? AvatarText
|
||||
{
|
||||
get => _avatarText;
|
||||
set => this.RaiseAndSetIfChanged(ref _avatarText, value);
|
||||
}
|
||||
|
||||
private double? _avatarGap;
|
||||
|
||||
public double? AvatarGap
|
||||
{
|
||||
get => _avatarGap;
|
||||
set => this.RaiseAndSetIfChanged(ref _avatarGap, value);
|
||||
}
|
||||
|
||||
private string? _avatarBackground;
|
||||
|
||||
public string? AvatarBackground
|
||||
{
|
||||
get => _avatarBackground;
|
||||
set => this.RaiseAndSetIfChanged(ref _avatarBackground, value);
|
||||
}
|
||||
|
||||
private int _textCurrentIndex = 0;
|
||||
private int _gapCurrentIndex = 0;
|
||||
|
||||
private readonly List<string> _userList;
|
||||
private readonly List<string> _colorList;
|
||||
private readonly List<double> _gapList;
|
||||
|
||||
public ReactiveCommand<Unit, Unit> ChangeUserCommand { get; }
|
||||
public ReactiveCommand<Unit, Unit> ChangeGapCommand { get; }
|
||||
|
||||
public AvatarViewModel(IScreen screen)
|
||||
{
|
||||
Activator = new ViewModelActivator();
|
||||
HostScreen = screen;
|
||||
|
||||
_userList = ["U", "Lucy", "Tom", "Edward"];
|
||||
_colorList = ["#f56a00", "#7265e6", "#ffbf00", "#00a2ae"];
|
||||
_gapList = [4, 3, 2, 1];
|
||||
|
||||
ChangeUserCommand = ReactiveCommand.Create(SetupAvatarText);
|
||||
ChangeGapCommand = ReactiveCommand.Create(SetupAvatarGap);
|
||||
|
||||
this.WhenActivated((CompositeDisposable disposables) =>
|
||||
{
|
||||
SetupAvatarText();
|
||||
SetupAvatarGap();
|
||||
});
|
||||
}
|
||||
|
||||
private void SetupAvatarText()
|
||||
{
|
||||
var index = (_textCurrentIndex++) % 4;
|
||||
AvatarText = _userList[index];
|
||||
AvatarBackground = _colorList[index];
|
||||
}
|
||||
|
||||
private void SetupAvatarGap()
|
||||
{
|
||||
var index = (_gapCurrentIndex++) % 4;
|
||||
AvatarGap = _gapList[index];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
x:Class="AtomUIGallery.ShowCases.Views.AvatarShowCase"
|
||||
xmlns:atom="https://atomui.net"
|
||||
xmlns:antdicons="https://atomui.net/icons/antdesign"
|
||||
xmlns:gallery="https://atomui.net/oss-controls/gallery"
|
||||
xmlns:vm="using:AtomUIGallery.ShowCases.ViewModels"
|
||||
x:DataType="vm:AvatarViewModel">
|
||||
<gallery:ShowCasePanel>
|
||||
<gallery:ShowCasePanel.Styles>
|
||||
<Style Selector="atom|Avatar">
|
||||
<Setter Property="VerticalAlignment" Value="Center"/>
|
||||
</Style>
|
||||
</gallery:ShowCasePanel.Styles>
|
||||
<gallery:ShowCaseItem Title="Basic"
|
||||
Description="Three sizes and two shapes are available.">
|
||||
<StackPanel Orientation="Vertical" Spacing="20">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider UserOutlined}" Size="64" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider UserOutlined}" SizeType="Large" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider UserOutlined}" SizeType="Small" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider UserOutlined}" Size="14" />
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<atom:Avatar Shape="Square" Icon="{antdicons:AntDesignIconProvider UserOutlined}" Size="64" />
|
||||
<atom:Avatar Shape="Square" Icon="{antdicons:AntDesignIconProvider UserOutlined}" SizeType="Large" />
|
||||
<atom:Avatar Shape="Square" Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
<atom:Avatar Shape="Square" Icon="{antdicons:AntDesignIconProvider UserOutlined}" SizeType="Small" />
|
||||
<atom:Avatar Shape="Square" Icon="{antdicons:AntDesignIconProvider UserOutlined}" Size="14" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</gallery:ShowCaseItem>
|
||||
|
||||
<gallery:ShowCaseItem Title="Type"
|
||||
Description="Image, Icon and letter are supported, and the latter two kinds of avatar can have custom colors and background colors.">
|
||||
<StackPanel Orientation="Vertical" Spacing="20">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
<atom:Avatar>U</atom:Avatar>
|
||||
<atom:Avatar Size="40">USER</atom:Avatar>
|
||||
<atom:Avatar Src="avares://AtomUIGallery/Assets/AvatarShowCase/AntDesign.svg" />
|
||||
<atom:Avatar Background="#fde3cf" Foreground="#f56a00">U</atom:Avatar>
|
||||
<atom:Avatar Background="#87d068" Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</gallery:ShowCaseItem>
|
||||
|
||||
<gallery:ShowCaseItem Title="Autoset Font Size"
|
||||
Description="For letter type Avatar, when the letters are too long to display, the font size can be automatically adjusted according to the width of the Avatar. You can also use gap to set the unit distance between left and right sides.">
|
||||
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||
<atom:Avatar Background="{Binding AvatarBackground}"
|
||||
Gap="{Binding AvatarGap}"
|
||||
SizeType="Large"
|
||||
Text="{Binding AvatarText}"/>
|
||||
<atom:Button Name="ChangeUserButton"
|
||||
ButtonType="Default"
|
||||
SizeType="Small"
|
||||
VerticalAlignment="Center">
|
||||
ChangeUser
|
||||
</atom:Button>
|
||||
<atom:Button Name="ChangeGapButton"
|
||||
ButtonType="Default"
|
||||
SizeType="Small"
|
||||
VerticalAlignment="Center">
|
||||
ChangeGap
|
||||
</atom:Button>
|
||||
</StackPanel>
|
||||
</gallery:ShowCaseItem>
|
||||
|
||||
<gallery:ShowCaseItem Title="Avatar.Group"
|
||||
Description="Avatar group display.">
|
||||
<StackPanel Orientation="Vertical" Spacing="20">
|
||||
<atom:AvatarGroup>
|
||||
<atom:Avatar Src="avares://AtomUIGallery/Assets/AvatarShowCase/PeopleAvatar1.svg"/>
|
||||
<atom:Avatar Background="#f56a00">K</atom:Avatar>
|
||||
<atom:Avatar Background="#87d068" Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider AntDesignOutlined}" Background="#1677ff"/>
|
||||
</atom:AvatarGroup>
|
||||
<atom:Separator/>
|
||||
<atom:AvatarGroup MaxDisplayCount="2"
|
||||
FoldInfoAvatarForeground="#f56a00"
|
||||
FoldInfoAvatarBackground="#fde3cf">
|
||||
<atom:Avatar Src="avares://AtomUIGallery/Assets/AvatarShowCase/PeopleAvatar2.svg"/>
|
||||
<atom:Avatar Background="#f56a00">K</atom:Avatar>
|
||||
<atom:Avatar Background="#87d068" Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider AntDesignOutlined}" Background="#1677ff"/>
|
||||
</atom:AvatarGroup>
|
||||
<atom:Separator/>
|
||||
<atom:AvatarGroup MaxDisplayCount="2"
|
||||
FoldInfoAvatarForeground="#f56a00"
|
||||
FoldInfoAvatarBackground="#fde3cf"
|
||||
SizeType="Large">
|
||||
<atom:Avatar Src="avares://AtomUIGallery/Assets/AvatarShowCase/PeopleAvatar3.svg"/>
|
||||
<atom:Avatar Background="#f56a00">K</atom:Avatar>
|
||||
<atom:Avatar Background="#87d068" Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider AntDesignOutlined}" Background="#1677ff"/>
|
||||
</atom:AvatarGroup>
|
||||
<atom:Separator/>
|
||||
<atom:AvatarGroup MaxDisplayCount="2"
|
||||
FoldInfoAvatarForeground="#f56a00"
|
||||
FoldInfoAvatarBackground="#fde3cf"
|
||||
SizeType="Large"
|
||||
FoldAvatarFlyoutTriggerType="Click">
|
||||
<atom:Avatar BitmapSrc="/Assets/AvatarShowCase/PeopleAvatar4.png"/>
|
||||
<atom:Avatar Background="#f56a00">K</atom:Avatar>
|
||||
<atom:Avatar Background="#87d068" Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider AntDesignOutlined}" Background="#1677ff"/>
|
||||
</atom:AvatarGroup>
|
||||
<atom:Separator/>
|
||||
<atom:AvatarGroup Shape="Square">
|
||||
<atom:Avatar Background="#fde3cf">A</atom:Avatar>
|
||||
<atom:Avatar Background="#f56a00">K</atom:Avatar>
|
||||
<atom:Avatar Background="#87d068" Icon="{antdicons:AntDesignIconProvider UserOutlined}" />
|
||||
<atom:Avatar Icon="{antdicons:AntDesignIconProvider AntDesignOutlined}" Background="#1677ff"/>
|
||||
</atom:AvatarGroup>
|
||||
</StackPanel>
|
||||
</gallery:ShowCaseItem>
|
||||
</gallery:ShowCasePanel>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Reactive.Disposables.Fluent;
|
||||
using AtomUIGallery.ShowCases.ViewModels;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Avalonia;
|
||||
|
||||
namespace AtomUIGallery.ShowCases.Views;
|
||||
|
||||
public partial class AvatarShowCase : ReactiveUserControl<AvatarViewModel>
|
||||
{
|
||||
public AvatarShowCase()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
this.BindCommand(ViewModel!, vm => vm.ChangeUserCommand, v => v.ChangeUserButton)
|
||||
.DisposeWith(disposables);
|
||||
this.BindCommand(ViewModel!, vm => vm.ChangeGapCommand, v => v.ChangeGapButton)
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ internal class en_US : LanguageProvider
|
||||
public const string Navigation_ButtonSpinner = "ButtonSpinner";
|
||||
public const string DataEntry = "Data Entry";
|
||||
public const string DataDisplay = "Data Display";
|
||||
public const string DataDisplay_Avatar = "Avatar";
|
||||
public const string DataDisplay_Carousel = "Carousel";
|
||||
public const string Feedback = "Feedback";
|
||||
public const string Feedback_Alert = "Alert";
|
||||
|
||||
@@ -21,6 +21,7 @@ internal class zh_CN : LanguageProvider
|
||||
public const string Navigation_ButtonSpinner = "ButtonSpinner 按钮微调器";
|
||||
public const string DataEntry = "数据录入";
|
||||
public const string DataDisplay = "数据展示";
|
||||
public const string DataDisplay_Avatar = "Avatar 头像";
|
||||
public const string DataDisplay_Carousel = "Carousel 走马灯";
|
||||
public const string Feedback = "反馈";
|
||||
public const string Feedback_Alert = "Alert 警告提示";
|
||||
|
||||
@@ -68,6 +68,7 @@ public class CaseNavigationViewModel : ReactiveObject, IActivatableViewModel
|
||||
_showCaseViewModelFactories.Add(SpinViewModel.ID, () => new SpinViewModel(HostScreen));
|
||||
_showCaseViewModelFactories.Add(FlexPanelViewModel.ID, () => new FlexPanelViewModel(HostScreen));
|
||||
_showCaseViewModelFactories.Add(CarouselViewModel.ID, () => new CarouselViewModel(HostScreen));
|
||||
_showCaseViewModelFactories.Add(AvatarViewModel.ID, () => new AvatarViewModel(HostScreen));
|
||||
}
|
||||
|
||||
private void DoNavigateTo(EntityKey showCaseId)
|
||||
|
||||
@@ -44,6 +44,8 @@
|
||||
|
||||
<atom:NavMenuNode Header="{gallery:CaseNavigationLangResource DataDisplay}"
|
||||
Icon="{antdicons:AntDesignIconProvider Kind=AreaChartOutlined}">
|
||||
<atom:NavMenuNode Header="{gallery:CaseNavigationLangResource DataDisplay_Avatar}"
|
||||
ItemKey="{x:Static viewmodels:AvatarViewModel.ID}" />
|
||||
<atom:NavMenuNode Header="{gallery:CaseNavigationLangResource DataDisplay_Carousel}"
|
||||
ItemKey="{x:Static viewmodels:CarouselViewModel.ID}" />
|
||||
</atom:NavMenuNode>
|
||||
|
||||
@@ -0,0 +1,290 @@
|
||||
using System.Diagnostics;
|
||||
using AtomUI.Media;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Transformation;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
namespace AtomUI.Controls.Commons;
|
||||
|
||||
internal enum AvatarContentType
|
||||
{
|
||||
Icon,
|
||||
BitmapImage,
|
||||
SvgImage,
|
||||
Text
|
||||
}
|
||||
|
||||
[TemplatePart("PART_TextPresenter", typeof(TextBlock))]
|
||||
public abstract class AbstractAvatar : TemplatedControl, IMotionAwareControl
|
||||
{
|
||||
#region 公共属性定义
|
||||
|
||||
public static readonly StyledProperty<double> GapProperty =
|
||||
AvaloniaProperty.Register<AbstractAvatar, double>(nameof(Gap), defaultValue: 4.0);
|
||||
|
||||
public static readonly StyledProperty<PathIcon?> IconProperty =
|
||||
AvaloniaProperty.Register<AbstractAvatar, PathIcon?>(nameof(Icon));
|
||||
|
||||
public static readonly StyledProperty<IImage?> BitmapSrcProperty =
|
||||
AvaloniaProperty.Register<AbstractAvatar, IImage?>(nameof(BitmapSrc));
|
||||
|
||||
public static readonly StyledProperty<string?> SrcProperty =
|
||||
AvaloniaProperty.Register<AbstractAvatar, string?>(nameof(Src));
|
||||
|
||||
public static readonly StyledProperty<string?> TextProperty =
|
||||
AvaloniaProperty.Register<TemplatedControl, string?>(nameof (Text));
|
||||
|
||||
public static readonly StyledProperty<CustomizableSizeType> SizeTypeProperty =
|
||||
AvaloniaProperty.Register<AbstractAvatar, CustomizableSizeType>(nameof(SizeType),
|
||||
CustomizableSizeType.Middle);
|
||||
|
||||
public static readonly StyledProperty<double> SizeProperty =
|
||||
AvaloniaProperty.Register<AbstractAvatar, double>(nameof(Size), Double.NaN);
|
||||
|
||||
public static readonly StyledProperty<AvatarShape> ShapeProperty =
|
||||
AvaloniaProperty.Register<AbstractAvatar, AvatarShape>(nameof(Shape), AvatarShape.Circle);
|
||||
|
||||
public static readonly StyledProperty<bool> IsMotionEnabledProperty =
|
||||
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<AbstractAvatar>();
|
||||
|
||||
public double Gap
|
||||
{
|
||||
get => GetValue(GapProperty);
|
||||
set => SetValue(GapProperty, value);
|
||||
}
|
||||
|
||||
public PathIcon? 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 CustomizableSizeType 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<AbstractAvatar, double>(nameof(EffectiveIconSize));
|
||||
|
||||
internal static readonly StyledProperty<ITransform?> TextRenderTransformProperty =
|
||||
AvaloniaProperty.Register<AbstractAvatar, ITransform?>(nameof(TextRenderTransform));
|
||||
|
||||
internal static readonly DirectProperty<AbstractAvatar, AvatarContentType> ContentTypeProperty =
|
||||
AvaloniaProperty.RegisterDirect<AbstractAvatar, 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);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private CustomizableSizeType? _originSizeType;
|
||||
private TextBlock? _textPresenter;
|
||||
|
||||
static AbstractAvatar()
|
||||
{
|
||||
AffectsMeasure<AbstractAvatar>(SizeTypeProperty, TextProperty);
|
||||
AffectsRender<AbstractAvatar>(ShapeProperty, IconProperty, SrcProperty, GapProperty);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
{
|
||||
base.OnPropertyChanged(change);
|
||||
if (change.Property == SizeProperty)
|
||||
{
|
||||
if (!double.IsNaN(Size))
|
||||
{
|
||||
_originSizeType = SizeType;
|
||||
SizeType = CustomizableSizeType.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);
|
||||
if (_textPresenter != null)
|
||||
{
|
||||
_textPresenter.SizeChanged -= HandleTextPresenterSizeChanged;
|
||||
}
|
||||
_textPresenter = e.NameScope.Find<TextBlock>("PART_TextPresenter");
|
||||
if (_textPresenter != null)
|
||||
{
|
||||
_textPresenter.SizeChanged += HandleTextPresenterSizeChanged;
|
||||
}
|
||||
ConfigureShape();
|
||||
ConfigureIconSize();
|
||||
ConfigureContentType();
|
||||
}
|
||||
|
||||
private void HandleTextPresenterSizeChanged(object? sender, SizeChangedEventArgs e)
|
||||
{
|
||||
ConfigureTextRenderTransform();
|
||||
}
|
||||
|
||||
private void ConfigureIconSize()
|
||||
{
|
||||
if (SizeType == CustomizableSizeType.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AtomUI.Controls.Commons;
|
||||
|
||||
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";
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public enum AvatarShape
|
||||
{
|
||||
Circle,
|
||||
Square
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using AtomUI.Controls.Commons;
|
||||
using AtomUI.Theme;
|
||||
|
||||
namespace AtomUI.Desktop.Controls;
|
||||
|
||||
public class Avatar : AbstractAvatar
|
||||
{
|
||||
public Avatar()
|
||||
{
|
||||
this.RegisterTokenResourceScope(AvatarToken.ScopeProvider);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
using System.Collections.Specialized;
|
||||
using AtomUI.Controls;
|
||||
using AtomUI.Theme;
|
||||
using AtomUI.Utils;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Metadata;
|
||||
|
||||
namespace AtomUI.Desktop.Controls;
|
||||
|
||||
using ControlList = Avalonia.Controls.Controls;
|
||||
|
||||
public class AvatarGroup : TemplatedControl, IMotionAwareControl
|
||||
{
|
||||
#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<int?> MaxDisplayCountProperty =
|
||||
AvaloniaProperty.Register<AvatarGroup, int?>(nameof(MaxDisplayCount));
|
||||
|
||||
public static readonly StyledProperty<CustomizableSizeType> 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 int? MaxDisplayCount
|
||||
{
|
||||
get => GetValue(MaxDisplayCountProperty);
|
||||
set => SetValue(MaxDisplayCountProperty, value);
|
||||
}
|
||||
|
||||
public CustomizableSizeType 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 StyledProperty<double> GroupSpaceProperty =
|
||||
AvaloniaProperty.Register<AvatarGroup, double>(nameof (GroupSpace));
|
||||
|
||||
internal static readonly StyledProperty<double> GroupOverlappingProperty =
|
||||
AvaloniaProperty.Register<AvatarGroup, double>(nameof (GroupOverlapping));
|
||||
|
||||
internal double GroupSpace
|
||||
{
|
||||
get => GetValue(GroupSpaceProperty);
|
||||
set => SetValue(GroupSpaceProperty, value);
|
||||
}
|
||||
|
||||
internal double GroupOverlapping
|
||||
{
|
||||
get => GetValue(GroupOverlappingProperty);
|
||||
set => SetValue(GroupOverlappingProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private Avatar? _foldCountAvatar;
|
||||
private FlyoutHost? _foldCountFlyout;
|
||||
private StackPanel? _foldCountStackPanel;
|
||||
|
||||
static AvatarGroup()
|
||||
{
|
||||
AffectsMeasure<AvatarGroup>(GroupOverlappingProperty, GroupSpaceProperty);
|
||||
}
|
||||
|
||||
public AvatarGroup()
|
||||
{
|
||||
Children.CollectionChanged += ChildrenChanged;
|
||||
this.RegisterTokenResourceScope(AvatarToken.ScopeProvider);
|
||||
this.ConfigureMotionBindingStyle();
|
||||
}
|
||||
|
||||
protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var item in e.NewItems!.OfType<Control>())
|
||||
{
|
||||
if (item is Avatar avatar)
|
||||
{
|
||||
ConfigureAvatar(avatar);
|
||||
}
|
||||
}
|
||||
if (!MaxDisplayCount.HasValue || LogicalChildren.Count < MaxDisplayCount.Value)
|
||||
{
|
||||
var startingIndex = e.NewStartingIndex;
|
||||
foreach (var item in e.NewItems!.OfType<Control>())
|
||||
{
|
||||
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 removedItems = e.OldItems!.OfType<Control>();
|
||||
LogicalChildren.RemoveAll(removedItems);
|
||||
VisualChildren.RemoveAll(removedItems);
|
||||
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();
|
||||
}
|
||||
|
||||
UpdateChildrenZIndex(e);
|
||||
ConfigureFoldInfo();
|
||||
InvalidateMeasureOnChildrenChanged();
|
||||
}
|
||||
|
||||
private void UpdateChildrenZIndex(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
// Update ZIndex for all children after the change
|
||||
var startIndex = e.Action == NotifyCollectionChangedAction.Remove
|
||||
? e.OldStartingIndex
|
||||
: e.NewStartingIndex;
|
||||
for (var i = startIndex; i < Children.Count; i++)
|
||||
{
|
||||
Children[i].ZIndex = i + 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
// Update ZIndex for affected range
|
||||
var minIndex = Math.Min(e.OldStartingIndex, e.NewStartingIndex);
|
||||
var maxIndex = Math.Max(e.OldStartingIndex + e.OldItems!.Count,
|
||||
e.NewStartingIndex + e.OldItems!.Count);
|
||||
for (var i = minIndex; i < Math.Min(maxIndex, Children.Count); i++)
|
||||
{
|
||||
Children[i].ZIndex = i + 1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void BindAvatarProperties(Avatar avatar)
|
||||
{
|
||||
avatar[!BorderThicknessProperty] = this[!BorderThicknessProperty];
|
||||
avatar[!ShapeProperty] = this[!ShapeProperty];
|
||||
avatar[!SizeProperty] = this[!SizeProperty];
|
||||
avatar[!SizeTypeProperty] = this[!SizeTypeProperty];
|
||||
}
|
||||
|
||||
private void ConfigureAvatar(Avatar avatar)
|
||||
{
|
||||
BindAvatarProperties(avatar);
|
||||
avatar[!IsMotionEnabledProperty] = this[!IsMotionEnabledProperty];
|
||||
}
|
||||
|
||||
private Avatar GetFoldCountAvatar()
|
||||
{
|
||||
if (_foldCountAvatar == null)
|
||||
{
|
||||
_foldCountAvatar = new Avatar();
|
||||
|
||||
BindAvatarProperties(_foldCountAvatar);
|
||||
_foldCountAvatar[!ForegroundProperty] = this[!FoldInfoAvatarForegroundProperty];
|
||||
_foldCountAvatar[!BackgroundProperty] = this[!FoldInfoAvatarBackgroundProperty];
|
||||
}
|
||||
|
||||
return _foldCountAvatar;
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
var foldCountAvatar = GetFoldCountAvatar();
|
||||
if (_foldCountFlyout == null)
|
||||
{
|
||||
_foldCountFlyout = new FlyoutHost();
|
||||
_foldCountFlyout.ZIndex = Int32.MaxValue;
|
||||
_foldCountFlyout.Content = foldCountAvatar;
|
||||
_foldCountStackPanel = new StackPanel();
|
||||
_foldCountStackPanel.Orientation = Orientation.Horizontal;
|
||||
|
||||
_foldCountStackPanel[!StackPanel.SpacingProperty] = this[!GroupSpaceProperty];
|
||||
_foldCountFlyout[!FlyoutHost.TriggerProperty] = this[!FoldAvatarFlyoutTriggerTypeProperty];
|
||||
|
||||
_foldCountFlyout.Flyout = new Flyout
|
||||
{
|
||||
Content = _foldCountStackPanel
|
||||
};
|
||||
}
|
||||
|
||||
ConfigureFoldInfo();
|
||||
}
|
||||
|
||||
private void ConfigureFoldInfo()
|
||||
{
|
||||
var foldCountAvatar = GetFoldCountAvatar();
|
||||
if (_foldCountFlyout != null && _foldCountStackPanel != 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 offsetX = 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(offsetX, 0, childSize.Width, childSize.Height));
|
||||
offsetX += 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,86 @@
|
||||
using AtomUI.Desktop.Controls.DesignTokens;
|
||||
using AtomUI.Theme;
|
||||
using AtomUI.Theme.TokenSystem;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Desktop.Controls;
|
||||
|
||||
[ControlDesignToken]
|
||||
internal class AvatarToken : AbstractControlDesignToken
|
||||
{
|
||||
public const string ID = "Avatar";
|
||||
public static readonly ControlTokenResourceScopeProvider ScopeProvider = new(ID);
|
||||
|
||||
/// <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)
|
||||
{
|
||||
}
|
||||
|
||||
public override void CalculateTokenValues(bool isDarkMode)
|
||||
{
|
||||
base.CalculateTokenValues(isDarkMode);
|
||||
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;
|
||||
}
|
||||
|
||||
protected override Type GetTokenKindType() => typeof(AvatarTokenKind);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<ResourceDictionary
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:atom="https://atomui.net"
|
||||
x:ClassModifier="internal">
|
||||
<ControlTheme x:Key="{x:Type atom:AvatarGroup}" TargetType="atom:AvatarGroup">
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="GroupSpace" Value="{atom:AvatarTokenResource GroupSpace}" />
|
||||
<Setter Property="GroupOverlapping" Value="{atom:AvatarTokenResource GroupOverlapping}" />
|
||||
<Setter Property="FoldInfoAvatarForeground"
|
||||
Value="{atom:AvatarTokenResource AvatarColor}" />
|
||||
<Setter Property="FoldInfoAvatarBackground" Value="{atom:AvatarTokenResource AvatarBg}" />
|
||||
<Setter Property="BorderThickness" Value="{atom:SharedTokenResource BorderThickness}" />
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,119 @@
|
||||
<ResourceDictionary
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:atom="https://atomui.net"
|
||||
x:ClassModifier="internal">
|
||||
<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"
|
||||
Width="{TemplateBinding EffectiveIconSize}"
|
||||
Height="{TemplateBinding EffectiveIconSize}"
|
||||
Icon="{TemplateBinding Icon}"
|
||||
IconBrush="{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="PART_TextPresenter"
|
||||
Text="{TemplateBinding Text}"
|
||||
RenderTransform="{TemplateBinding TextRenderTransform}"
|
||||
IsVisible="False"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center" />
|
||||
</Panel>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
|
||||
<Setter Property="IsMotionEnabled" Value="{atom:SharedTokenResource EnableMotion}" />
|
||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="Foreground" Value="{atom:AvatarTokenResource AvatarColor}" />
|
||||
<Setter Property="BorderBrush" Value="{atom:AvatarTokenResource GroupBorderColor}" />
|
||||
<Style Selector="^[ContentType=Icon]">
|
||||
<Setter Property="Background" Value="{atom:AvatarTokenResource AvatarBg}" />
|
||||
<Style Selector="^ /template/ atom|IconPresenter#IconPresenter">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[ContentType=Text]">
|
||||
<Setter Property="Background" Value="{atom:AvatarTokenResource 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="{atom:AvatarTokenResource ContainerSizeLG}" />
|
||||
<Setter Property="Height" Value="{atom:AvatarTokenResource ContainerSizeLG}" />
|
||||
<Setter Property="EffectiveIconSize"
|
||||
Value="{atom:AvatarTokenResource TextFontSizeLG}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[SizeType=Middle]">
|
||||
<Setter Property="Width" Value="{atom:AvatarTokenResource ContainerSize}" />
|
||||
<Setter Property="Height" Value="{atom:AvatarTokenResource ContainerSize}" />
|
||||
<Setter Property="EffectiveIconSize" Value="{atom:AvatarTokenResource TextFontSize}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[SizeType=Small]">
|
||||
<Setter Property="Width" Value="{atom:AvatarTokenResource ContainerSizeSM}" />
|
||||
<Setter Property="Height" Value="{atom:AvatarTokenResource ContainerSizeSM}" />
|
||||
<Setter Property="EffectiveIconSize"
|
||||
Value="{atom:AvatarTokenResource TextFontSizeSM}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[Shape=Square]">
|
||||
<Style Selector="^[SizeType=Large]">
|
||||
<Setter Property="CornerRadius" Value="{atom:SharedTokenResource BorderRadiusLG}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[SizeType=Middle]">
|
||||
<Setter Property="CornerRadius" Value="{atom:SharedTokenResource BorderRadius}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[SizeType=Small]">
|
||||
<Setter Property="CornerRadius" Value="{atom:SharedTokenResource BorderRadiusSM}" />
|
||||
</Style>
|
||||
|
||||
<Style Selector="^[SizeType=Custom]">
|
||||
<Setter Property="CornerRadius" Value="{atom:SharedTokenResource BorderRadius}" />
|
||||
</Style>
|
||||
</Style>
|
||||
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,7 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceInclude Source="AvatarGroupTheme.axaml" />
|
||||
<ResourceInclude Source="AvatarTheme.axaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
Reference in New Issue
Block a user