Add SkeletonWidth

This commit is contained in:
chinboy
2025-08-13 13:43:02 +08:00
parent 6ca55ae099
commit bc21fbc9d5
15 changed files with 317 additions and 0 deletions
@@ -45,6 +45,7 @@ namespace AtomUI.Theme
tokenTypes.Add(typeof(AtomUI.Controls.ScrollBarToken));
tokenTypes.Add(typeof(AtomUI.Controls.SegmentedToken));
tokenTypes.Add(typeof(AtomUI.Controls.SeparatorToken));
tokenTypes.Add(typeof(AtomUI.Controls.SkeletonToken));
tokenTypes.Add(typeof(AtomUI.Controls.SliderToken));
tokenTypes.Add(typeof(AtomUI.Controls.TabControlToken));
tokenTypes.Add(typeof(AtomUI.Controls.TagToken));
@@ -659,6 +659,18 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey VerticalMarginInline = new TokenResourceKey("Separator.VerticalMarginInline");
}
public static class SkeletonTokenKey
{
public static readonly TokenResourceKey BlockRadius = new TokenResourceKey("Skeleton.BlockRadius");
public static readonly TokenResourceKey GradientFromColor = new TokenResourceKey("Skeleton.GradientFromColor");
public static readonly TokenResourceKey GradientToColor = new TokenResourceKey("Skeleton.GradientToColor");
public static readonly TokenResourceKey LoadingBackground = new TokenResourceKey("Skeleton.LoadingBackground");
public static readonly TokenResourceKey LoadingMotionDuration = new TokenResourceKey("Skeleton.LoadingMotionDuration");
public static readonly TokenResourceKey ParagraphLineHeight = new TokenResourceKey("Skeleton.ParagraphLineHeight");
public static readonly TokenResourceKey ParagraphMarginTop = new TokenResourceKey("Skeleton.ParagraphMarginTop");
public static readonly TokenResourceKey TitleHeight = new TokenResourceKey("Skeleton.TitleHeight");
}
public static class SliderTokenKey
{
public static readonly TokenResourceKey MarginPartWithMark = new TokenResourceKey("Slider.MarginPartWithMark");
@@ -0,0 +1,29 @@
using Avalonia;
using Avalonia.Controls.Primitives;
namespace AtomUI.Controls;
public abstract class AbstractSkeleton : TemplatedControl,
ISizeTypeAware
{
#region 公共属性定义
public static readonly StyledProperty<bool> IsActiveProperty =
AvaloniaProperty.Register<StyledElement, bool>(nameof(IsActive));
public static readonly StyledProperty<SizeType> SizeTypeProperty =
SizeTypeAwareControlProperty.SizeTypeProperty.AddOwner<AbstractSkeleton>();
public bool IsActive
{
get => GetValue(IsActiveProperty);
set => SetValue(IsActiveProperty, value);
}
public SizeType SizeType
{
get => GetValue(SizeTypeProperty);
set => SetValue(SizeTypeProperty, value);
}
#endregion
}
+8
View File
@@ -0,0 +1,8 @@
using Avalonia.Controls;
namespace AtomUI.Controls;
public class Skeleton : ContentControl
{
}
@@ -0,0 +1,6 @@
namespace AtomUI.Controls;
public class SkeletonAvatar : AbstractSkeleton
{
}
@@ -0,0 +1,6 @@
namespace AtomUI.Controls;
public class SkeletonButton : AbstractSkeleton
{
}
@@ -0,0 +1,6 @@
namespace AtomUI.Controls;
public class SkeletonInput : AbstractSkeleton
{
}
@@ -0,0 +1,6 @@
namespace AtomUI.Controls;
internal class SkeletonLine : AbstractSkeleton
{
}
@@ -0,0 +1,6 @@
namespace AtomUI.Controls;
public class SkeletonParagraph : AbstractSkeleton
{
}
@@ -0,0 +1,6 @@
namespace AtomUI.Controls;
public class SkeletonTitle
{
}
@@ -0,0 +1,86 @@
using AtomUI.Theme.TokenSystem;
using Avalonia;
using Avalonia.Media;
namespace AtomUI.Controls;
[ControlDesignToken]
internal class SkeletonToken : AbstractControlDesignToken
{
public const string ID = "Skeleton";
/// <summary>
/// 渐变色起点颜色
/// Start color of gradient
/// </summary>
public Color GradientFromColor { get; set; }
/// <summary>
/// 渐变色终点颜色
/// End color of gradient
/// </summary>
public Color GradientToColor { get; set; }
/// <summary>
/// 标题骨架屏高度
/// Height of title skeleton
/// </summary>
public double TitleHeight { get; set; }
/// <summary>
/// 骨架屏圆角
/// Border radius of skeleton
/// </summary>
public CornerRadius BlockRadius { get; set; }
/// <summary>
/// 段落骨架屏上间距
/// Margin top of paragraph skeleton
/// </summary>
public Thickness ParagraphMarginTop { get; set; }
/// <summary>
/// 段落骨架屏单行高度
/// Line height of paragraph skeleton
/// </summary>
public double ParagraphLineHeight { get; set; }
/// <summary>
/// 默认加载的时长
/// </summary>
public TimeSpan LoadingMotionDuration { get; set; }
/// <summary>
/// 默认加载的动画背景笔刷
/// </summary>
public IBrush? LoadingBackground { get; set; }
public SkeletonToken()
: base(ID)
{
}
protected internal override void CalculateFromAlias()
{
base.CalculateFromAlias();
GradientFromColor = SharedToken.ColorFillContent;
GradientToColor = SharedToken.ColorFill;
TitleHeight = SharedToken.ControlHeight / 2;
BlockRadius = SharedToken.BorderRadiusSM;
ParagraphMarginTop = new Thickness(0, SharedToken.UniformlyMarginLG + SharedToken.UniformlyMarginXXS, 0, 0);
ParagraphLineHeight = SharedToken.ControlHeight / 2;
LoadingMotionDuration = TimeSpan.FromSeconds(1.4);
LoadingBackground = new LinearGradientBrush()
{
StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
GradientStops =
{
new GradientStop { Offset = 0.25, Color = GradientFromColor },
new GradientStop { Offset = 0.37, Color = GradientToColor },
new GradientStop { Offset = 0.63, Color = GradientFromColor }
}
};
}
}
@@ -0,0 +1,126 @@
using System.Globalization;
using System.Text.RegularExpressions;
using AtomUI.Utils;
namespace AtomUI.Controls;
public enum SkeletonUnitType
{
Percentage = 0,
Pixel = 1,
}
public struct SkeletonWidth : IEquatable<SkeletonWidth>
{
private readonly SkeletonUnitType _type;
private readonly double _value;
public SkeletonWidth(double value)
: this(value, SkeletonUnitType.Pixel)
{
}
public SkeletonWidth(double value, SkeletonUnitType type)
{
if (value < 0 || double.IsNaN(value) || double.IsInfinity(value))
{
throw new ArgumentException("Invalid value", nameof(value));
}
if (type < SkeletonUnitType.Percentage || type > SkeletonUnitType.Pixel)
{
throw new ArgumentException("Invalid value", nameof(type));
}
_type = type;
_value = value;
}
public SkeletonUnitType SkeletonUnitType => _type;
public bool IsAbsolute => _type == SkeletonUnitType.Pixel;
public bool IsPercentage => _type == SkeletonUnitType.Percentage;
public bool IsPixel => _type == SkeletonUnitType.Pixel;
public double Value => _value;
public static bool operator ==(SkeletonWidth a, SkeletonWidth b)
{
return (MathUtils.AreClose(a._value, b._value) && a._type == b._type);
}
public static bool operator !=(SkeletonWidth gl1, SkeletonWidth gl2)
{
return !(gl1 == gl2);
}
public override bool Equals(object? o)
{
if (o == null)
{
return false;
}
if (!(o is SkeletonWidth))
{
return false;
}
return this == (SkeletonWidth)o;
}
public bool Equals(SkeletonWidth gridLength)
{
return this == gridLength;
}
public override int GetHashCode()
{
return _value.GetHashCode() ^ _type.GetHashCode();
}
public override string ToString()
{
var vstr = _value.ToString(CultureInfo.InvariantCulture);
if (IsPercentage)
{
return $"{vstr}%";
}
return $"{vstr}px";
}
public static SkeletonWidth Parse(string s)
{
s = s.ToUpperInvariant();
var match = Regex.Match(s.Trim(), @"^([+-]?\d*\.?\d+)\s*([a-zA-Z%]+)?$", RegexOptions.IgnoreCase);
if (!match.Success)
{
throw new FormatException($"Invalid width format: '{s}'");
}
SkeletonUnitType unit = SkeletonUnitType.Pixel;
if (match.Groups[2].Value == "%")
{
unit = SkeletonUnitType.Percentage;
}
var value = double.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
return new SkeletonWidth(value, unit);
}
public static IEnumerable<SkeletonWidth> ParseWidths(string s)
{
var result = new List<SkeletonWidth>();
using (var tokenizer = new SpanStringTokenizer(s, CultureInfo.InvariantCulture))
{
while (tokenizer.TryReadString(out var item))
{
result.Add(Parse(item));
}
}
return result;
}
}
@@ -0,0 +1,6 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:atom="https://atomui.net"
x:ClassModifier="internal">
</ResourceDictionary>
@@ -0,0 +1,6 @@
namespace AtomUI.Controls.Themes;
internal static class SkeletonThemeConstants
{
}
@@ -0,0 +1,7 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:ClassModifier="internal">
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="SkeletonTheme.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>