mirror of
https://gitee.com/chinware/atomui.git
synced 2024-12-02 03:47:52 +08:00
Refactor badge Motion
This commit is contained in:
parent
d604d2aec3
commit
2ef0690a96
@ -63,6 +63,9 @@ internal class BadgeToken : AbstractControlDesignToken
|
|||||||
public int BadgeRibbonCornerDarkenAmount { get; set; }
|
public int BadgeRibbonCornerDarkenAmount { get; set; }
|
||||||
public Thickness BadgeRibbonTextPadding { get; set; }
|
public Thickness BadgeRibbonTextPadding { get; set; }
|
||||||
public Thickness DotBadgeLabelMargin { get; set; }
|
public Thickness DotBadgeLabelMargin { get; set; }
|
||||||
|
public Thickness CountBadgeTextPadding { get; set; }
|
||||||
|
public CornerRadius CountBadgeCornerRadius { get; set; }
|
||||||
|
public CornerRadius CountBadgeCornerRadiusSM { get; set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -92,5 +95,8 @@ internal class BadgeToken : AbstractControlDesignToken
|
|||||||
BadgeRibbonCornerDarkenAmount = 15;
|
BadgeRibbonCornerDarkenAmount = 15;
|
||||||
BadgeRibbonTextPadding = new Thickness(_globalToken.PaddingXS, 0);
|
BadgeRibbonTextPadding = new Thickness(_globalToken.PaddingXS, 0);
|
||||||
DotBadgeLabelMargin = new Thickness(_globalToken.MarginXS, 0, 0, 0);
|
DotBadgeLabelMargin = new Thickness(_globalToken.MarginXS, 0, 0, 0);
|
||||||
|
CountBadgeTextPadding = new Thickness(_globalToken.PaddingXXS, 0);
|
||||||
|
CountBadgeCornerRadius = new CornerRadius(IndicatorHeight);
|
||||||
|
CountBadgeCornerRadiusSM = new CornerRadius(IndicatorHeightSM);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,4 @@
|
|||||||
using AtomUI.Controls.Badge;
|
using AtomUI.Data;
|
||||||
using AtomUI.Controls.MotionScene;
|
|
||||||
using AtomUI.Data;
|
|
||||||
using AtomUI.MotionScene;
|
|
||||||
using AtomUI.Theme.Palette;
|
using AtomUI.Theme.Palette;
|
||||||
using AtomUI.Theme.Styling;
|
using AtomUI.Theme.Styling;
|
||||||
using AtomUI.Utils;
|
using AtomUI.Utils;
|
||||||
@ -48,7 +45,7 @@ public class CountBadge : Control
|
|||||||
AvaloniaProperty.Register<CountBadge, CountBadgeSize>(nameof(Size));
|
AvaloniaProperty.Register<CountBadge, CountBadgeSize>(nameof(Size));
|
||||||
|
|
||||||
public static readonly StyledProperty<bool> BadgeIsVisibleProperty =
|
public static readonly StyledProperty<bool> BadgeIsVisibleProperty =
|
||||||
AvaloniaProperty.Register<CountBadge, bool>(nameof(BadgeIsVisible));
|
AvaloniaProperty.Register<CountBadge, bool>(nameof(BadgeIsVisible), true);
|
||||||
|
|
||||||
public string? BadgeColor
|
public string? BadgeColor
|
||||||
{
|
{
|
||||||
@ -117,7 +114,7 @@ public class CountBadge : Control
|
|||||||
|
|
||||||
private CountBadgeAdorner? _badgeAdorner;
|
private CountBadgeAdorner? _badgeAdorner;
|
||||||
private AdornerLayer? _adornerLayer;
|
private AdornerLayer? _adornerLayer;
|
||||||
private bool _animating;
|
private bool _isInitialized;
|
||||||
|
|
||||||
static CountBadge()
|
static CountBadge()
|
||||||
{
|
{
|
||||||
@ -131,10 +128,17 @@ public class CountBadge : Control
|
|||||||
public sealed override void ApplyTemplate()
|
public sealed override void ApplyTemplate()
|
||||||
{
|
{
|
||||||
base.ApplyTemplate();
|
base.ApplyTemplate();
|
||||||
|
if (!_isInitialized)
|
||||||
|
{
|
||||||
if (DecoratedTarget is null)
|
if (DecoratedTarget is null)
|
||||||
{
|
{
|
||||||
CreateBadgeAdorner();
|
CreateBadgeAdorner();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SetupShowZero();
|
||||||
|
|
||||||
|
_isInitialized = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CountBadgeAdorner CreateBadgeAdorner()
|
private CountBadgeAdorner CreateBadgeAdorner()
|
||||||
@ -154,123 +158,47 @@ public class CountBadge : Control
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void PrepareAdorner()
|
private void PrepareAdorner()
|
||||||
{
|
|
||||||
if (_adornerLayer is null && DecoratedTarget is not null)
|
|
||||||
{
|
{
|
||||||
var badgeAdorner = CreateBadgeAdorner();
|
var badgeAdorner = CreateBadgeAdorner();
|
||||||
|
if (DecoratedTarget is not null)
|
||||||
|
{
|
||||||
_adornerLayer = AdornerLayer.GetAdornerLayer(this);
|
_adornerLayer = AdornerLayer.GetAdornerLayer(this);
|
||||||
// 这里需要抛出异常吗?
|
// 这里需要抛出异常吗?
|
||||||
if (_adornerLayer == null)
|
if (_adornerLayer == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
badgeAdorner.ApplyToTarget(_adornerLayer, this);
|
||||||
AdornerLayer.SetAdornedElement(badgeAdorner, this);
|
|
||||||
AdornerLayer.SetIsClipEnabled(badgeAdorner, false);
|
|
||||||
_adornerLayer.Children.Add(badgeAdorner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PrepareAdornerWithMotion()
|
|
||||||
{
|
|
||||||
PrepareAdorner();
|
|
||||||
|
|
||||||
if (VisualRoot is null || _animating)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_animating = true;
|
|
||||||
var director = Director.Instance;
|
|
||||||
|
|
||||||
AbstractMotion motion;
|
|
||||||
var adorner = _badgeAdorner!;
|
|
||||||
if (DecoratedTarget is not null)
|
|
||||||
{
|
|
||||||
var countBadgeZoomBadgeIn = new CountBadgeZoomBadgeIn();
|
|
||||||
countBadgeZoomBadgeIn.ConfigureOpacity(MotionDuration);
|
|
||||||
countBadgeZoomBadgeIn.ConfigureRenderTransform(MotionDuration);
|
|
||||||
motion = countBadgeZoomBadgeIn;
|
|
||||||
adorner.AnimationRenderTransformOrigin = motion.MotionRenderTransformOrigin;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var countBadgeNoWrapperZoomBadgeIn = new CountBadgeNoWrapperZoomBadgeIn();
|
badgeAdorner.ApplyToTarget(null, this);
|
||||||
countBadgeNoWrapperZoomBadgeIn.ConfigureOpacity(MotionDuration);
|
}
|
||||||
countBadgeNoWrapperZoomBadgeIn.ConfigureRenderTransform(MotionDuration);
|
|
||||||
motion = countBadgeNoWrapperZoomBadgeIn;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var motionActor = new MotionActor(adorner, motion);
|
private void HideAdorner(bool enableMotion)
|
||||||
motionActor.DispatchInSceneLayer = false;
|
|
||||||
motionActor.Completed += (sender, args) =>
|
|
||||||
{
|
|
||||||
adorner.AnimationRenderTransformOrigin = null;
|
|
||||||
_animating = false;
|
|
||||||
};
|
|
||||||
director?.Schedule(motionActor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HideAdorner()
|
|
||||||
{
|
{
|
||||||
// 这里需要抛出异常吗?
|
// 这里需要抛出异常吗?
|
||||||
if (_adornerLayer is null || _badgeAdorner is null)
|
if (_badgeAdorner is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_badgeAdorner.DetachFromTarget(_adornerLayer, enableMotion);
|
||||||
_adornerLayer.Children.Remove(_badgeAdorner);
|
|
||||||
_adornerLayer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HideAdornerWithMotion()
|
|
||||||
{
|
|
||||||
if (VisualRoot is null || _animating)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_animating = true;
|
|
||||||
var director = Director.Instance;
|
|
||||||
AbstractMotion motion;
|
|
||||||
var adorner = _badgeAdorner!;
|
|
||||||
if (DecoratedTarget is not null)
|
|
||||||
{
|
|
||||||
var countBadgeZoomBadgeOut = new CountBadgeZoomBadgeOut();
|
|
||||||
countBadgeZoomBadgeOut.ConfigureOpacity(MotionDuration);
|
|
||||||
countBadgeZoomBadgeOut.ConfigureRenderTransform(MotionDuration);
|
|
||||||
motion = countBadgeZoomBadgeOut;
|
|
||||||
adorner.AnimationRenderTransformOrigin = motion.MotionRenderTransformOrigin;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var countBadgeNoWrapperZoomBadgeOut = new CountBadgeNoWrapperZoomBadgeOut();
|
|
||||||
countBadgeNoWrapperZoomBadgeOut.ConfigureOpacity(MotionDuration);
|
|
||||||
countBadgeNoWrapperZoomBadgeOut.ConfigureRenderTransform(MotionDuration);
|
|
||||||
motion = countBadgeNoWrapperZoomBadgeOut;
|
|
||||||
}
|
|
||||||
|
|
||||||
var motionActor = new MotionActor(adorner, motion);
|
|
||||||
motionActor.DispatchInSceneLayer = false;
|
|
||||||
motionActor.Completed += (sender, args) =>
|
|
||||||
{
|
|
||||||
HideAdorner();
|
|
||||||
adorner.AnimationRenderTransformOrigin = null;
|
|
||||||
_animating = false;
|
|
||||||
};
|
|
||||||
director?.Schedule(motionActor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnAttachedToVisualTree(e);
|
base.OnAttachedToVisualTree(e);
|
||||||
|
if (BadgeIsVisible)
|
||||||
|
{
|
||||||
PrepareAdorner();
|
PrepareAdorner();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnDetachedFromVisualTree(e);
|
base.OnDetachedFromVisualTree(e);
|
||||||
HideAdorner();
|
HideAdorner(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupTokenBindings()
|
private void SetupTokenBindings()
|
||||||
@ -308,38 +236,16 @@ public class CountBadge : Control
|
|||||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnPropertyChanged(e);
|
base.OnPropertyChanged(e);
|
||||||
if (e.Property == IsVisibleProperty)
|
if (e.Property == BadgeIsVisibleProperty)
|
||||||
{
|
{
|
||||||
var badgeIsVisible = e.GetNewValue<bool>();
|
if (BadgeIsVisible)
|
||||||
if (badgeIsVisible)
|
|
||||||
{
|
{
|
||||||
if (_adornerLayer is not null)
|
SetupShowZero();
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareAdorner();
|
PrepareAdorner();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HideAdorner();
|
HideAdorner(true);
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e.Property == BadgeIsVisibleProperty)
|
|
||||||
{
|
|
||||||
var badgeIsVisible = e.GetNewValue<bool>();
|
|
||||||
if (badgeIsVisible)
|
|
||||||
{
|
|
||||||
if (_adornerLayer is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareAdornerWithMotion();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HideAdornerWithMotion();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -356,19 +262,24 @@ public class CountBadge : Control
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.Property == CountProperty)
|
if (e.Property == CountProperty ||
|
||||||
|
e.Property == ShowZeroProperty)
|
||||||
{
|
{
|
||||||
var newCount = e.GetNewValue<int>();
|
SetupShowZero();
|
||||||
if (newCount == 0 && !ShowZero)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetupShowZero()
|
||||||
|
{
|
||||||
|
if (Count == 0 && !ShowZero)
|
||||||
{
|
{
|
||||||
BadgeIsVisible = false;
|
BadgeIsVisible = false;
|
||||||
}
|
}
|
||||||
else if (newCount > 0)
|
else if (Count > 0)
|
||||||
{
|
{
|
||||||
BadgeIsVisible = true;
|
BadgeIsVisible = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void SetupBadgeColor(string colorStr)
|
private void SetupBadgeColor(string colorStr)
|
||||||
{
|
{
|
||||||
|
@ -1,18 +1,16 @@
|
|||||||
using System.Globalization;
|
using AtomUI.Controls.Badge;
|
||||||
using AtomUI.Media;
|
using AtomUI.MotionScene;
|
||||||
using AtomUI.Theme.Styling;
|
using AtomUI.Theme.Styling;
|
||||||
|
using AtomUI.Utils;
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Animation;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Controls.Documents;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Layout;
|
|
||||||
using Avalonia.LogicalTree;
|
|
||||||
using Avalonia.Media;
|
using Avalonia.Media;
|
||||||
using Avalonia.Media.TextFormatting;
|
|
||||||
using Avalonia.Styling;
|
|
||||||
|
|
||||||
namespace AtomUI.Controls;
|
namespace AtomUI.Controls;
|
||||||
|
|
||||||
internal class CountBadgeAdorner : Control
|
internal class CountBadgeAdorner : TemplatedControl
|
||||||
{
|
{
|
||||||
#region 公共属性定义
|
#region 公共属性定义
|
||||||
|
|
||||||
@ -20,17 +18,28 @@ internal class CountBadgeAdorner : Control
|
|||||||
AvaloniaProperty.Register<CountBadgeAdorner, IBrush?>(
|
AvaloniaProperty.Register<CountBadgeAdorner, IBrush?>(
|
||||||
nameof(BadgeColor));
|
nameof(BadgeColor));
|
||||||
|
|
||||||
public static readonly DirectProperty<CountBadgeAdorner, int> OverflowCountProperty =
|
internal static readonly StyledProperty<Point> OffsetProperty =
|
||||||
AvaloniaProperty.RegisterDirect<CountBadgeAdorner, int>(
|
AvaloniaProperty.Register<CountBadgeAdorner, Point>(
|
||||||
nameof(OverflowCount),
|
nameof(Offset));
|
||||||
o => o.OverflowCount,
|
|
||||||
(o, v) => o.OverflowCount = v);
|
|
||||||
|
|
||||||
public static readonly DirectProperty<CountBadgeAdorner, CountBadgeSize> SizeProperty =
|
public static readonly StyledProperty<int> OverflowCountProperty =
|
||||||
AvaloniaProperty.RegisterDirect<CountBadgeAdorner, CountBadgeSize>(
|
AvaloniaProperty.Register<CountBadgeAdorner, int>(nameof(OverflowCount));
|
||||||
nameof(Size),
|
|
||||||
o => o.Size,
|
public static readonly StyledProperty<CountBadgeSize> SizeProperty =
|
||||||
(o, v) => o.Size = v);
|
AvaloniaProperty.Register<CountBadgeAdorner, CountBadgeSize>(
|
||||||
|
nameof(Size));
|
||||||
|
|
||||||
|
internal static readonly StyledProperty<int> CountProperty =
|
||||||
|
AvaloniaProperty.Register<CountBadgeAdorner, int>(
|
||||||
|
nameof(Count));
|
||||||
|
|
||||||
|
internal static readonly StyledProperty<IBrush?> BadgeShadowColorProperty =
|
||||||
|
AvaloniaProperty.Register<CountBadgeAdorner, IBrush?>(
|
||||||
|
nameof(BadgeShadowColor));
|
||||||
|
|
||||||
|
internal static readonly StyledProperty<double> BadgeShadowSizeProperty =
|
||||||
|
AvaloniaProperty.Register<CountBadgeAdorner, double>(
|
||||||
|
nameof(BadgeShadowSize));
|
||||||
|
|
||||||
public IBrush? BadgeColor
|
public IBrush? BadgeColor
|
||||||
{
|
{
|
||||||
@ -44,115 +53,63 @@ internal class CountBadgeAdorner : Control
|
|||||||
set => SetValue(OffsetProperty, value);
|
set => SetValue(OffsetProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int _overflowCount;
|
|
||||||
|
|
||||||
public int OverflowCount
|
public int OverflowCount
|
||||||
{
|
{
|
||||||
get => _overflowCount;
|
get => GetValue(OverflowCountProperty);
|
||||||
set => SetAndRaise(OverflowCountProperty, ref _overflowCount, value);
|
set => SetValue(OverflowCountProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CountBadgeSize Size
|
||||||
|
{
|
||||||
|
get => GetValue(SizeProperty);
|
||||||
|
set => SetValue(SizeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count
|
||||||
|
{
|
||||||
|
get => GetValue(CountProperty);
|
||||||
|
set => SetValue(CountProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBrush? BadgeShadowColor
|
||||||
|
{
|
||||||
|
get => GetValue(BadgeShadowColorProperty);
|
||||||
|
set => SetValue(BadgeShadowColorProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double BadgeShadowSize
|
||||||
|
{
|
||||||
|
get => GetValue(BadgeShadowSizeProperty);
|
||||||
|
set => SetValue(BadgeShadowSizeProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region 内部属性定义
|
#region 内部属性定义
|
||||||
|
|
||||||
internal static readonly StyledProperty<IBrush?> BadgeTextColorProperty =
|
internal static readonly DirectProperty<CountBadgeAdorner, bool> IsAdornerModeProperty =
|
||||||
AvaloniaProperty.Register<CountBadgeAdorner, IBrush?>(
|
AvaloniaProperty.RegisterDirect<CountBadgeAdorner, bool>(
|
||||||
nameof(BadgeTextColor));
|
|
||||||
|
|
||||||
internal static readonly StyledProperty<double> TextFontSizeProperty =
|
|
||||||
AvaloniaProperty.Register<CountBadgeAdorner, double>(
|
|
||||||
nameof(TextFontSize));
|
|
||||||
|
|
||||||
internal static readonly StyledProperty<double> IndicatorHeightProperty =
|
|
||||||
AvaloniaProperty.Register<CountBadgeAdorner, double>(
|
|
||||||
nameof(IndicatorHeight));
|
|
||||||
|
|
||||||
internal static readonly StyledProperty<int> CountProperty =
|
|
||||||
AvaloniaProperty.Register<CountBadgeAdorner, int>(
|
|
||||||
nameof(Count));
|
|
||||||
|
|
||||||
internal static readonly StyledProperty<FontFamily> FontFamilyProperty =
|
|
||||||
TextElement.FontFamilyProperty.AddOwner<CountBadgeAdorner>();
|
|
||||||
|
|
||||||
internal static readonly StyledProperty<FontWeight> TextFontWeightProperty =
|
|
||||||
TextElement.FontWeightProperty.AddOwner<CountBadgeAdorner>();
|
|
||||||
|
|
||||||
internal static readonly StyledProperty<IBrush?> BadgeShadowColorProperty =
|
|
||||||
AvaloniaProperty.Register<CountBadgeAdorner, IBrush?>(
|
|
||||||
nameof(BadgeShadowColor));
|
|
||||||
|
|
||||||
internal static readonly StyledProperty<double> BadgeShadowSizeProperty =
|
|
||||||
AvaloniaProperty.Register<CountBadgeAdorner, double>(
|
|
||||||
nameof(BadgeShadowSize));
|
|
||||||
|
|
||||||
internal static readonly StyledProperty<double> PaddingInlineProperty =
|
|
||||||
AvaloniaProperty.Register<CountBadgeAdorner, double>(
|
|
||||||
nameof(PaddingInline));
|
|
||||||
|
|
||||||
internal static readonly DirectProperty<DotBadgeAdorner, bool> IsAdornerModeProperty =
|
|
||||||
AvaloniaProperty.RegisterDirect<DotBadgeAdorner, bool>(
|
|
||||||
nameof(IsAdornerMode),
|
nameof(IsAdornerMode),
|
||||||
o => o.IsAdornerMode,
|
o => o.IsAdornerMode,
|
||||||
(o, v) => o.IsAdornerMode = v);
|
(o, v) => o.IsAdornerMode = v);
|
||||||
|
|
||||||
internal static readonly StyledProperty<Point> OffsetProperty =
|
internal static readonly DirectProperty<CountBadgeAdorner, BoxShadows> BoxShadowProperty =
|
||||||
AvaloniaProperty.Register<CountBadgeAdorner, Point>(
|
AvaloniaProperty.RegisterDirect<CountBadgeAdorner, BoxShadows>(
|
||||||
nameof(Offset));
|
nameof(BoxShadow),
|
||||||
|
o => o.BoxShadow,
|
||||||
|
(o, v) => o.BoxShadow = v);
|
||||||
|
|
||||||
internal IBrush? BadgeTextColor
|
internal static readonly DirectProperty<CountBadgeAdorner, string?> CountTextProperty =
|
||||||
{
|
AvaloniaProperty.RegisterDirect<CountBadgeAdorner, string?>(
|
||||||
get => GetValue(BadgeTextColorProperty);
|
nameof(CountText),
|
||||||
set => SetValue(BadgeTextColorProperty, value);
|
o => o.CountText,
|
||||||
}
|
(o, v) => o.CountText = v);
|
||||||
|
|
||||||
internal double TextFontSize
|
internal static readonly DirectProperty<CountBadgeAdorner, TimeSpan> MotionDurationProperty =
|
||||||
{
|
AvaloniaProperty.RegisterDirect<CountBadgeAdorner, TimeSpan>(
|
||||||
get => GetValue(TextFontSizeProperty);
|
nameof(MotionDuration),
|
||||||
set => SetValue(TextFontSizeProperty, value);
|
o => o.MotionDuration,
|
||||||
}
|
(o, v) => o.MotionDuration = v);
|
||||||
|
|
||||||
internal double IndicatorHeight
|
|
||||||
{
|
|
||||||
get => GetValue(IndicatorHeightProperty);
|
|
||||||
set => SetValue(IndicatorHeightProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal int Count
|
|
||||||
{
|
|
||||||
get => GetValue(CountProperty);
|
|
||||||
set => SetValue(CountProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal FontFamily FontFamily
|
|
||||||
{
|
|
||||||
get => GetValue(FontFamilyProperty);
|
|
||||||
set => SetValue(FontFamilyProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal FontWeight TextFontWeight
|
|
||||||
{
|
|
||||||
get => GetValue(TextFontWeightProperty);
|
|
||||||
set => SetValue(TextFontWeightProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal IBrush? BadgeShadowColor
|
|
||||||
{
|
|
||||||
get => GetValue(BadgeShadowColorProperty);
|
|
||||||
set => SetValue(BadgeShadowColorProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal double BadgeShadowSize
|
|
||||||
{
|
|
||||||
get => GetValue(BadgeShadowSizeProperty);
|
|
||||||
set => SetValue(BadgeShadowSizeProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal double PaddingInline
|
|
||||||
{
|
|
||||||
get => GetValue(PaddingInlineProperty);
|
|
||||||
set => SetValue(PaddingInlineProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool _isAdornerMode;
|
private bool _isAdornerMode;
|
||||||
|
|
||||||
@ -162,19 +119,33 @@ internal class CountBadgeAdorner : Control
|
|||||||
set => SetAndRaise(IsAdornerModeProperty, ref _isAdornerMode, value);
|
set => SetAndRaise(IsAdornerModeProperty, ref _isAdornerMode, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private CountBadgeSize _size;
|
private BoxShadows _boxShadow;
|
||||||
|
public BoxShadows BoxShadow
|
||||||
public CountBadgeSize Size
|
|
||||||
{
|
{
|
||||||
get => _size;
|
get => _boxShadow;
|
||||||
set => SetAndRaise(SizeProperty, ref _size, value);
|
set => SetAndRaise(BoxShadowProperty, ref _boxShadow, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? _countText;
|
||||||
|
public string? CountText
|
||||||
|
{
|
||||||
|
get => _countText;
|
||||||
|
set => SetAndRaise(CountTextProperty, ref _countText, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TimeSpan _motionDuration;
|
||||||
|
public TimeSpan MotionDuration
|
||||||
|
{
|
||||||
|
get => _motionDuration;
|
||||||
|
set => SetAndRaise(MotionDurationProperty, ref _motionDuration, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
// 不知道为什么这个值会被 AdornerLayer 重写
|
private Panel? _rootLayout;
|
||||||
// 非常不优美,但是能工作
|
private MotionActorControl? _indicatorMotionActor;
|
||||||
internal RelativePoint? AnimationRenderTransformOrigin;
|
private CancellationTokenSource? _motionCancellationTokenSource;
|
||||||
|
private bool _needInitialHide;
|
||||||
|
|
||||||
static CountBadgeAdorner()
|
static CountBadgeAdorner()
|
||||||
{
|
{
|
||||||
@ -185,66 +156,28 @@ internal class CountBadgeAdorner : Control
|
|||||||
AffectsRender<CountBadgeAdorner>(BadgeColorProperty, OffsetProperty);
|
AffectsRender<CountBadgeAdorner>(BadgeColorProperty, OffsetProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool _initialized;
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
private BoxShadows _boxShadows;
|
|
||||||
private Size _countTextSize;
|
|
||||||
private string? _countText;
|
|
||||||
private readonly List<FormattedText> _formattedTexts;
|
|
||||||
|
|
||||||
public CountBadgeAdorner()
|
|
||||||
{
|
{
|
||||||
_formattedTexts = new List<FormattedText>();
|
base.OnApplyTemplate(e);
|
||||||
|
TokenResourceBinder.CreateTokenBinding(this, BadgeShadowSizeProperty, BadgeTokenResourceKey.BadgeShadowSize);
|
||||||
|
TokenResourceBinder.CreateTokenBinding(this, BadgeShadowColorProperty, BadgeTokenResourceKey.BadgeShadowColor);
|
||||||
|
TokenResourceBinder.CreateTokenBinding(this, MotionDurationProperty, GlobalTokenResourceKey.MotionDurationMid);
|
||||||
|
TokenResourceBinder.CreateTokenBinding(this, BadgeColorProperty, BadgeTokenResourceKey.BadgeColor);
|
||||||
|
_indicatorMotionActor = e.NameScope.Get<MotionActorControl>(CountBadgeAdornerTheme.IndicatorMotionActorPart);
|
||||||
|
if (_needInitialHide)
|
||||||
|
{
|
||||||
|
_indicatorMotionActor.IsVisible = false;
|
||||||
|
_needInitialHide = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnAttachedToLogicalTree(e);
|
|
||||||
if (Styles.Count == 0)
|
|
||||||
{
|
|
||||||
BuildStyles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildStyles()
|
|
||||||
{
|
|
||||||
var commonStyle = new Style();
|
|
||||||
commonStyle.Add(TextFontWeightProperty, BadgeTokenResourceKey.TextFontWeight);
|
|
||||||
commonStyle.Add(BadgeColorProperty, BadgeTokenResourceKey.BadgeColor);
|
|
||||||
commonStyle.Add(BadgeShadowSizeProperty, BadgeTokenResourceKey.BadgeShadowSize);
|
|
||||||
commonStyle.Add(BadgeShadowColorProperty, BadgeTokenResourceKey.BadgeShadowColor);
|
|
||||||
commonStyle.Add(BadgeTextColorProperty, BadgeTokenResourceKey.BadgeTextColor);
|
|
||||||
commonStyle.Add(PaddingInlineProperty, GlobalTokenResourceKey.PaddingXS);
|
|
||||||
Styles.Add(commonStyle);
|
|
||||||
|
|
||||||
var defaultSizeStyle =
|
|
||||||
new Style(selector => selector.PropertyEquals(SizeProperty, CountBadgeSize.Default));
|
|
||||||
defaultSizeStyle.Add(TextFontSizeProperty, BadgeTokenResourceKey.TextFontSize);
|
|
||||||
defaultSizeStyle.Add(IndicatorHeightProperty, BadgeTokenResourceKey.IndicatorHeight);
|
|
||||||
Styles.Add(defaultSizeStyle);
|
|
||||||
|
|
||||||
var smallSizeStyle = new Style(selector => selector.PropertyEquals(SizeProperty, CountBadgeSize.Small));
|
|
||||||
smallSizeStyle.Add(TextFontSizeProperty, BadgeTokenResourceKey.TextFontSizeSM);
|
|
||||||
smallSizeStyle.Add(IndicatorHeightProperty, BadgeTokenResourceKey.IndicatorHeightSM);
|
|
||||||
Styles.Add(smallSizeStyle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void ApplyTemplate()
|
|
||||||
{
|
|
||||||
if (!_initialized)
|
|
||||||
{
|
|
||||||
BuildBoxShadow();
|
BuildBoxShadow();
|
||||||
BuildCountText();
|
BuildCountText();
|
||||||
CalculateCountTextSize();
|
|
||||||
BuildFormattedTexts();
|
|
||||||
_initialized = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildBoxShadow()
|
private void BuildBoxShadow()
|
||||||
{
|
{
|
||||||
if (BadgeShadowColor is not null)
|
if (BadgeShadowColor is not null)
|
||||||
{
|
{
|
||||||
_boxShadows = new BoxShadows(new BoxShadow
|
BoxShadow = new BoxShadows(new BoxShadow
|
||||||
{
|
{
|
||||||
OffsetX = 0,
|
OffsetX = 0,
|
||||||
OffsetY = 0,
|
OffsetY = 0,
|
||||||
@ -269,143 +202,100 @@ internal class CountBadgeAdorner : Control
|
|||||||
if (e.Property == CountProperty || e.Property == OverflowCountProperty)
|
if (e.Property == CountProperty || e.Property == OverflowCountProperty)
|
||||||
{
|
{
|
||||||
BuildCountText();
|
BuildCountText();
|
||||||
CalculateCountTextSize(true);
|
|
||||||
BuildFormattedTexts(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Size MeasureOverride(Size availableSize)
|
|
||||||
{
|
|
||||||
if (IsAdornerMode)
|
|
||||||
{
|
|
||||||
return availableSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetBadgePillSize();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Size GetBadgePillSize()
|
|
||||||
{
|
|
||||||
var targetWidth = IndicatorHeight;
|
|
||||||
var targetHeight = IndicatorHeight;
|
|
||||||
if (_countText?.Length > 1)
|
|
||||||
{
|
|
||||||
targetWidth += PaddingInline;
|
|
||||||
if (Count > _overflowCount)
|
|
||||||
{
|
|
||||||
targetWidth += PaddingInline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
targetWidth = Math.Max(targetWidth, _countTextSize.Width);
|
|
||||||
targetHeight = Math.Max(targetHeight, _countTextSize.Height);
|
|
||||||
return new Size(targetWidth, targetHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CalculateCountTextSize(bool force = false)
|
|
||||||
{
|
|
||||||
if (force || _countTextSize == default)
|
|
||||||
{
|
|
||||||
var fontSize = TextFontSize;
|
|
||||||
var typeface = new Typeface(FontFamily, FontStyle.Normal, TextFontWeight);
|
|
||||||
var textLayout = new TextLayout(_countText,
|
|
||||||
typeface,
|
|
||||||
null,
|
|
||||||
fontSize,
|
|
||||||
null,
|
|
||||||
lineHeight: IndicatorHeight);
|
|
||||||
_countTextSize = new Size(Math.Round(textLayout.Width), Math.Round(textLayout.Height));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildCountText()
|
private void BuildCountText()
|
||||||
{
|
{
|
||||||
if (Count > _overflowCount)
|
CountText = Count > OverflowCount ? $"{OverflowCount}+" : $"{Count}";
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Size ArrangeOverride(Size finalSize)
|
||||||
{
|
{
|
||||||
_countText = $"{_overflowCount}+";
|
var size = base.ArrangeOverride(finalSize);
|
||||||
|
if (IsAdornerMode && _indicatorMotionActor is not null)
|
||||||
|
{
|
||||||
|
var offsetX = Offset.X;
|
||||||
|
var offsetY = Offset.Y;
|
||||||
|
var indicatorSize = _indicatorMotionActor.DesiredSize;
|
||||||
|
offsetX += finalSize.Width - indicatorSize.Width / 2;
|
||||||
|
offsetY -= indicatorSize.Height / 2;
|
||||||
|
_indicatorMotionActor.Arrange(new Rect(new Point(offsetX, offsetY), indicatorSize));
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyShowMotion()
|
||||||
|
{
|
||||||
|
if (_indicatorMotionActor is not null)
|
||||||
|
{
|
||||||
|
_indicatorMotionActor.IsVisible = false;
|
||||||
|
var zoomBadgeInMotionConfig = BadgeMotionFactory.BuildBadgeZoomBadgeInMotion(MotionDuration, null,
|
||||||
|
FillMode.Forward);
|
||||||
|
MotionInvoker.Invoke(_indicatorMotionActor, zoomBadgeInMotionConfig, () =>
|
||||||
|
{
|
||||||
|
_indicatorMotionActor.IsVisible = true;
|
||||||
|
}, null, _motionCancellationTokenSource!.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyHideMotion(Action completedAction)
|
||||||
|
{
|
||||||
|
if (_indicatorMotionActor is not null)
|
||||||
|
{
|
||||||
|
var zoomBadgeOutMotionConfig = BadgeMotionFactory.BuildBadgeZoomBadgeOutMotion(MotionDuration, null,
|
||||||
|
FillMode.Forward);
|
||||||
|
_motionCancellationTokenSource?.Cancel();
|
||||||
|
_motionCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
MotionInvoker.Invoke(_indicatorMotionActor, zoomBadgeOutMotionConfig, null, () =>
|
||||||
|
{
|
||||||
|
completedAction();
|
||||||
|
}, _motionCancellationTokenSource.Token);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_countText = $"{Count}";
|
_needInitialHide = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Render(DrawingContext context)
|
internal void ApplyToTarget(AdornerLayer? adornerLayer, Control adorned)
|
||||||
{
|
{
|
||||||
var offsetX = 0d;
|
if (adornerLayer is not null)
|
||||||
var offsetY = 0d;
|
|
||||||
var badgeSize = GetBadgePillSize();
|
|
||||||
if (IsAdornerMode)
|
|
||||||
{
|
{
|
||||||
offsetX = DesiredSize.Width - badgeSize.Width / 2;
|
adornerLayer.Children.Remove(this);
|
||||||
offsetY = -badgeSize.Height / 2;
|
|
||||||
offsetX += Offset.X;
|
AdornerLayer.SetAdornedElement(this, adorned);
|
||||||
offsetY += Offset.Y;
|
AdornerLayer.SetIsClipEnabled(this, false);
|
||||||
|
adornerLayer.Children.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
var badgeRect = new Rect(new Point(offsetX, offsetY), badgeSize);
|
_motionCancellationTokenSource?.Cancel();
|
||||||
|
_motionCancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
if (RenderTransform is not null)
|
ApplyShowMotion();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void DetachFromTarget(AdornerLayer? adornerLayer, bool enableMotion = true)
|
||||||
{
|
{
|
||||||
Point origin;
|
|
||||||
if (AnimationRenderTransformOrigin.HasValue)
|
if (enableMotion)
|
||||||
{
|
{
|
||||||
origin = AnimationRenderTransformOrigin.Value.ToPixels(badgeRect.Size);
|
ApplyHideMotion(() =>
|
||||||
|
{
|
||||||
|
if (adornerLayer is not null)
|
||||||
|
{
|
||||||
|
adornerLayer.Children.Remove(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
origin = RenderTransformOrigin.ToPixels(badgeRect.Size);
|
if (adornerLayer is not null)
|
||||||
}
|
|
||||||
|
|
||||||
var offset = Matrix.CreateTranslation(new Point(origin.X + offsetX, origin.Y + offsetY));
|
|
||||||
var renderTransform = -offset * RenderTransform.Value * offset;
|
|
||||||
context.PushTransform(renderTransform);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.DrawPilledRect(BadgeColor, null, badgeRect, Orientation.Horizontal, _boxShadows);
|
|
||||||
// 计算合适的文字 x 坐标
|
|
||||||
var textOffsetX = offsetX + (badgeSize.Width - _countTextSize.Width) / 2;
|
|
||||||
var textOffsetY = offsetY + (badgeSize.Height - _countTextSize.Height) / 2;
|
|
||||||
foreach (var formattedText in _formattedTexts)
|
|
||||||
{
|
{
|
||||||
context.DrawText(formattedText, new Point(textOffsetX, textOffsetY));
|
adornerLayer.Children.Remove(this);
|
||||||
textOffsetX += formattedText.Width;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildFormattedTexts(bool force = false)
|
|
||||||
{
|
|
||||||
if (_formattedTexts.Count == 0 || force)
|
|
||||||
{
|
|
||||||
_formattedTexts.Clear();
|
|
||||||
if (_countText is not null)
|
|
||||||
{
|
|
||||||
if (Count > _overflowCount)
|
|
||||||
{
|
|
||||||
// 生成一个即可
|
|
||||||
_formattedTexts.Add(BuildFormattedText(_countText));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 没有数字都生成一个
|
|
||||||
foreach (var c in _countText)
|
|
||||||
{
|
|
||||||
_formattedTexts.Add(BuildFormattedText(c.ToString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private FormattedText BuildFormattedText(string text)
|
|
||||||
{
|
|
||||||
var typeface = new Typeface(FontFamily, FontStyle.Normal, TextFontWeight);
|
|
||||||
var formattedText = new FormattedText(text, CultureInfo.CurrentUICulture, GetFlowDirection(this),
|
|
||||||
typeface, 1, BadgeTextColor);
|
|
||||||
formattedText.SetFontSize(TextFontSize);
|
|
||||||
formattedText.TextAlignment = TextAlignment.Left;
|
|
||||||
formattedText.LineHeight = IndicatorHeight;
|
|
||||||
return formattedText;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,13 +1,114 @@
|
|||||||
using AtomUI.Theme;
|
using AtomUI.MotionScene;
|
||||||
|
using AtomUI.Theme;
|
||||||
using AtomUI.Theme.Styling;
|
using AtomUI.Theme.Styling;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
|
||||||
namespace AtomUI.Controls.Badge;
|
namespace AtomUI.Controls.Badge;
|
||||||
|
|
||||||
[ControlThemeProvider]
|
[ControlThemeProvider]
|
||||||
internal class CountBadgeAdornerTheme : BaseControlTheme
|
internal class CountBadgeAdornerTheme : BaseControlTheme
|
||||||
{
|
{
|
||||||
|
internal const string IndicatorMotionActorPart = "PART_IndicatorMotionActor";
|
||||||
|
internal const string RootLayoutPart = "PART_RootLayout";
|
||||||
|
internal const string BadgeIndicatorPart = "PART_BadgeIndicator";
|
||||||
|
internal const string BadgeTextPart = "PART_BadgeText";
|
||||||
|
|
||||||
public CountBadgeAdornerTheme()
|
public CountBadgeAdornerTheme()
|
||||||
: base(typeof(CountBadgeAdorner))
|
: base(typeof(CountBadgeAdorner))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IControlTemplate BuildControlTemplate()
|
||||||
|
{
|
||||||
|
return new FuncControlTemplate<CountBadgeAdorner>((adorner, scope) =>
|
||||||
|
{
|
||||||
|
var indicatorMotionActor = new MotionActorControl()
|
||||||
|
{
|
||||||
|
Name = IndicatorMotionActorPart,
|
||||||
|
ClipToBounds = false,
|
||||||
|
UseRenderTransform = true
|
||||||
|
};
|
||||||
|
var layout = new Panel()
|
||||||
|
{
|
||||||
|
Name = RootLayoutPart,
|
||||||
|
};
|
||||||
|
indicatorMotionActor.Child = layout;
|
||||||
|
indicatorMotionActor.RegisterInNameScope(scope);
|
||||||
|
layout.RegisterInNameScope(scope);
|
||||||
|
BuildBadgeIndicator(adorner, layout, scope);
|
||||||
|
BuildBadgeText(layout, scope);
|
||||||
|
return indicatorMotionActor;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildBadgeIndicator(CountBadgeAdorner adorner, Panel layout, INameScope scope)
|
||||||
|
{
|
||||||
|
var indicator = new Border()
|
||||||
|
{
|
||||||
|
Name = BadgeIndicatorPart
|
||||||
|
};
|
||||||
|
|
||||||
|
CreateTemplateParentBinding(indicator, Border.BoxShadowProperty, CountBadgeAdorner.BoxShadowProperty);
|
||||||
|
CreateTemplateParentBinding(indicator, Border.BackgroundProperty, CountBadgeAdorner.BadgeColorProperty);
|
||||||
|
|
||||||
|
layout.Children.Add(indicator);
|
||||||
|
indicator.RegisterInNameScope(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildBadgeText(Panel layout, INameScope scope)
|
||||||
|
{
|
||||||
|
var badgeText = new TextBlock()
|
||||||
|
{
|
||||||
|
Name = BadgeTextPart,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center
|
||||||
|
};
|
||||||
|
CreateTemplateParentBinding(badgeText, TextBlock.TextProperty, CountBadgeAdorner.CountTextProperty);
|
||||||
|
layout.Children.Add(badgeText);
|
||||||
|
badgeText.RegisterInNameScope(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void BuildStyles()
|
||||||
|
{
|
||||||
|
var commonStyle = new Style(selector => selector.Nesting());
|
||||||
|
commonStyle.Add(CountBadgeAdorner.ClipToBoundsProperty, false);
|
||||||
|
var inAdornerStyle = new Style(selector => selector.Nesting().PropertyEquals(CountBadgeAdorner.IsAdornerModeProperty, true));
|
||||||
|
var layoutStyle = new Style(selector => selector.Nesting().Template().Name(RootLayoutPart));
|
||||||
|
layoutStyle.Add(Panel.HorizontalAlignmentProperty, HorizontalAlignment.Right);
|
||||||
|
layoutStyle.Add(Panel.VerticalAlignmentProperty, VerticalAlignment.Top);
|
||||||
|
inAdornerStyle.Add(layoutStyle);
|
||||||
|
commonStyle.Add(inAdornerStyle);
|
||||||
|
{
|
||||||
|
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(BadgeIndicatorPart));
|
||||||
|
indicatorStyle.Add(Border.HeightProperty, BadgeTokenResourceKey.IndicatorHeight);
|
||||||
|
indicatorStyle.Add(Border.MinWidthProperty, BadgeTokenResourceKey.IndicatorHeight);
|
||||||
|
indicatorStyle.Add(Border.CornerRadiusProperty, BadgeTokenResourceKey.CountBadgeCornerRadius);
|
||||||
|
commonStyle.Add(indicatorStyle);
|
||||||
|
|
||||||
|
var badgeTextStyle = new Style(selector => selector.Nesting().Template().Name(BadgeTextPart));
|
||||||
|
badgeTextStyle.Add(TextBlock.ForegroundProperty, BadgeTokenResourceKey.BadgeTextColor);
|
||||||
|
badgeTextStyle.Add(TextBlock.FontSizeProperty, BadgeTokenResourceKey.TextFontSize);
|
||||||
|
badgeTextStyle.Add(TextBlock.PaddingProperty, BadgeTokenResourceKey.CountBadgeTextPadding);
|
||||||
|
commonStyle.Add(badgeTextStyle);
|
||||||
|
}
|
||||||
|
|
||||||
|
var smallSizeStyle = new Style(selector => selector.Nesting().PropertyEquals(CountBadgeAdorner.SizeProperty, CountBadgeSize.Small));
|
||||||
|
{
|
||||||
|
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(BadgeIndicatorPart));
|
||||||
|
indicatorStyle.Add(Border.HeightProperty, BadgeTokenResourceKey.IndicatorHeightSM);
|
||||||
|
indicatorStyle.Add(Border.MinWidthProperty, BadgeTokenResourceKey.IndicatorHeightSM);
|
||||||
|
indicatorStyle.Add(Border.CornerRadiusProperty, BadgeTokenResourceKey.CountBadgeCornerRadiusSM);
|
||||||
|
smallSizeStyle.Add(indicatorStyle);
|
||||||
|
|
||||||
|
var badgeTextStyle = new Style(selector => selector.Nesting().Template().Name(BadgeTextPart));
|
||||||
|
badgeTextStyle.Add(TextBlock.FontSizeProperty, BadgeTokenResourceKey.TextFontSizeSM);
|
||||||
|
smallSizeStyle.Add(badgeTextStyle);
|
||||||
|
}
|
||||||
|
commonStyle.Add(smallSizeStyle);
|
||||||
|
|
||||||
|
Add(commonStyle);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,168 +0,0 @@
|
|||||||
using AtomUI.MotionScene;
|
|
||||||
using Avalonia;
|
|
||||||
using Avalonia.Animation.Easings;
|
|
||||||
using Avalonia.Controls;
|
|
||||||
|
|
||||||
namespace AtomUI.Controls.Badge;
|
|
||||||
|
|
||||||
internal class CountBadgeZoomBadgeIn : AbstractMotion
|
|
||||||
{
|
|
||||||
public MotionConfig? OpacityConfig => GetMotionConfig(MotionOpacityProperty);
|
|
||||||
public MotionConfig? RenderTransformConfig => GetMotionConfig(MotionRenderTransformProperty);
|
|
||||||
|
|
||||||
public CountBadgeZoomBadgeIn()
|
|
||||||
{
|
|
||||||
MotionRenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureOpacity(TimeSpan duration, Easing? easing = null)
|
|
||||||
{
|
|
||||||
easing ??= new ExponentialEaseOut();
|
|
||||||
var config = new MotionConfig(MotionOpacityProperty)
|
|
||||||
{
|
|
||||||
TransitionKind = TransitionKind.Double,
|
|
||||||
StartValue = 0d,
|
|
||||||
EndValue = 1d,
|
|
||||||
MotionDuration = duration,
|
|
||||||
MotionEasing = easing
|
|
||||||
};
|
|
||||||
AddMotionConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureRenderTransform(TimeSpan duration, Easing? easing = null)
|
|
||||||
{
|
|
||||||
easing ??= new BackEaseOut();
|
|
||||||
|
|
||||||
var config = new MotionConfig(MotionRenderTransformProperty)
|
|
||||||
{
|
|
||||||
TransitionKind = TransitionKind.TransformOperations,
|
|
||||||
StartValue = BuildScaleTransform(0),
|
|
||||||
EndValue = BuildScaleTransform(1),
|
|
||||||
MotionDuration = duration,
|
|
||||||
MotionEasing = easing
|
|
||||||
};
|
|
||||||
AddMotionConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void NotifyConfigMotionTarget(Control motionTarget)
|
|
||||||
{
|
|
||||||
base.NotifyConfigMotionTarget(motionTarget);
|
|
||||||
motionTarget.RenderTransformOrigin = MotionRenderTransformOrigin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CountBadgeZoomBadgeOut : AbstractMotion
|
|
||||||
{
|
|
||||||
public MotionConfig? OpacityConfig => GetMotionConfig(MotionOpacityProperty);
|
|
||||||
public MotionConfig? RenderTransformConfig => GetMotionConfig(MotionRenderTransformProperty);
|
|
||||||
|
|
||||||
public CountBadgeZoomBadgeOut()
|
|
||||||
{
|
|
||||||
MotionRenderTransformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureOpacity(TimeSpan duration, Easing? easing = null)
|
|
||||||
{
|
|
||||||
easing ??= new ExponentialEaseIn();
|
|
||||||
var config = new MotionConfig(MotionOpacityProperty)
|
|
||||||
{
|
|
||||||
TransitionKind = TransitionKind.Double,
|
|
||||||
StartValue = 1d,
|
|
||||||
EndValue = 0d,
|
|
||||||
MotionDuration = duration,
|
|
||||||
MotionEasing = easing
|
|
||||||
};
|
|
||||||
AddMotionConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureRenderTransform(TimeSpan duration, Easing? easing = null)
|
|
||||||
{
|
|
||||||
easing ??= new ExponentialEaseIn();
|
|
||||||
|
|
||||||
var config = new MotionConfig(MotionRenderTransformProperty)
|
|
||||||
{
|
|
||||||
TransitionKind = TransitionKind.TransformOperations,
|
|
||||||
StartValue = BuildScaleTransform(1),
|
|
||||||
EndValue = BuildScaleTransform(0),
|
|
||||||
MotionDuration = duration,
|
|
||||||
MotionEasing = easing
|
|
||||||
};
|
|
||||||
AddMotionConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
internal override void NotifyConfigMotionTarget(Control motionTarget)
|
|
||||||
{
|
|
||||||
base.NotifyConfigMotionTarget(motionTarget);
|
|
||||||
motionTarget.RenderTransformOrigin = MotionRenderTransformOrigin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CountBadgeNoWrapperZoomBadgeIn : AbstractMotion
|
|
||||||
{
|
|
||||||
public MotionConfig? OpacityConfig => GetMotionConfig(MotionOpacityProperty);
|
|
||||||
public MotionConfig? RenderTransformConfig => GetMotionConfig(MotionRenderTransformProperty);
|
|
||||||
|
|
||||||
public void ConfigureOpacity(TimeSpan duration, Easing? easing = null)
|
|
||||||
{
|
|
||||||
easing ??= new QuarticEaseOut();
|
|
||||||
var config = new MotionConfig(MotionOpacityProperty)
|
|
||||||
{
|
|
||||||
TransitionKind = TransitionKind.Double,
|
|
||||||
StartValue = 0d,
|
|
||||||
EndValue = 1d,
|
|
||||||
MotionDuration = duration,
|
|
||||||
MotionEasing = easing
|
|
||||||
};
|
|
||||||
AddMotionConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureRenderTransform(TimeSpan duration, Easing? easing = null)
|
|
||||||
{
|
|
||||||
easing ??= new QuarticEaseOut();
|
|
||||||
|
|
||||||
var config = new MotionConfig(MotionRenderTransformProperty)
|
|
||||||
{
|
|
||||||
TransitionKind = TransitionKind.TransformOperations,
|
|
||||||
StartValue = BuildScaleTransform(0),
|
|
||||||
EndValue = BuildScaleTransform(1),
|
|
||||||
MotionDuration = duration,
|
|
||||||
MotionEasing = easing
|
|
||||||
};
|
|
||||||
AddMotionConfig(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CountBadgeNoWrapperZoomBadgeOut : AbstractMotion
|
|
||||||
{
|
|
||||||
public MotionConfig? OpacityConfig => GetMotionConfig(MotionOpacityProperty);
|
|
||||||
public MotionConfig? RenderTransformConfig => GetMotionConfig(MotionRenderTransformProperty);
|
|
||||||
|
|
||||||
public void ConfigureOpacity(TimeSpan duration, Easing? easing = null)
|
|
||||||
{
|
|
||||||
easing ??= new CircularEaseIn();
|
|
||||||
var config = new MotionConfig(MotionOpacityProperty)
|
|
||||||
{
|
|
||||||
TransitionKind = TransitionKind.Double,
|
|
||||||
StartValue = 1d,
|
|
||||||
EndValue = 0d,
|
|
||||||
MotionDuration = duration,
|
|
||||||
MotionEasing = easing
|
|
||||||
};
|
|
||||||
AddMotionConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConfigureRenderTransform(TimeSpan duration, Easing? easing = null)
|
|
||||||
{
|
|
||||||
easing ??= new CircularEaseIn();
|
|
||||||
|
|
||||||
var config = new MotionConfig(MotionRenderTransformProperty)
|
|
||||||
{
|
|
||||||
TransitionKind = TransitionKind.TransformOperations,
|
|
||||||
StartValue = BuildScaleTransform(1),
|
|
||||||
EndValue = BuildScaleTransform(0),
|
|
||||||
MotionDuration = duration,
|
|
||||||
MotionEasing = easing
|
|
||||||
};
|
|
||||||
AddMotionConfig(config);
|
|
||||||
}
|
|
||||||
}
|
|
@ -125,20 +125,20 @@ public class DotBadge : Control
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HideAdorner()
|
private void HideAdorner(bool enableMotion)
|
||||||
{
|
{
|
||||||
// 这里需要抛出异常吗?
|
// 这里需要抛出异常吗?
|
||||||
if ( _dotBadgeAdorner is null)
|
if ( _dotBadgeAdorner is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_dotBadgeAdorner.DetachFromTarget(_adornerLayer);
|
_dotBadgeAdorner.DetachFromTarget(_adornerLayer, enableMotion);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnDetachedFromVisualTree(e);
|
base.OnDetachedFromVisualTree(e);
|
||||||
HideAdorner();
|
HideAdorner(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupTokenBindings()
|
private void SetupTokenBindings()
|
||||||
@ -186,32 +186,17 @@ public class DotBadge : Control
|
|||||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnPropertyChanged(e);
|
base.OnPropertyChanged(e);
|
||||||
if (e.Property == IsVisibleProperty)
|
if (e.Property == IsVisibleProperty ||
|
||||||
|
e.Property == BadgeIsVisibleProperty)
|
||||||
{
|
{
|
||||||
var badgeIsVisible = e.GetNewValue<bool>();
|
var badgeIsVisible = e.GetNewValue<bool>();
|
||||||
if (badgeIsVisible)
|
if (badgeIsVisible)
|
||||||
{
|
{
|
||||||
if (_adornerLayer is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrepareAdorner();
|
PrepareAdorner();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HideAdorner();
|
HideAdorner(true);
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (e.Property == BadgeIsVisibleProperty)
|
|
||||||
{
|
|
||||||
if (BadgeIsVisible)
|
|
||||||
{
|
|
||||||
PrepareAdorner();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HideAdorner();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,8 +187,8 @@ internal class DotBadgeAdorner : TemplatedControl
|
|||||||
var offsetY = Offset.Y;
|
var offsetY = Offset.Y;
|
||||||
var dotSize = _indicatorMotionActor.Bounds.Size;
|
var dotSize = _indicatorMotionActor.Bounds.Size;
|
||||||
offsetX += dotSize.Width / 3;
|
offsetX += dotSize.Width / 3;
|
||||||
offsetY += dotSize.Height / 3;
|
offsetY -= dotSize.Height / 3;
|
||||||
_indicatorMotionActor.Arrange(new Rect(new Point(offsetX, -offsetY), dotSize));
|
_indicatorMotionActor.Arrange(new Rect(new Point(offsetX, offsetY), dotSize));
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
@ -212,12 +212,20 @@ internal class DotBadgeAdorner : TemplatedControl
|
|||||||
ApplyShowMotion();
|
ApplyShowMotion();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void DetachFromTarget(AdornerLayer? adornerLayer)
|
internal void DetachFromTarget(AdornerLayer? adornerLayer, bool enableMotion = true)
|
||||||
{
|
{
|
||||||
if (adornerLayer is null)
|
if (adornerLayer is null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enableMotion)
|
||||||
|
{
|
||||||
ApplyHideMotion(() => adornerLayer.Children.Remove(this));
|
ApplyHideMotion(() => adornerLayer.Children.Remove(this));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adornerLayer.Children.Remove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -23,7 +23,7 @@ internal class DotBadgeAdornerTheme : BaseControlTheme
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override IControlTemplate? BuildControlTemplate()
|
protected override IControlTemplate BuildControlTemplate()
|
||||||
{
|
{
|
||||||
return new FuncControlTemplate<DotBadgeAdorner>((adorner, scope) =>
|
return new FuncControlTemplate<DotBadgeAdorner>((adorner, scope) =>
|
||||||
{
|
{
|
||||||
|
@ -65,6 +65,9 @@ namespace AtomUI.Theme.Styling
|
|||||||
public static readonly TokenResourceKey BadgeRibbonCornerDarkenAmount = new TokenResourceKey("Badge.BadgeRibbonCornerDarkenAmount", "AtomUI.Token");
|
public static readonly TokenResourceKey BadgeRibbonCornerDarkenAmount = new TokenResourceKey("Badge.BadgeRibbonCornerDarkenAmount", "AtomUI.Token");
|
||||||
public static readonly TokenResourceKey BadgeRibbonTextPadding = new TokenResourceKey("Badge.BadgeRibbonTextPadding", "AtomUI.Token");
|
public static readonly TokenResourceKey BadgeRibbonTextPadding = new TokenResourceKey("Badge.BadgeRibbonTextPadding", "AtomUI.Token");
|
||||||
public static readonly TokenResourceKey DotBadgeLabelMargin = new TokenResourceKey("Badge.DotBadgeLabelMargin", "AtomUI.Token");
|
public static readonly TokenResourceKey DotBadgeLabelMargin = new TokenResourceKey("Badge.DotBadgeLabelMargin", "AtomUI.Token");
|
||||||
|
public static readonly TokenResourceKey CountBadgeTextPadding = new TokenResourceKey("Badge.CountBadgeTextPadding", "AtomUI.Token");
|
||||||
|
public static readonly TokenResourceKey CountBadgeCornerRadius = new TokenResourceKey("Badge.CountBadgeCornerRadius", "AtomUI.Token");
|
||||||
|
public static readonly TokenResourceKey CountBadgeCornerRadiusSM = new TokenResourceKey("Badge.CountBadgeCornerRadiusSM", "AtomUI.Token");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ButtonSpinnerTokenResourceKey
|
public static class ButtonSpinnerTokenResourceKey
|
||||||
|
Loading…
Reference in New Issue
Block a user