mirror of
https://gitee.com/chinware/atomui.git
synced 2024-12-02 03:47:52 +08:00
完成 LoadingIndicator
This commit is contained in:
parent
e95ba39dc5
commit
90e288a33c
@ -0,0 +1,43 @@
|
||||
<UserControl
|
||||
x:Class="AtomUI.Demo.Desktop.ShowCase.LoadingIndicatorShowCase"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:atom="https://atomui.net"
|
||||
xmlns:showcase="clr-namespace:AtomUI.Demo.Desktop.ShowCase"
|
||||
mc:Ignorable="d">
|
||||
<showcase:ShowCasePanel>
|
||||
<showcase:ShowCaseItem
|
||||
Title="Basic Usage"
|
||||
Description="A simple loading status.">
|
||||
<atom:LoadingIndicator/>
|
||||
</showcase:ShowCaseItem>
|
||||
|
||||
<showcase:ShowCaseItem
|
||||
Title="Size"
|
||||
Description="A small LoadingIndicator is used for loading text, default sized LoadingIndicator for loading a card-level block, and large LoadingIndicator used for loading a page.">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<atom:LoadingIndicator SizeType="Small" VerticalAlignment="Center"/>
|
||||
<atom:LoadingIndicator SizeType="Middle" VerticalAlignment="Center"/>
|
||||
<atom:LoadingIndicator SizeType="Large" VerticalAlignment="Center"/>
|
||||
</StackPanel>
|
||||
</showcase:ShowCaseItem>
|
||||
|
||||
<showcase:ShowCaseItem
|
||||
Title="Custom spinning indicator"
|
||||
Description="Use custom loading indicator.">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<atom:LoadingIndicator SizeType="Small"
|
||||
VerticalAlignment="Center"
|
||||
CustomIndicatorIcon="{atom:IconProvider Kind=LoadingOutlined,NormalFilledColor=#1677ff}"/>
|
||||
<atom:LoadingIndicator SizeType="Middle"
|
||||
VerticalAlignment="Center"
|
||||
CustomIndicatorIcon="{atom:IconProvider Kind=LoadingOutlined,NormalFilledColor=#1677ff}"/>
|
||||
<atom:LoadingIndicator SizeType="Large"
|
||||
VerticalAlignment="Center"
|
||||
CustomIndicatorIcon="{atom:IconProvider Kind=LoadingOutlined,NormalFilledColor=#1677ff}"/>
|
||||
</StackPanel>
|
||||
</showcase:ShowCaseItem>
|
||||
</showcase:ShowCasePanel>
|
||||
</UserControl>
|
@ -2,9 +2,9 @@ using Avalonia.Controls;
|
||||
|
||||
namespace AtomUI.Demo.Desktop.ShowCase;
|
||||
|
||||
public partial class ProgressIndicatorShowCase : UserControl
|
||||
public partial class LoadingIndicatorShowCase : UserControl
|
||||
{
|
||||
public ProgressIndicatorShowCase()
|
||||
public LoadingIndicatorShowCase()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="AtomUI.Demo.Desktop.ShowCase.ProgressIndicatorShowCase"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:atom="https://atomui.net"
|
||||
xmlns:showcase="clr-namespace:AtomUI.Demo.Desktop.ShowCase"
|
||||
mc:Ignorable="d">
|
||||
<showcase:ShowCasePanel>
|
||||
|
||||
</showcase:ShowCasePanel>
|
||||
</UserControl>
|
@ -1,13 +0,0 @@
|
||||
<UserControl
|
||||
x:Class="AtomUI.Demo.Desktop.ShowCase.SpinIndicatorShowCase"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:atom="https://atomui.net"
|
||||
xmlns:showcase="clr-namespace:AtomUI.Demo.Desktop.ShowCase"
|
||||
mc:Ignorable="d">
|
||||
<showcase:ShowCasePanel>
|
||||
|
||||
</showcase:ShowCasePanel>
|
||||
</UserControl>
|
@ -1,13 +0,0 @@
|
||||
using Avalonia.Controls;
|
||||
using Button = AtomUI.Controls.Button;
|
||||
using ToggleSwitch = AtomUI.Controls.ToggleSwitch;
|
||||
|
||||
namespace AtomUI.Demo.Desktop.ShowCase;
|
||||
|
||||
public partial class SpinIndicatorShowCase : UserControl
|
||||
{
|
||||
public SpinIndicatorShowCase()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
@ -81,8 +81,8 @@
|
||||
<TabItem Header="ProgressBar">
|
||||
<showcases:ProgressBarShowCase />
|
||||
</TabItem>
|
||||
<TabItem Header="ProgressIndicator">
|
||||
<showcases:ProgressIndicatorShowCase />
|
||||
<TabItem Header="LoadingIndicator">
|
||||
<showcases:LoadingIndicatorShowCase />
|
||||
</TabItem>
|
||||
<TabItem Header="Segmented">
|
||||
<showcases:SegmentedShowCase />
|
||||
@ -111,9 +111,6 @@
|
||||
<TabItem Header="InputNumber">
|
||||
<showcases:InputNumberShowCase />
|
||||
</TabItem>
|
||||
<TabItem Header="SpinIndicator">
|
||||
<showcases:SpinIndicatorShowCase />
|
||||
</TabItem>
|
||||
<TabItem Header="TitleBar">
|
||||
<showcases:TitleBarShowCase />
|
||||
</TabItem>
|
||||
|
@ -19,21 +19,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AtomUI.Generator\AtomUI.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Avatar\" />
|
||||
<Folder Include="Collapse\" />
|
||||
<Folder Include="ComboBox\" />
|
||||
<Folder Include="EmptyIndicator\" />
|
||||
<Folder Include="Expander\" />
|
||||
<Folder Include="Input\" />
|
||||
<Folder Include="Menu\" />
|
||||
<Folder Include="Pagination\" />
|
||||
<Folder Include="ProgressIndicator\" />
|
||||
<Folder Include="Slider\" />
|
||||
<Folder Include="TabBar\" />
|
||||
<Folder Include="TabControl\" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<PropertyGroup>
|
||||
<!-- enable private APIs -->
|
||||
<AvaloniaAccessUnstablePrivateApis>true</AvaloniaAccessUnstablePrivateApis>
|
||||
|
@ -5,7 +5,7 @@ using Avalonia.Media;
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
[ControlDesignToken]
|
||||
public class BadgeToken : AbstractControlDesignToken
|
||||
internal class BadgeToken : AbstractControlDesignToken
|
||||
{
|
||||
public const string ID = "Badge";
|
||||
|
||||
|
@ -25,110 +25,78 @@ public enum ButtonShape
|
||||
|
||||
public partial class Button : AvaloniaButton, ISizeTypeAware
|
||||
{
|
||||
// 需要改造
|
||||
public static readonly DirectProperty<Button, ButtonType> ButtonTypeProperty =
|
||||
AvaloniaProperty.RegisterDirect<Button, ButtonType>(nameof(ButtonType),
|
||||
o => o.ButtonType,
|
||||
(o, v) => o.ButtonType = v,
|
||||
ButtonType.Default);
|
||||
#region 公共属性定义
|
||||
public static readonly StyledProperty<ButtonType> ButtonTypeProperty =
|
||||
AvaloniaProperty.Register<Button, ButtonType>(nameof(ButtonType), ButtonType.Default);
|
||||
|
||||
public static readonly DirectProperty<Button, ButtonShape> ButtonShapeProperty =
|
||||
AvaloniaProperty.RegisterDirect<Button, ButtonShape>(nameof(Shape),
|
||||
o => o.Shape,
|
||||
(o, v) => o.Shape = v,
|
||||
ButtonShape.Default);
|
||||
public static readonly StyledProperty<ButtonShape> ButtonShapeProperty =
|
||||
AvaloniaProperty.Register<Button, ButtonShape>(nameof(Shape), ButtonShape.Default);
|
||||
|
||||
public static readonly DirectProperty<Button, bool> IsDangerProperty =
|
||||
AvaloniaProperty.RegisterDirect<Button, bool>(nameof(IsDanger),
|
||||
o => o.IsDanger,
|
||||
(o, v) => o.IsDanger = v,
|
||||
false);
|
||||
public static readonly StyledProperty<bool> IsDangerProperty =
|
||||
AvaloniaProperty.Register<Button, bool>(nameof(IsDanger), false);
|
||||
|
||||
public static readonly DirectProperty<Button, bool> IsGhostProperty =
|
||||
AvaloniaProperty.RegisterDirect<Button, bool>(nameof(IsGhost),
|
||||
o => o.IsGhost,
|
||||
(o, v) => o.IsGhost = v,
|
||||
false);
|
||||
public static readonly StyledProperty<bool> IsGhostProperty =
|
||||
AvaloniaProperty.Register<Button, bool>(nameof(IsGhost), false);
|
||||
|
||||
public static readonly DirectProperty<Button, ButtonSizeType> SizeTypeProperty =
|
||||
AvaloniaProperty.RegisterDirect<Button, ButtonSizeType>(nameof(SizeType),
|
||||
o => o.SizeType,
|
||||
(o, v) => o.SizeType = v,
|
||||
ButtonSizeType.Middle);
|
||||
public static readonly StyledProperty<ButtonSizeType> SizeTypeProperty =
|
||||
AvaloniaProperty.Register<Button, ButtonSizeType>(nameof(SizeType), ButtonSizeType.Middle);
|
||||
|
||||
public static readonly DirectProperty<Button, PathIcon?> IconProperty
|
||||
= AvaloniaProperty.RegisterDirect<Button, PathIcon?>(nameof(Icon),
|
||||
o => o.Icon,
|
||||
(o, v) => o.Icon = v);
|
||||
public static readonly StyledProperty<PathIcon?> IconProperty
|
||||
= AvaloniaProperty.Register<Button, PathIcon?>(nameof(Icon));
|
||||
|
||||
public static readonly DirectProperty<Button, string> TextProperty
|
||||
= AvaloniaProperty.RegisterDirect<Button, string>(nameof(Text),
|
||||
o => o.Text,
|
||||
(o, v) => o.Text = v,
|
||||
string.Empty);
|
||||
|
||||
private ButtonType _buttonType = ButtonType.Default;
|
||||
public static readonly StyledProperty<string?> TextProperty
|
||||
= AvaloniaProperty.Register<Button, string?>(nameof(Text));
|
||||
|
||||
public ButtonType ButtonType
|
||||
{
|
||||
get => _buttonType;
|
||||
set => SetAndRaise(ButtonTypeProperty, ref _buttonType, value);
|
||||
get => GetValue(ButtonTypeProperty);
|
||||
set => SetValue(ButtonTypeProperty, value);
|
||||
}
|
||||
|
||||
private ButtonShape _shape = ButtonShape.Default;
|
||||
|
||||
public ButtonShape Shape
|
||||
{
|
||||
get => _shape;
|
||||
set => SetAndRaise(ButtonShapeProperty, ref _shape, value);
|
||||
get => GetValue(ButtonShapeProperty);
|
||||
set => SetValue(ButtonShapeProperty, value);
|
||||
}
|
||||
|
||||
private bool _isDanger = false;
|
||||
|
||||
|
||||
public bool IsDanger
|
||||
{
|
||||
get => _isDanger;
|
||||
set => SetAndRaise(IsDangerProperty, ref _isDanger, value);
|
||||
get => GetValue(IsDangerProperty);
|
||||
set => SetValue(IsDangerProperty, value);
|
||||
}
|
||||
|
||||
private bool _isGhost = false;
|
||||
|
||||
|
||||
public bool IsGhost
|
||||
{
|
||||
get => _isGhost;
|
||||
set => SetAndRaise(IsGhostProperty, ref _isGhost, value);
|
||||
get => GetValue(IsGhostProperty);
|
||||
set => SetValue(IsGhostProperty, value);
|
||||
}
|
||||
|
||||
private ButtonSizeType _sizeType = ButtonSizeType.Middle;
|
||||
|
||||
|
||||
public ButtonSizeType SizeType
|
||||
{
|
||||
get => _sizeType;
|
||||
set => SetAndRaise(SizeTypeProperty, ref _sizeType, value);
|
||||
get => GetValue(SizeTypeProperty);
|
||||
set => SetValue(SizeTypeProperty, value);
|
||||
}
|
||||
|
||||
private PathIcon? _icon;
|
||||
|
||||
|
||||
public PathIcon? Icon
|
||||
{
|
||||
get => _icon;
|
||||
set => SetAndRaise(IconProperty, ref _icon, value);
|
||||
get => GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
private string _text = string.Empty;
|
||||
|
||||
public string Text
|
||||
|
||||
public string? Text
|
||||
{
|
||||
get => _text;
|
||||
set => SetAndRaise(TextProperty, ref _text, value);
|
||||
get => GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
static Button()
|
||||
{
|
||||
AffectsMeasure<Button>(SizeTypeProperty,
|
||||
ButtonShapeProperty,
|
||||
IconProperty,
|
||||
WidthProperty,
|
||||
IconProperty,
|
||||
WidthProperty,
|
||||
HeightProperty);
|
||||
AffectsRender<Button>(ButtonTypeProperty,
|
||||
IsDangerProperty,
|
||||
|
@ -79,7 +79,7 @@ public partial class Button : IWaveAdornerInfoProvider, IControlCustomStyle
|
||||
|
||||
private void CreateMainLayout()
|
||||
{
|
||||
if (Text.Length == 0 && Content is string content) {
|
||||
if (Text is null && Content is string content) {
|
||||
Text = content;
|
||||
}
|
||||
_label = new Label()
|
||||
@ -402,7 +402,7 @@ public partial class Button : IWaveAdornerInfoProvider, IControlCustomStyle
|
||||
{
|
||||
if (Icon is not null) {
|
||||
_stackPanel!.Children.Insert(0, Icon);
|
||||
if (Text.Length != 0) {
|
||||
if (Text is not null) {
|
||||
if (SizeType == SizeType.Small) {
|
||||
_controlTokenBinder.AddControlBinding(Icon, WidthProperty, GlobalResourceKey.IconSizeSM);
|
||||
_controlTokenBinder.AddControlBinding(Icon, HeightProperty, GlobalResourceKey.IconSizeSM);
|
||||
|
@ -88,6 +88,14 @@
|
||||
public const string IndicatorTristateMarkSize = "IndicatorTristateMarkSize";
|
||||
}
|
||||
|
||||
public static class LoadingIndicatorResourceKey
|
||||
{
|
||||
public const string DotSize = "DotSize";
|
||||
public const string DotSizeSM = "DotSizeSM";
|
||||
public const string DotSizeLG = "DotSizeLG";
|
||||
public const string IndicatorDuration = "IndicatorDuration";
|
||||
}
|
||||
|
||||
public static class MarqueeLabelResourceKey
|
||||
{
|
||||
public const string CycleSpace = "CycleSpace";
|
||||
|
443
src/AtomUI.Controls/Loading/LoadingIndicator.cs
Normal file
443
src/AtomUI.Controls/Loading/LoadingIndicator.cs
Normal file
@ -0,0 +1,443 @@
|
||||
using AtomUI.Data;
|
||||
using AtomUI.Icon;
|
||||
using AtomUI.Styling;
|
||||
using AtomUI.Utils;
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.LogicalTree;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Media.Transformation;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public partial class LoadingIndicator : Control, ISizeTypeAware, IControlCustomStyle
|
||||
{
|
||||
#region 公共属性定义
|
||||
public static readonly StyledProperty<SizeType> SizeTypeProperty =
|
||||
AvaloniaProperty.Register<LoadingIndicator, SizeType>(nameof(SizeType), SizeType.Middle);
|
||||
|
||||
public static readonly StyledProperty<string?> LoadingMsgProperty =
|
||||
AvaloniaProperty.Register<LoadingIndicator, string?>(nameof(LoadingMsg));
|
||||
|
||||
public static readonly StyledProperty<bool> IsShowLoadingMsgProperty =
|
||||
AvaloniaProperty.Register<LoadingIndicator, bool>(nameof(IsShowLoadingMsg), false);
|
||||
|
||||
public static readonly StyledProperty<PathIcon?> CustomIndicatorIconProperty =
|
||||
AvaloniaProperty.Register<LoadingIndicator, PathIcon?>(nameof(CustomIndicatorIcon));
|
||||
|
||||
public static readonly StyledProperty<TimeSpan?> MotionDurationProperty =
|
||||
AvaloniaProperty.Register<LoadingIndicator, TimeSpan?>(nameof(MotionDuration));
|
||||
|
||||
public static readonly StyledProperty<Easing?> MotionEasingCurveProperty =
|
||||
AvaloniaProperty.Register<LoadingIndicator, Easing?>(nameof(MotionEasingCurve));
|
||||
|
||||
public SizeType SizeType
|
||||
{
|
||||
get => GetValue(SizeTypeProperty);
|
||||
set => SetValue(SizeTypeProperty, value);
|
||||
}
|
||||
|
||||
public string? LoadingMsg
|
||||
{
|
||||
get => GetValue(LoadingMsgProperty);
|
||||
set => SetValue(LoadingMsgProperty, value);
|
||||
}
|
||||
|
||||
public bool IsShowLoadingMsg
|
||||
{
|
||||
get => GetValue(IsShowLoadingMsgProperty);
|
||||
set => SetValue(IsShowLoadingMsgProperty, value);
|
||||
}
|
||||
|
||||
public PathIcon? CustomIndicatorIcon
|
||||
{
|
||||
get => GetValue(CustomIndicatorIconProperty);
|
||||
set => SetValue(CustomIndicatorIconProperty, value);
|
||||
}
|
||||
|
||||
public TimeSpan? MotionDuration
|
||||
{
|
||||
get => GetValue(MotionDurationProperty);
|
||||
set => SetValue(MotionDurationProperty, value);
|
||||
}
|
||||
|
||||
public Easing? MotionEasingCurve
|
||||
{
|
||||
get => GetValue(MotionEasingCurveProperty);
|
||||
set => SetValue(MotionEasingCurveProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool _initialized = false;
|
||||
private IControlCustomStyle _customStyle;
|
||||
private ControlTokenBinder _controlTokenBinder;
|
||||
private Animation? _animation;
|
||||
private TextBlock? _textBlock;
|
||||
private RenderInfo? _renderInfo;
|
||||
private CancellationTokenSource?_cancellationTokenSource;
|
||||
|
||||
private const double LARGE_INDICATOR_SIZE = 48;
|
||||
private const double MIDDLE_INDICATOR_SIZE = 32;
|
||||
private const double SMALL_INDICATOR_SIZE = 16;
|
||||
private const double MAX_CONTENT_WIDTH = 120; // 拍脑袋的决定
|
||||
private const double MAX_CONTENT_HEIGHT = 400;
|
||||
private const double DOT_START_OPACITY = 0.3;
|
||||
|
||||
static LoadingIndicator()
|
||||
{
|
||||
AffectsMeasure<LoadingIndicator>(SizeTypeProperty,
|
||||
LoadingMsgProperty,
|
||||
IsShowLoadingMsgProperty,
|
||||
CustomIndicatorIconProperty);
|
||||
AffectsRender<LoadingIndicator>(IndicatorAngleProperty);
|
||||
}
|
||||
|
||||
public LoadingIndicator()
|
||||
{
|
||||
_customStyle = this;
|
||||
_controlTokenBinder = new ControlTokenBinder(this, LoadingIndicatorToken.ID);
|
||||
}
|
||||
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToLogicalTree(e);
|
||||
if (!_initialized) {
|
||||
_customStyle.SetupUi();
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
BuildIndicatorAnimation();
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
_animation!.RunAsync(this, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_cancellationTokenSource?.Cancel();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
_customStyle.HandlePropertyChangedForStyle(e);
|
||||
}
|
||||
|
||||
void IControlCustomStyle.HandlePropertyChangedForStyle(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property == CustomIndicatorIconProperty) {
|
||||
if (_initialized) {
|
||||
var oldCustomIcon = e.GetOldValue<PathIcon?>();
|
||||
if (oldCustomIcon is not null) {
|
||||
LogicalChildren.Remove(oldCustomIcon);
|
||||
VisualChildren.Remove(oldCustomIcon);
|
||||
}
|
||||
SetupCustomIndicator();
|
||||
}
|
||||
} else if (e.Property == SizeTypeProperty) {
|
||||
HandleSizeTypeChanged();
|
||||
} else if (e.Property == IndicatorAngleProperty) {
|
||||
if (CustomIndicatorIcon is not null) {
|
||||
HandleIndicatorAngleChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 只在使用自定义的 Icon 的时候有效
|
||||
private void HandleIndicatorAngleChanged()
|
||||
{
|
||||
if (CustomIndicatorIcon is not null) {
|
||||
var builder = new TransformOperations.Builder(1);
|
||||
builder.AppendRotate(MathUtils.Deg2Rad(IndicatorAngle));
|
||||
CustomIndicatorIcon.RenderTransform = builder.Build();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupCustomIndicator()
|
||||
{
|
||||
if (CustomIndicatorIcon is not null) {
|
||||
VisualChildren.Add(CustomIndicatorIcon);
|
||||
LogicalChildren.Add(CustomIndicatorIcon);
|
||||
// 暂时为了简单起见,我们在这里先只能使用 SizeType 的大小
|
||||
var indicatorSize = GetIndicatorSize(SizeType);
|
||||
CustomIndicatorIcon.Width = indicatorSize;
|
||||
CustomIndicatorIcon.Height = indicatorSize;
|
||||
CustomIndicatorIcon.IconMode = IconMode.Normal;
|
||||
CustomIndicatorIcon.VerticalAlignment = VerticalAlignment.Center;
|
||||
CustomIndicatorIcon.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSizeTypeChanged()
|
||||
{
|
||||
if (CustomIndicatorIcon is not null) {
|
||||
var indicatorSize = GetIndicatorSize(SizeType);
|
||||
CustomIndicatorIcon.Width = indicatorSize;
|
||||
CustomIndicatorIcon.Height = indicatorSize;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var targetWidth = 0d;
|
||||
var targetHeight = 0d;
|
||||
if (IsShowLoadingMsg) {
|
||||
var size = base.MeasureOverride(new Size(Math.Min(availableSize.Width, MAX_CONTENT_WIDTH),
|
||||
Math.Min(availableSize.Height, MAX_CONTENT_HEIGHT)));
|
||||
targetWidth += size.Width;
|
||||
targetHeight += size.Height;
|
||||
if (size.Height > 0) {
|
||||
targetHeight += GetLoadMsgPaddingTop();
|
||||
}
|
||||
}
|
||||
|
||||
var indicatorSize = GetIndicatorSize(SizeType);
|
||||
targetWidth = Math.Max(indicatorSize, targetWidth);
|
||||
targetHeight += indicatorSize;
|
||||
|
||||
return new Size(targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
private double GetLoadMsgPaddingTop()
|
||||
{
|
||||
return (GetEffectiveDotSize() - _fontSizeToken) / 2 + 2;
|
||||
}
|
||||
|
||||
private double GetEffectiveDotSize()
|
||||
{
|
||||
var dotSize = 0d;
|
||||
if (SizeType == SizeType.Large) {
|
||||
dotSize = _dotSizeLGToken;
|
||||
} else if (SizeType == SizeType.Middle) {
|
||||
dotSize = _dotSizeToken;
|
||||
} else {
|
||||
dotSize = _dotSizeSMToken;
|
||||
}
|
||||
|
||||
return dotSize;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
if (IsShowLoadingMsg) {
|
||||
var msgRect = GetLoadingMsgRect();
|
||||
_textBlock!.Arrange(msgRect);
|
||||
}
|
||||
|
||||
if (CustomIndicatorIcon is not null) {
|
||||
var indicatorRect = GetIndicatorRect();
|
||||
CustomIndicatorIcon.Arrange(indicatorRect);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
#region IControlCustomStyle 实现
|
||||
|
||||
void IControlCustomStyle.SetupUi()
|
||||
{
|
||||
SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Left, BindingPriority.Style);
|
||||
SetValue(VerticalAlignmentProperty, VerticalAlignment.Top, BindingPriority.Style);
|
||||
|
||||
_textBlock = new TextBlock()
|
||||
{
|
||||
Text = LoadingMsg,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
};
|
||||
|
||||
LogicalChildren.Add(_textBlock);
|
||||
VisualChildren.Add(_textBlock);
|
||||
|
||||
SetupCustomIndicator();
|
||||
|
||||
_customStyle.ApplyFixedStyleConfig();
|
||||
}
|
||||
|
||||
void IControlCustomStyle.ApplyFixedStyleConfig()
|
||||
{
|
||||
_controlTokenBinder.AddControlBinding(DotSizeTokenProperty, LoadingIndicatorResourceKey.DotSize);
|
||||
_controlTokenBinder.AddControlBinding(DotSizeSMTokenProperty, LoadingIndicatorResourceKey.DotSizeSM);
|
||||
_controlTokenBinder.AddControlBinding(DotSizeLGTokenProperty, LoadingIndicatorResourceKey.DotSizeLG);
|
||||
_controlTokenBinder.AddControlBinding(IndicatorDurationTokenProperty, LoadingIndicatorResourceKey.IndicatorDuration);
|
||||
_controlTokenBinder.AddControlBinding(FontSizeTokenProperty, GlobalResourceKey.FontSize);
|
||||
_controlTokenBinder.AddControlBinding(MarginXXSTokenProperty, GlobalResourceKey.MarginXXS);
|
||||
_controlTokenBinder.AddControlBinding(ColorPrimaryTokenProperty, GlobalResourceKey.ColorPrimary);
|
||||
}
|
||||
|
||||
private void BuildIndicatorAnimation(bool force = false)
|
||||
{
|
||||
if (force || _animation is null) {
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_animation = new Animation()
|
||||
{
|
||||
IterationCount = IterationCount.Infinite,
|
||||
Easing = MotionEasingCurve ?? new LinearEasing(),
|
||||
Duration = MotionDuration ?? _indicatorDurationToken,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame
|
||||
{
|
||||
Setters = { new Setter(IndicatorAngleProperty, 0d), }, Cue = new Cue(0.0d)
|
||||
},
|
||||
new KeyFrame
|
||||
{
|
||||
Setters = { new Setter(IndicatorAngleProperty, 360d), }, Cue = new Cue(1.0d)
|
||||
}
|
||||
}
|
||||
};
|
||||
_cancellationTokenSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
private Rect GetIndicatorRect()
|
||||
{
|
||||
var indicatorSize = GetIndicatorSize(SizeType);
|
||||
var offsetX = (DesiredSize.Width - indicatorSize) / 2;
|
||||
var offsetY = (DesiredSize.Height - indicatorSize) / 2;
|
||||
if (IsShowLoadingMsg && LoadingMsg is not null) {
|
||||
offsetY -= _textBlock!.DesiredSize.Height / 2;
|
||||
}
|
||||
|
||||
return new Rect(new Point(offsetX, offsetY), new Size(indicatorSize, indicatorSize));
|
||||
}
|
||||
|
||||
private Rect GetLoadingMsgRect()
|
||||
{
|
||||
if (!IsShowLoadingMsg) {
|
||||
return default;
|
||||
}
|
||||
|
||||
var indicatorRect = GetIndicatorRect();
|
||||
var offsetX = indicatorRect.Left;
|
||||
var offsetY = indicatorRect.Bottom;
|
||||
offsetX -= (_textBlock!.DesiredSize.Width - indicatorRect.Width) / 2;
|
||||
return new Rect(new Point(offsetX, offsetY), _textBlock.DesiredSize);
|
||||
}
|
||||
|
||||
private static double GetIndicatorSize(SizeType sizeType)
|
||||
{
|
||||
return sizeType switch
|
||||
{
|
||||
SizeType.Small => SMALL_INDICATOR_SIZE,
|
||||
SizeType.Middle => MIDDLE_INDICATOR_SIZE,
|
||||
SizeType.Large => LARGE_INDICATOR_SIZE,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(sizeType), sizeType, "Invalid value for SizeType")
|
||||
};
|
||||
}
|
||||
|
||||
private static double GetOpacityForAngle(double degree)
|
||||
{
|
||||
var mappedValue = (Math.Sin(MathUtils.Deg2Rad(degree)) + 1) / 2; // 将正弦波的范围从[-1, 1]映射到[0, 1]
|
||||
return DOT_START_OPACITY + (1 - DOT_START_OPACITY) * mappedValue;
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
_customStyle.PrepareRenderInfo();
|
||||
if (CustomIndicatorIcon is null) {
|
||||
RenderBuiltInIndicator(context);
|
||||
}
|
||||
|
||||
_renderInfo = null;
|
||||
}
|
||||
|
||||
private void RenderBuiltInIndicator(DrawingContext context)
|
||||
{
|
||||
if (_renderInfo is not null) {
|
||||
var itemSize = _renderInfo.IndicatorItemSize;
|
||||
var rightItemOpacity = GetOpacityForAngle(_indicatorAngle);
|
||||
var bottomItemOpacity = GetOpacityForAngle(_indicatorAngle + 90);
|
||||
var leftItemOpacity = GetOpacityForAngle(_indicatorAngle + 180);
|
||||
var topItemOpacity = GetOpacityForAngle(_indicatorAngle + 270);
|
||||
|
||||
var itemEdgeMargin = _renderInfo.ItemEdgeMargin;
|
||||
|
||||
var indicatorRect = GetIndicatorRect();
|
||||
var centerPoint = indicatorRect.Center;
|
||||
|
||||
var rightItemOffset = new Point(indicatorRect.Right - itemEdgeMargin - itemSize, centerPoint.Y - itemSize / 2);
|
||||
var bottomItemOffset = new Point(centerPoint.X - itemSize / 2, indicatorRect.Bottom - itemEdgeMargin - itemSize);
|
||||
var leftItemOffset = new Point(itemEdgeMargin, centerPoint.Y - itemSize / 2);
|
||||
var topItemOffset = new Point(centerPoint.X - itemSize / 2, itemEdgeMargin);
|
||||
|
||||
var matrix = Matrix.CreateTranslation(-indicatorRect.Center.X, -indicatorRect.Center.Y);
|
||||
matrix *= Matrix.CreateRotation(MathUtils.Deg2Rad(IndicatorAngle));
|
||||
matrix *= Matrix.CreateTranslation(indicatorRect.Center.X, indicatorRect.Center.Y);
|
||||
using var translateState = context.PushTransform(matrix);
|
||||
|
||||
{
|
||||
using var opacityState = context.PushOpacity(rightItemOpacity);
|
||||
var itemRect = new Rect(rightItemOffset, new Size(itemSize, itemSize));
|
||||
context.DrawEllipse(_renderInfo.DotBgBrush, null, itemRect);
|
||||
}
|
||||
{
|
||||
using var opacityState = context.PushOpacity(bottomItemOpacity);
|
||||
var itemRect = new Rect(bottomItemOffset, new Size(itemSize, itemSize));
|
||||
context.DrawEllipse(_renderInfo.DotBgBrush, null, itemRect);
|
||||
}
|
||||
{
|
||||
using var opacityState = context.PushOpacity(leftItemOpacity);
|
||||
var itemRect = new Rect(leftItemOffset, new Size(itemSize, itemSize));
|
||||
context.DrawEllipse(_renderInfo.DotBgBrush, null, itemRect);
|
||||
}
|
||||
{
|
||||
using var opacityState = context.PushOpacity(topItemOpacity);
|
||||
var itemRect = new Rect(topItemOffset, new Size(itemSize, itemSize));
|
||||
context.DrawEllipse(_renderInfo.DotBgBrush, null, itemRect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderCustomIndicator(DrawingContext context)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void IControlCustomStyle.PrepareRenderInfo()
|
||||
{
|
||||
_renderInfo = new RenderInfo();
|
||||
if (SizeType == SizeType.Large) {
|
||||
_renderInfo.DotSize = _dotSizeLGToken;
|
||||
_renderInfo.IndicatorItemSize = (_dotSizeLGToken - _marginXXSToken) / 2.5;
|
||||
} else if (SizeType == SizeType.Middle) {
|
||||
_renderInfo.DotSize = _dotSizeToken;
|
||||
_renderInfo.IndicatorItemSize = (_dotSizeToken - _marginXXSToken) / 2;
|
||||
} else {
|
||||
_renderInfo.DotSize = _dotSizeSMToken;
|
||||
_renderInfo.IndicatorItemSize = (_dotSizeSMToken - _marginXXSToken) / 2;
|
||||
}
|
||||
_renderInfo.IndicatorItemSize *= 0.9;
|
||||
if (SizeType == SizeType.Large) {
|
||||
_renderInfo.ItemEdgeMargin = _renderInfo.IndicatorItemSize / 1.5;
|
||||
} else if (SizeType == SizeType.Middle) {
|
||||
_renderInfo.ItemEdgeMargin = _renderInfo.IndicatorItemSize / 1.8;
|
||||
} else {
|
||||
_renderInfo.ItemEdgeMargin = 0.5;
|
||||
}
|
||||
|
||||
_renderInfo.DotBgBrush = _colorPrimaryToken;
|
||||
}
|
||||
|
||||
// 跟渲染相关的数据
|
||||
private class RenderInfo
|
||||
{
|
||||
public double DotSize { get; set; }
|
||||
public double IndicatorItemSize { get; set; }
|
||||
public double ItemEdgeMargin { get; set; }
|
||||
public IBrush? DotBgBrush { get; set; }
|
||||
};
|
||||
|
||||
#endregion
|
||||
}
|
84
src/AtomUI.Controls/Loading/LoadingIndicatorProperties.cs
Normal file
84
src/AtomUI.Controls/Loading/LoadingIndicatorProperties.cs
Normal file
@ -0,0 +1,84 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public partial class LoadingIndicator
|
||||
{
|
||||
#region Control token 值绑定属性定义
|
||||
|
||||
private double _dotSizeToken;
|
||||
|
||||
private static readonly DirectProperty<LoadingIndicator, double> DotSizeTokenProperty =
|
||||
AvaloniaProperty.RegisterDirect<LoadingIndicator, double>(
|
||||
nameof(_dotSizeToken),
|
||||
o => o._dotSizeToken,
|
||||
(o, v) => o._dotSizeToken = v);
|
||||
|
||||
private double _dotSizeSMToken;
|
||||
|
||||
private static readonly DirectProperty<LoadingIndicator, double> DotSizeSMTokenProperty =
|
||||
AvaloniaProperty.RegisterDirect<LoadingIndicator, double>(
|
||||
nameof(_dotSizeSMToken),
|
||||
o => o._dotSizeSMToken,
|
||||
(o, v) => o._dotSizeSMToken = v);
|
||||
|
||||
private double _dotSizeLGToken;
|
||||
|
||||
private static readonly DirectProperty<LoadingIndicator, double> DotSizeLGTokenProperty =
|
||||
AvaloniaProperty.RegisterDirect<LoadingIndicator, double>(
|
||||
nameof(_dotSizeLGToken),
|
||||
o => o._dotSizeLGToken,
|
||||
(o, v) => o._dotSizeLGToken = v);
|
||||
|
||||
private TimeSpan _indicatorDurationToken;
|
||||
|
||||
private static readonly DirectProperty<LoadingIndicator, TimeSpan> IndicatorDurationTokenProperty =
|
||||
AvaloniaProperty.RegisterDirect<LoadingIndicator, TimeSpan>(
|
||||
nameof(_indicatorDurationToken),
|
||||
o => o._indicatorDurationToken,
|
||||
(o, v) => o._indicatorDurationToken = v);
|
||||
|
||||
private double _fontSizeToken;
|
||||
|
||||
private static readonly DirectProperty<LoadingIndicator, double> FontSizeTokenProperty =
|
||||
AvaloniaProperty.RegisterDirect<LoadingIndicator, double>(
|
||||
nameof(_fontSizeToken),
|
||||
o => o._fontSizeToken,
|
||||
(o, v) => o._fontSizeToken = v);
|
||||
|
||||
private double _marginXXSToken;
|
||||
|
||||
private static readonly DirectProperty<LoadingIndicator, double> MarginXXSTokenProperty =
|
||||
AvaloniaProperty.RegisterDirect<LoadingIndicator, double>(
|
||||
nameof(_marginXXSToken),
|
||||
o => o._marginXXSToken,
|
||||
(o, v) => o._marginXXSToken = v);
|
||||
|
||||
private IBrush? _colorPrimaryToken;
|
||||
private static readonly DirectProperty<LoadingIndicator, IBrush?> ColorPrimaryTokenProperty =
|
||||
AvaloniaProperty.RegisterDirect<LoadingIndicator, IBrush?>(
|
||||
nameof(_colorPrimaryToken),
|
||||
o => o._colorPrimaryToken,
|
||||
(o, v) => o._colorPrimaryToken = v);
|
||||
#endregion
|
||||
|
||||
#region 私有属性
|
||||
|
||||
// 当前指示器的角度,动画输出目标属性
|
||||
|
||||
private static readonly DirectProperty<LoadingIndicator, double> IndicatorAngleProperty =
|
||||
AvaloniaProperty.RegisterDirect<LoadingIndicator, double>(
|
||||
nameof(IndicatorAngle),
|
||||
o => o.IndicatorAngle,
|
||||
(o, v) => o.IndicatorAngle = v);
|
||||
|
||||
|
||||
private double _indicatorAngle;
|
||||
private double IndicatorAngle
|
||||
{
|
||||
get => _indicatorAngle;
|
||||
set => SetAndRaise(IndicatorAngleProperty, ref _indicatorAngle, value);
|
||||
}
|
||||
#endregion
|
||||
}
|
45
src/AtomUI.Controls/Loading/LoadingIndicatorToken.cs
Normal file
45
src/AtomUI.Controls/Loading/LoadingIndicatorToken.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using AtomUI.TokenSystem;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
[ControlDesignToken]
|
||||
internal class LoadingIndicatorToken : AbstractControlDesignToken
|
||||
{
|
||||
public const string ID = "LoadingIndicator";
|
||||
|
||||
public LoadingIndicatorToken()
|
||||
: base(ID)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载图标尺寸
|
||||
/// </summary>
|
||||
public double DotSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加载图标尺寸
|
||||
/// </summary>
|
||||
public double DotSizeSM { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 大号加载图标尺寸
|
||||
/// </summary>
|
||||
public double DotSizeLG { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 加载器的周期时间
|
||||
/// </summary>
|
||||
public TimeSpan IndicatorDuration { get; set; }
|
||||
|
||||
internal override void CalculateFromAlias()
|
||||
{
|
||||
base.CalculateFromAlias();
|
||||
var controlHeightLG = _globalToken.HeightToken.ControlHeightLG;
|
||||
var controlHeight = _globalToken.SeedToken.ControlHeight;
|
||||
DotSize = controlHeightLG / 2;
|
||||
DotSizeSM = controlHeightLG * 0.35;
|
||||
DotSizeLG = controlHeight;
|
||||
IndicatorDuration = _globalToken.StyleToken.MotionDurationSlow * 4;
|
||||
}
|
||||
}
|
6
src/AtomUI.Controls/Loading/LoadingMask.cs
Normal file
6
src/AtomUI.Controls/Loading/LoadingMask.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class LoadingMask
|
||||
{
|
||||
|
||||
}
|
@ -14,4 +14,5 @@ internal interface IControlCustomStyle
|
||||
void ApplyRenderScalingAwareStyleConfig() {}
|
||||
void ApplySizeTypeStyleConfig() {}
|
||||
void HandlePropertyChangedForStyle(AvaloniaPropertyChangedEventArgs e) {}
|
||||
void PrepareRenderInfo() {}
|
||||
}
|
Loading…
Reference in New Issue
Block a user