diff --git a/Samples/AtomUI.Demo.Desktop/ShowCase/LoadingIndicatorShowCase.axaml b/Samples/AtomUI.Demo.Desktop/ShowCase/LoadingIndicatorShowCase.axaml
index c7561ba..8824c25 100644
--- a/Samples/AtomUI.Demo.Desktop/ShowCase/LoadingIndicatorShowCase.axaml
+++ b/Samples/AtomUI.Demo.Desktop/ShowCase/LoadingIndicatorShowCase.axaml
@@ -28,16 +28,65 @@
Title="Custom spinning indicator"
Description="Use custom loading indicator.">
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading state:
+
+
+
+
\ No newline at end of file
diff --git a/Samples/AtomUI.Demo.Desktop/ShowCase/LoadingIndicatorShowCase.axaml.cs b/Samples/AtomUI.Demo.Desktop/ShowCase/LoadingIndicatorShowCase.axaml.cs
index df4244d..a1c7be9 100644
--- a/Samples/AtomUI.Demo.Desktop/ShowCase/LoadingIndicatorShowCase.axaml.cs
+++ b/Samples/AtomUI.Demo.Desktop/ShowCase/LoadingIndicatorShowCase.axaml.cs
@@ -1,11 +1,22 @@
+using Avalonia;
using Avalonia.Controls;
namespace AtomUI.Demo.Desktop.ShowCase;
public partial class LoadingIndicatorShowCase : UserControl
{
+ public static readonly StyledProperty IsLoadingSwitchCheckedProperty =
+ AvaloniaProperty.Register(nameof(IsLoadingSwitchChecked), false);
+
+ public bool IsLoadingSwitchChecked
+ {
+ get => GetValue(IsLoadingSwitchCheckedProperty);
+ set => SetValue(IsLoadingSwitchCheckedProperty, value);
+ }
+
public LoadingIndicatorShowCase()
{
+ DataContext = this;
InitializeComponent();
}
diff --git a/src/AtomUI.Controls/ICornerRadiusInfoProvider.cs b/src/AtomUI.Controls/ICornerRadiusInfoProvider.cs
new file mode 100644
index 0000000..4b36d42
--- /dev/null
+++ b/src/AtomUI.Controls/ICornerRadiusInfoProvider.cs
@@ -0,0 +1,8 @@
+using Avalonia;
+
+namespace AtomUI.Controls;
+
+public interface ICornerRadiusInfoProvider
+{
+ public CornerRadius CornerRadius { get; }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Loading/LoadingIndicator.cs b/src/AtomUI.Controls/Loading/LoadingIndicator.cs
index ed1751f..51636ed 100644
--- a/src/AtomUI.Controls/Loading/LoadingIndicator.cs
+++ b/src/AtomUI.Controls/Loading/LoadingIndicator.cs
@@ -274,6 +274,7 @@ public partial class LoadingIndicator : Control, ISizeTypeAware, IControlCustomS
_controlTokenBinder.AddControlBinding(FontSizeTokenProperty, GlobalResourceKey.FontSize);
_controlTokenBinder.AddControlBinding(MarginXXSTokenProperty, GlobalResourceKey.MarginXXS);
_controlTokenBinder.AddControlBinding(ColorPrimaryTokenProperty, GlobalResourceKey.ColorPrimary);
+ _controlTokenBinder.AddControlBinding(_textBlock!, TextBlock.ForegroundProperty, GlobalResourceKey.ColorPrimary);
}
private void BuildIndicatorAnimation(bool force = false)
@@ -369,14 +370,14 @@ public partial class LoadingIndicator : Control, ISizeTypeAware, IControlCustomS
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 leftItemOffset = new Point(indicatorRect.Left + itemEdgeMargin, centerPoint.Y - itemSize / 2);
+ var topItemOffset = new Point(centerPoint.X - itemSize / 2, indicatorRect.Top + 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));
diff --git a/src/AtomUI.Controls/Loading/LoadingIndicatorAdorner.cs b/src/AtomUI.Controls/Loading/LoadingIndicatorAdorner.cs
new file mode 100644
index 0000000..cc71b2e
--- /dev/null
+++ b/src/AtomUI.Controls/Loading/LoadingIndicatorAdorner.cs
@@ -0,0 +1,61 @@
+using AtomUI.Styling;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+
+namespace AtomUI.Controls;
+
+public class LoadingIndicatorAdorner : Control, IControlCustomStyle
+{
+ private bool _initialized = false;
+ private IControlCustomStyle _customStyle;
+ private LoadingIndicator? _loadingIndicator;
+
+ public EventHandler? IndicatorCreated;
+
+ public LoadingIndicatorAdorner()
+ {
+ _customStyle = this;
+ }
+
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+ if (!_initialized) {
+ _customStyle.SetupUi();
+ _initialized = true;
+ }
+ }
+
+ void IControlCustomStyle.SetupUi()
+ {
+ _loadingIndicator = new LoadingIndicator();
+ IndicatorCreated?.Invoke(this, new LoadingIndicatorCreatedEventArgs(_loadingIndicator));
+ LogicalChildren.Add(_loadingIndicator);
+ VisualChildren.Add(_loadingIndicator);
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ var offsetX = (finalSize.Width - _loadingIndicator!.DesiredSize.Width) / 2;
+ var offsetY = (finalSize.Height - _loadingIndicator.DesiredSize.Height) / 2;
+ _loadingIndicator.Arrange(new Rect(new Point(offsetX, offsetY), _loadingIndicator.DesiredSize));
+ return finalSize;
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ base.MeasureOverride(availableSize);
+ return availableSize;
+ }
+}
+
+public class LoadingIndicatorCreatedEventArgs : EventArgs
+{
+ public LoadingIndicator LoadingIndicator { get; set; }
+
+ public LoadingIndicatorCreatedEventArgs(LoadingIndicator indicator)
+ {
+ LoadingIndicator = indicator;
+ }
+}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Loading/LoadingMask.cs b/src/AtomUI.Controls/Loading/LoadingMask.cs
index f4ad028..8fd17e1 100644
--- a/src/AtomUI.Controls/Loading/LoadingMask.cs
+++ b/src/AtomUI.Controls/Loading/LoadingMask.cs
@@ -1,6 +1,175 @@
-namespace AtomUI.Controls;
+using AtomUI.Utils;
+using Avalonia;
+using Avalonia.Animation.Easings;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.LogicalTree;
+using Avalonia.Media;
-public class LoadingMask
+namespace AtomUI.Controls;
+
+public class LoadingMask : AvaloniaObject, IDisposable
{
+ #region 公共属性定义
+
+ public static readonly StyledProperty SizeTypeProperty =
+ LoadingIndicator.SizeTypeProperty.AddOwner();
+
+ public static readonly StyledProperty LoadingMsgProperty =
+ LoadingIndicator.LoadingMsgProperty.AddOwner();
+ public static readonly StyledProperty IsShowLoadingMsgProperty =
+ LoadingIndicator.IsShowLoadingMsgProperty.AddOwner();
+
+ public static readonly StyledProperty CustomIndicatorIconProperty =
+ LoadingIndicator.CustomIndicatorIconProperty.AddOwner();
+
+ public static readonly StyledProperty MotionDurationProperty =
+ LoadingIndicator.MotionDurationProperty.AddOwner();
+
+ public static readonly StyledProperty MotionEasingCurveProperty =
+ LoadingIndicator.MotionEasingCurveProperty.AddOwner();
+
+ 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);
+ }
+
+ public bool IsLoading
+ {
+ get;
+ private set;
+ }
+
+ #endregion
+
+ private Control? _attachTarget;
+ private LoadingIndicatorAdorner? _loadingIndicatorAdorner;
+ private AdornerLayer? _adornerLayer;
+
+ public LoadingMask(Control? attachTarget = null)
+ {
+ if (attachTarget is not null) {
+ Attach(attachTarget);
+ }
+ }
+
+ public void Attach(Control attachTarget)
+ {
+ if (_attachTarget is not null) {
+ Detach();
+ }
+
+ attachTarget.DetachedFromLogicalTree += HandleTargetDetachedFromVisualTree;
+ _attachTarget = attachTarget;
+ }
+
+ public void Detach()
+ {
+ if (_attachTarget is null) {
+ return;
+ }
+ Hide();
+ _attachTarget.DetachedFromLogicalTree -= HandleTargetDetachedFromVisualTree;
+ _attachTarget = null;
+ }
+
+ public void Show()
+ {
+ if (_attachTarget is null || IsLoading) {
+ return;
+ }
+
+ if (_adornerLayer is null) {
+ _adornerLayer = AdornerLayer.GetAdornerLayer(_attachTarget);
+ if (_adornerLayer == null) {
+ throw new SystemException("No Adorner Layer found.");
+ }
+ }
+
+ _loadingIndicatorAdorner ??= new LoadingIndicatorAdorner();
+ _loadingIndicatorAdorner.IndicatorCreated += HandleLoadingIndicatorCreated;
+
+ // 尽力探测 cornerradius
+ _attachTarget.Effect = new BlurEffect()
+ {
+ Radius = 5
+ };
+ AdornerLayer.SetAdornedElement(_loadingIndicatorAdorner, _attachTarget);
+ AdornerLayer.SetIsClipEnabled(_loadingIndicatorAdorner, true);
+ _adornerLayer.Children.Add(_loadingIndicatorAdorner);
+
+ IsLoading = true;
+ }
+
+ // 在这里进行配置
+ private void HandleLoadingIndicatorCreated(object? sender, LoadingIndicatorCreatedEventArgs args)
+ {
+ var indicator = args.LoadingIndicator;
+ BindUtils.RelayBind(this, SizeTypeProperty, indicator, SizeTypeProperty);
+ BindUtils.RelayBind(this, LoadingMsgProperty, indicator, LoadingMsgProperty);
+ BindUtils.RelayBind(this, IsShowLoadingMsgProperty, indicator, IsShowLoadingMsgProperty);
+ BindUtils.RelayBind(this, CustomIndicatorIconProperty, indicator, CustomIndicatorIconProperty);
+ BindUtils.RelayBind(this, MotionDurationProperty, indicator, MotionDurationProperty);
+ BindUtils.RelayBind(this, MotionEasingCurveProperty, indicator, MotionEasingCurveProperty);
+ }
+
+ public void Hide()
+ {
+ if (!IsLoading || _attachTarget is null) {
+ return;
+ }
+ if (_adornerLayer is not null && _loadingIndicatorAdorner is not null) {
+ _adornerLayer.Children.Remove(_loadingIndicatorAdorner);
+ }
+
+ _attachTarget.Effect = null;
+ IsLoading = false;
+ }
+
+ public void Dispose()
+ {
+ if (_attachTarget is not null) {
+ Detach();
+ }
+
+ _adornerLayer = null;
+ _loadingIndicatorAdorner = null;
+ }
+
+ private void HandleTargetDetachedFromVisualTree(object? sender, LogicalTreeAttachmentEventArgs args)
+ {
+ Dispose();
+ }
}
\ No newline at end of file
diff --git a/src/AtomUI.Controls/Loading/LoadingMaskHost.cs b/src/AtomUI.Controls/Loading/LoadingMaskHost.cs
new file mode 100644
index 0000000..09eb629
--- /dev/null
+++ b/src/AtomUI.Controls/Loading/LoadingMaskHost.cs
@@ -0,0 +1,173 @@
+using AtomUI.Styling;
+using AtomUI.Utils;
+using Avalonia;
+using Avalonia.Animation.Easings;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.LogicalTree;
+using Avalonia.Metadata;
+
+namespace AtomUI.Controls;
+
+public class LoadingMaskHost : Control, IControlCustomStyle
+{
+ #region 公共属性定义
+
+ public static readonly StyledProperty SizeTypeProperty =
+ LoadingIndicator.SizeTypeProperty.AddOwner();
+
+ public static readonly StyledProperty LoadingMsgProperty =
+ LoadingIndicator.LoadingMsgProperty.AddOwner();
+
+ public static readonly StyledProperty IsShowLoadingMsgProperty =
+ LoadingIndicator.IsShowLoadingMsgProperty.AddOwner();
+
+ public static readonly StyledProperty CustomIndicatorIconProperty =
+ LoadingIndicator.CustomIndicatorIconProperty.AddOwner();
+
+ public static readonly StyledProperty MotionDurationProperty =
+ LoadingIndicator.MotionDurationProperty.AddOwner();
+
+ public static readonly StyledProperty MotionEasingCurveProperty =
+ LoadingIndicator.MotionEasingCurveProperty.AddOwner();
+
+ public static readonly StyledProperty IsLoadingProperty =
+ AvaloniaProperty.Register(nameof(IsLoading), false);
+
+ public static readonly StyledProperty MaskTargetProperty =
+ AvaloniaProperty.Register(nameof(MaskTarget));
+
+ 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);
+ }
+
+ public bool IsLoading
+ {
+ get => GetValue(IsLoadingProperty);
+ set => SetValue(IsLoadingProperty, value);
+ }
+
+ [Content]
+ public Control? MaskTarget
+ {
+ get => GetValue(MaskTargetProperty);
+ set => SetValue(MaskTargetProperty, value);
+ }
+
+ #endregion
+
+ private LoadingMask? _loadingMask;
+ private bool _initialized = false;
+ private IControlCustomStyle _customStyle;
+
+ public LoadingMaskHost()
+ {
+ _customStyle = this;
+ }
+
+ void IControlCustomStyle.SetupUi()
+ {
+ if (MaskTarget is not null) {
+ LogicalChildren.Add(MaskTarget);
+ VisualChildren.Add(MaskTarget);
+ }
+ }
+
+ public void ShowLoading()
+ {
+ if (_loadingMask is not null && _loadingMask.IsLoading) {
+ return;
+ }
+ if (_loadingMask is null && MaskTarget is not null) {
+ _loadingMask = new LoadingMask();
+ _loadingMask.Attach(this);
+ BindUtils.RelayBind(this, SizeTypeProperty, _loadingMask, SizeTypeProperty);
+ BindUtils.RelayBind(this, LoadingMsgProperty, _loadingMask, LoadingMsgProperty);
+ BindUtils.RelayBind(this, IsShowLoadingMsgProperty, _loadingMask, IsShowLoadingMsgProperty);
+ BindUtils.RelayBind(this, CustomIndicatorIconProperty, _loadingMask, CustomIndicatorIconProperty);
+ BindUtils.RelayBind(this, MotionDurationProperty, _loadingMask, MotionDurationProperty);
+ BindUtils.RelayBind(this, MotionEasingCurveProperty, _loadingMask, MotionEasingCurveProperty);
+ }
+ _loadingMask!.Show();
+ }
+
+ public void HideLoading()
+ {
+ if (_loadingMask is null ||
+ (_loadingMask is not null && !_loadingMask.IsLoading)) {
+ return;
+ }
+ _loadingMask?.Hide();
+ }
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ if (IsLoading && MaskTarget is not null) {
+ ShowLoading();
+ }
+ }
+
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+ if (!_initialized) {
+ _customStyle.SetupUi();
+ _initialized = true;
+ }
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnDetachedFromVisualTree(e);
+ HideLoading();
+ _loadingMask?.Dispose();
+ _loadingMask = null;
+ }
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ base.OnPropertyChanged(change);
+ if (_initialized && VisualRoot is not null) {
+ if (IsLoadingProperty == change.Property) {
+ if (IsLoading) {
+ ShowLoading();
+ } else {
+ HideLoading();
+ }
+ }
+ }
+ }
+}
\ No newline at end of file