loading masker 完成

This commit is contained in:
polarboy 2024-07-14 12:03:29 +08:00
parent 90e288a33c
commit bdc74533f2
7 changed files with 480 additions and 8 deletions

View File

@ -28,16 +28,65 @@
Title="Custom spinning indicator"
Description="Use custom loading indicator.">
<StackPanel Orientation="Horizontal">
<atom:LoadingIndicator SizeType="Small"
<atom:LoadingIndicator SizeType="Small"
VerticalAlignment="Center"
CustomIndicatorIcon="{atom:IconProvider Kind=LoadingOutlined,NormalFilledColor=#1677ff}"/>
<atom:LoadingIndicator SizeType="Middle"
<atom:LoadingIndicator SizeType="Middle"
VerticalAlignment="Center"
CustomIndicatorIcon="{atom:IconProvider Kind=LoadingOutlined,NormalFilledColor=#1677ff}"/>
<atom:LoadingIndicator SizeType="Large"
<atom:LoadingIndicator SizeType="Large"
VerticalAlignment="Center"
CustomIndicatorIcon="{atom:IconProvider Kind=LoadingOutlined,NormalFilledColor=#1677ff}"/>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Customized description"
Description="Customized description">
<StackPanel Orientation="Vertical" Spacing="10">
<StackPanel Orientation="Horizontal" Spacing="10">
<atom:LoadingMaskHost IsLoading="True" SizeType="Small"
IsShowLoadingMsg="True"
LoadingMsg="Loading...">
<Border Width="100" Height="100" Background="rgb(251, 251, 251)" />
</atom:LoadingMaskHost>
<atom:LoadingMaskHost IsLoading="True" SizeType="Middle"
IsShowLoadingMsg="True"
LoadingMsg="Loading...">
<Border Width="100" Height="100" Background="rgb(251, 251, 251)"/>
</atom:LoadingMaskHost>
<atom:LoadingMaskHost IsLoading="True" SizeType="Large"
IsShowLoadingMsg="True"
LoadingMsg="Loading...">
<Border Width="100" Height="100" Background="rgb(251, 251, 251)"/>
</atom:LoadingMaskHost>
</StackPanel>
<atom:LoadingMaskHost IsLoading="True"
IsShowLoadingMsg="True"
LoadingMsg="Loading...">
<atom:Alert Message="Alert message title"
Description="Further details about the context of this alert."
Type="Info"/>
</atom:LoadingMaskHost>
</StackPanel>
</showcase:ShowCaseItem>
<showcase:ShowCaseItem
Title="Customized description"
Description="Customized description">
<StackPanel Orientation="Vertical" Spacing="10">
<atom:LoadingMaskHost IsLoading="{Binding IsLoadingSwitchChecked}"
IsShowLoadingMsg="True"
LoadingMsg="Loading...">
<atom:Alert Message="Alert message title"
Description="Further details about the context of this alert."
Type="Info"/>
</atom:LoadingMaskHost>
<StackPanel Orientation="Horizontal" Spacing="10">
<TextBlock>Loading state</TextBlock>
<atom:ToggleSwitch IsChecked="{Binding IsLoadingSwitchChecked}"/>
</StackPanel>
</StackPanel>
</showcase:ShowCaseItem>
</showcase:ShowCasePanel>
</UserControl>

View File

@ -1,11 +1,22 @@
using Avalonia;
using Avalonia.Controls;
namespace AtomUI.Demo.Desktop.ShowCase;
public partial class LoadingIndicatorShowCase : UserControl
{
public static readonly StyledProperty<bool> IsLoadingSwitchCheckedProperty =
AvaloniaProperty.Register<ProgressBarShowCase, bool>(nameof(IsLoadingSwitchChecked), false);
public bool IsLoadingSwitchChecked
{
get => GetValue(IsLoadingSwitchCheckedProperty);
set => SetValue(IsLoadingSwitchCheckedProperty, value);
}
public LoadingIndicatorShowCase()
{
DataContext = this;
InitializeComponent();
}

View File

@ -0,0 +1,8 @@
using Avalonia;
namespace AtomUI.Controls;
public interface ICornerRadiusInfoProvider
{
public CornerRadius CornerRadius { get; }
}

View File

@ -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));

View File

@ -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<LoadingIndicatorCreatedEventArgs>? 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;
}
}

View File

@ -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<SizeType> SizeTypeProperty =
LoadingIndicator.SizeTypeProperty.AddOwner<LoadingMask>();
public static readonly StyledProperty<string?> LoadingMsgProperty =
LoadingIndicator.LoadingMsgProperty.AddOwner<LoadingMask>();
public static readonly StyledProperty<bool> IsShowLoadingMsgProperty =
LoadingIndicator.IsShowLoadingMsgProperty.AddOwner<LoadingMask>();
public static readonly StyledProperty<PathIcon?> CustomIndicatorIconProperty =
LoadingIndicator.CustomIndicatorIconProperty.AddOwner<LoadingMask>();
public static readonly StyledProperty<TimeSpan?> MotionDurationProperty =
LoadingIndicator.MotionDurationProperty.AddOwner<LoadingMask>();
public static readonly StyledProperty<Easing?> MotionEasingCurveProperty =
LoadingIndicator.MotionEasingCurveProperty.AddOwner<LoadingMask>();
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();
}
}

View File

@ -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<SizeType> SizeTypeProperty =
LoadingIndicator.SizeTypeProperty.AddOwner<LoadingMaskHost>();
public static readonly StyledProperty<string?> LoadingMsgProperty =
LoadingIndicator.LoadingMsgProperty.AddOwner<LoadingMaskHost>();
public static readonly StyledProperty<bool> IsShowLoadingMsgProperty =
LoadingIndicator.IsShowLoadingMsgProperty.AddOwner<LoadingMaskHost>();
public static readonly StyledProperty<PathIcon?> CustomIndicatorIconProperty =
LoadingIndicator.CustomIndicatorIconProperty.AddOwner<LoadingMaskHost>();
public static readonly StyledProperty<TimeSpan?> MotionDurationProperty =
LoadingIndicator.MotionDurationProperty.AddOwner<LoadingMaskHost>();
public static readonly StyledProperty<Easing?> MotionEasingCurveProperty =
LoadingIndicator.MotionEasingCurveProperty.AddOwner<LoadingMaskHost>();
public static readonly StyledProperty<bool> IsLoadingProperty =
AvaloniaProperty.Register<LoadingMaskHost, bool>(nameof(IsLoading), false);
public static readonly StyledProperty<Control?> MaskTargetProperty =
AvaloniaProperty.Register<CountBadge, Control?>(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();
}
}
}
}
}