Refactor DotBadgeAdorner motion

This commit is contained in:
polarboy 2024-09-30 19:18:18 +08:00
parent 6fc24d2bd8
commit 668d50ff7f
29 changed files with 710 additions and 464 deletions

View File

@ -1,14 +1,13 @@
using AtomUI.Controls.Primitives;
using Avalonia;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Styling;
namespace AtomUI.Controls.Utils;
namespace AtomUI.MotionScene;
internal static partial class MotionFactory
{
public static MotionConfig BuildCollapseMotion(Direction direction, TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildCollapseMotion(Direction direction, TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseOut();
@ -104,10 +103,10 @@ internal static partial class MotionFactory
}
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildExpandMotion(Direction direction, TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildExpandMotion(Direction direction, TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseIn();
@ -183,7 +182,7 @@ internal static partial class MotionFactory
endFrame.Setters.Add(scaleYSetter);
}
}
animation.Children.Add(endFrame);
if (direction == Direction.Left)
@ -204,6 +203,6 @@ internal static partial class MotionFactory
}
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
}

View File

@ -4,7 +4,7 @@ using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Transformation;
namespace AtomUI.Controls.Primitives;
namespace AtomUI.MotionScene;
public class MotionActorControl : Decorator
{
@ -12,6 +12,9 @@ public class MotionActorControl : Decorator
public static readonly StyledProperty<TransformOperations?> MotionTransformProperty =
AvaloniaProperty.Register<MotionActorControl, TransformOperations?>(nameof(MotionTransform));
public static readonly StyledProperty<bool> UseRenderTransformProperty =
AvaloniaProperty.Register<LayoutTransformControl, bool>(nameof(UseRenderTransform));
public TransformOperations? MotionTransform
{
@ -19,6 +22,12 @@ public class MotionActorControl : Decorator
set => SetValue(MotionTransformProperty, value);
}
public bool UseRenderTransform
{
get => GetValue(UseRenderTransformProperty);
set => SetValue(UseRenderTransformProperty, value);
}
public Control? MotionTransformRoot => Child;
#endregion
@ -107,9 +116,9 @@ public class MotionActorControl : Decorator
{
return;
}
_transformation = matrix;
_matrixTransform.Matrix = FilterScaleTransform(matrix);
_matrixTransform.Matrix = UseRenderTransform ? matrix : FilterScaleTransform(matrix);
RenderTransform = _matrixTransform;
// New transform means re-layout is necessary
InvalidateMeasure();
@ -145,15 +154,15 @@ public class MotionActorControl : Decorator
protected override Size ArrangeOverride(Size finalSize)
{
if (MotionTransformRoot == null || MotionTransform == null)
if (MotionTransformRoot == null || MotionTransform == null || UseRenderTransform)
{
// TODO 这里可能会引起混淆,因为我们不会对 Target 实施 Scale 转换
SetCurrentValue(MotionTransformProperty, RenderTransform);
return base.ArrangeOverride(finalSize);
}
// Determine the largest available size after the transformation
Size finalSizeTransformed = ComputeLargestTransformedSize(finalSize);
if (IsSizeSmaller(finalSizeTransformed, MotionTransformRoot.DesiredSize))
{
// Some elements do not like being given less space than they asked for (ex: TextBlock)
@ -181,9 +190,9 @@ public class MotionActorControl : Decorator
{
//// Unfortunately, all the work so far is invalid because the wrong DesiredSize was used
//// Make a note of the actual DesiredSize
//_childActualSize = arrangedsize;
//// Force a new measure/arrange pass
//InvalidateMeasure();
// _childActualSize = arrangedsize;
// //// Force a new measure/arrange pass
// InvalidateMeasure();
}
else
{
@ -197,12 +206,12 @@ public class MotionActorControl : Decorator
protected override Size MeasureOverride(Size availableSize)
{
if (MotionTransformRoot == null || MotionTransform == null)
if (MotionTransformRoot == null || MotionTransform == null || UseRenderTransform)
{
return base.MeasureOverride(availableSize);
}
Size measureSize;
Size measureSize ;
if (_childActualSize == default)
{
// Determine the largest size after the transformation

View File

@ -2,14 +2,14 @@
using Avalonia.Animation;
using Avalonia.Media.Transformation;
namespace AtomUI.Controls.Utils;
namespace AtomUI.MotionScene;
internal record MotionConfig
internal record MotionConfigX
{
public RelativePoint RenderTransformOrigin { get; }
public IList<Animation> Animations { get; }
public MotionConfig(RelativePoint renderTransformOrigin, IList<Animation> animations)
public MotionConfigX(RelativePoint renderTransformOrigin, IList<Animation> animations)
{
RenderTransformOrigin = renderTransformOrigin;
Animations = animations;
@ -18,36 +18,36 @@ internal record MotionConfig
internal static partial class MotionFactory
{
static TransformOperations BuildScaleTransform(double scaleX, double scaleY)
public static TransformOperations BuildScaleTransform(double scaleX, double scaleY)
{
var builder = new TransformOperations.Builder(1);
builder.AppendScale(scaleX, scaleY);
return builder.Build();
}
static TransformOperations BuildScaleTransform(double scale)
public static TransformOperations BuildScaleTransform(double scale)
{
return BuildScaleTransform(scale, scale);
}
static TransformOperations BuildScaleXTransform(double scale)
public static TransformOperations BuildScaleXTransform(double scale)
{
return BuildScaleTransform(scale, 1.0);
}
static TransformOperations BuildScaleYTransform(double scale)
public static TransformOperations BuildScaleYTransform(double scale)
{
return BuildScaleTransform(1.0, scale);
}
static TransformOperations BuildTranslateTransform(double offsetX, double offsetY)
public static TransformOperations BuildTranslateTransform(double offsetX, double offsetY)
{
var builder = new TransformOperations.Builder(1);
builder.AppendTranslate(offsetX, offsetY);
return builder.Build();
}
static TransformOperations BuildTranslateScaleAndTransform(double scaleX, double scaleY, double offsetX, double offsetY)
public static TransformOperations BuildTranslateScaleAndTransform(double scaleX, double scaleY, double offsetX, double offsetY)
{
var builder = new TransformOperations.Builder(2);
builder.AppendScale(scaleX, scaleY);

View File

@ -1,16 +1,16 @@
using AtomUI.Controls.Primitives;
using Avalonia;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Threading;
namespace AtomUI.Controls.Utils;
namespace AtomUI.MotionScene;
internal static class MotionInvoker
{
public static void Invoke(MotionActorControl target,
MotionConfig motionConfig,
MotionConfigX motionConfig,
Action? aboutToStart = null,
Action? completedAction = null)
Action? completedAction = null,
CancellationToken cancellationToken = default)
{
Dispatcher.UIThread.InvokeAsync(async () =>
{
@ -23,7 +23,7 @@ internal static class MotionInvoker
foreach (var animation in motionConfig.Animations)
{
await animation.RunAsync(target);
await animation.RunAsync(target, cancellationToken);
}
if (completedAction != null)

View File

@ -2,7 +2,7 @@
using Avalonia.Media;
using Avalonia.Media.Transformation;
namespace AtomUI.Controls.Utils;
namespace AtomUI.MotionScene;
internal class MotionTransformOptionsAnimator : InterpolatingAnimator<TransformOperations>
{

View File

@ -1,15 +1,14 @@
using AtomUI.Controls.Primitives;
using Avalonia;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Media;
using Avalonia.Styling;
namespace AtomUI.Controls.Utils;
namespace AtomUI.MotionScene;
internal static partial class MotionFactory
{
public static MotionConfig BuildMoveDownInMotion(double offset,
public static MotionConfigX BuildMoveDownInMotion(double offset,
TimeSpan duration,
Easing? easing = null,
FillMode fillMode = FillMode.None)
@ -89,10 +88,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildMoveDownOutMotion(double offset,
public static MotionConfigX BuildMoveDownOutMotion(double offset,
TimeSpan duration,
Easing? easing = null,
FillMode fillMode = FillMode.None)
@ -173,10 +172,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildMoveUpInMotion(double offset, TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildMoveUpInMotion(double offset, TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new QuinticEaseOut();
@ -254,10 +253,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildMoveUpOutMotion(double offset, TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildMoveUpOutMotion(double offset, TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new QuinticEaseIn();
@ -345,10 +344,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildMoveLeftInMotion(double offset, TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildMoveLeftInMotion(double offset, TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new QuinticEaseOut();
@ -429,10 +428,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildMoveLeftOutMotion(double offset, TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildMoveLeftOutMotion(double offset, TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new QuinticEaseIn();
@ -509,10 +508,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildMoveRightInMotion(double offset, TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildMoveRightInMotion(double offset, TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new QuinticEaseOut();
@ -590,10 +589,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildMoveRightOutMotion(double offset, TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildMoveRightOutMotion(double offset, TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new QuinticEaseIn();
@ -671,6 +670,6 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
}

View File

@ -1,14 +1,13 @@
using AtomUI.Controls.Primitives;
using Avalonia;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Styling;
namespace AtomUI.Controls.Utils;
namespace AtomUI.MotionScene;
internal static partial class MotionFactory
{
public static MotionConfig BuildSlideUpInMotion(TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildSlideUpInMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseOut();
@ -63,10 +62,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildSlideUpOutMotion(TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildSlideUpOutMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseIn();
@ -122,10 +121,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildSlideDownInMotion(TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildSlideDownInMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseOut();
@ -181,10 +180,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(1.0, 1.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildSlideDownOutMotion(TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildSlideDownOutMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseIn();
@ -240,10 +239,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(1.0, 1.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildSlideLeftInMotion(TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildSlideLeftInMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseOut();
@ -299,10 +298,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildSlideLeftOutMotion(TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildSlideLeftOutMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseIn();
@ -358,10 +357,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildSlideRightInMotion(TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildSlideRightInMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseOut();
@ -417,10 +416,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(1.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfig BuildSlideRightOutMotion(TimeSpan duration, Easing? easing = null,
public static MotionConfigX BuildSlideRightOutMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CubicEaseIn();
@ -476,6 +475,6 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(1.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfig(transformOrigin, animations);
return new MotionConfigX(transformOrigin, animations);
}
}

View File

@ -0,0 +1,250 @@
using AtomUI.MotionScene;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Styling;
namespace AtomUI.Controls.Badge;
internal static class BadgeMotionFactory
{
public static MotionConfigX BuildBadgeZoomBadgeInMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new ExponentialEaseOut();
var animations = new List<Animation>();
RelativePoint transformOrigin = default;
var animation = new Animation
{
Duration = duration,
Easing = easing,
FillMode = fillMode
};
var startFrame = new KeyFrame
{
Cue = new Cue(0.0)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 0.0
};
startFrame.Setters.Add(opacitySetter);
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = MotionFactory.BuildScaleTransform(0.0)
};
startFrame.Setters.Add(transformSetter);
}
animation.Children.Add(startFrame);
var endFrame = new KeyFrame
{
Cue = new Cue(1.0)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 1.0
};
endFrame.Setters.Add(opacitySetter);
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = MotionFactory.BuildScaleTransform(1.0)
};
endFrame.Setters.Add(transformSetter);
}
animation.Children.Add(endFrame);
transformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfigX BuildBadgeZoomBadgeOutMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new ExponentialEaseIn();
var animations = new List<Animation>();
RelativePoint transformOrigin = default;
var animation = new Animation
{
Duration = duration,
Easing = easing,
FillMode = fillMode
};
var startFrame = new KeyFrame
{
Cue = new Cue(0.0)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 1.0
};
startFrame.Setters.Add(opacitySetter);
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = MotionFactory.BuildScaleTransform(1.0)
};
startFrame.Setters.Add(transformSetter);
}
animation.Children.Add(startFrame);
var endFrame = new KeyFrame
{
Cue = new Cue(1.0)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 0.0
};
endFrame.Setters.Add(opacitySetter);
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = MotionFactory.BuildScaleTransform(0.0)
};
endFrame.Setters.Add(transformSetter);
}
animation.Children.Add(endFrame);
transformOrigin = new RelativePoint(0.5, 0.5, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfigX BuildCountBadgeNoWrapperZoomBadgeInMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CircularEaseOut();
var animations = new List<Animation>();
RelativePoint transformOrigin = default;
var animation = new Animation
{
Duration = duration,
Easing = easing,
FillMode = fillMode
};
var startFrame = new KeyFrame
{
Cue = new Cue(0.0)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 0.0
};
startFrame.Setters.Add(opacitySetter);
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = MotionFactory.BuildScaleTransform(0.0, 0.0)
};
startFrame.Setters.Add(transformSetter);
}
animation.Children.Add(startFrame);
var endFrame = new KeyFrame
{
Cue = new Cue(1.0)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 1.0
};
endFrame.Setters.Add(opacitySetter);
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = MotionFactory.BuildScaleTransform(1.0, 1.0)
};
endFrame.Setters.Add(transformSetter);
}
animation.Children.Add(endFrame);
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfigX(transformOrigin, animations);
}
public static MotionConfigX BuildCountBadgeNoWrapperZoomBadgeOutMotion(TimeSpan duration, Easing? easing = null,
FillMode fillMode = FillMode.None)
{
easing ??= new CircularEaseIn();
var animations = new List<Animation>();
RelativePoint transformOrigin = default;
var animation = new Animation
{
Duration = duration,
Easing = easing,
FillMode = fillMode
};
var startFrame = new KeyFrame
{
Cue = new Cue(0.0)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 1.0
};
startFrame.Setters.Add(opacitySetter);
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = MotionFactory.BuildScaleTransform(1.0, 1.0)
};
startFrame.Setters.Add(transformSetter);
}
animation.Children.Add(startFrame);
var endFrame = new KeyFrame
{
Cue = new Cue(1.0)
};
{
var opacitySetter = new Setter
{
Property = Visual.OpacityProperty,
Value = 0.0
};
endFrame.Setters.Add(opacitySetter);
var transformSetter = new Setter
{
Property = MotionActorControl.MotionTransformProperty,
Value = MotionFactory.BuildScaleTransform(0.0, 0.0)
};
endFrame.Setters.Add(transformSetter);
}
animation.Children.Add(endFrame);
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation);
return new MotionConfigX(transformOrigin, animations);
}
}

View File

@ -62,6 +62,7 @@ internal class BadgeToken : AbstractControlDesignToken
public Transform? BadgeRibbonCornerTransform { get; set; }
public int BadgeRibbonCornerDarkenAmount { get; set; }
public Thickness BadgeRibbonTextPadding { get; set; }
public Thickness DotBadgeLabelMargin { get; set; }
#endregion
@ -90,5 +91,6 @@ internal class BadgeToken : AbstractControlDesignToken
BadgeRibbonCornerTransform = new ScaleTransform(1, 0.75);
BadgeRibbonCornerDarkenAmount = 15;
BadgeRibbonTextPadding = new Thickness(_globalToken.PaddingXS, 0);
DotBadgeLabelMargin = new Thickness(_globalToken.MarginXS, 0, 0, 0);
}
}

View File

@ -98,9 +98,7 @@ public class CountBadge : Control
get => GetValue(BadgeIsVisibleProperty);
set => SetValue(BadgeIsVisibleProperty, value);
}
#endregion
#region

View File

@ -0,0 +1,13 @@
using AtomUI.Theme;
using AtomUI.Theme.Styling;
namespace AtomUI.Controls.Badge;
[ControlThemeProvider]
internal class CountBadgeAdornerTheme : BaseControlTheme
{
public CountBadgeAdornerTheme()
: base(typeof(CountBadgeAdorner))
{
}
}

View File

@ -1,13 +1,9 @@
using AtomUI.Controls.Badge;
using AtomUI.Controls.MotionScene;
using AtomUI.Data;
using AtomUI.MotionScene;
using AtomUI.Data;
using AtomUI.Theme.Palette;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Metadata;
@ -83,38 +79,18 @@ public class DotBadge : Control
get => GetValue(BadgeIsVisibleProperty);
set => SetValue(BadgeIsVisibleProperty, value);
}
#endregion
#region
internal static readonly StyledProperty<TimeSpan> MotionDurationProperty =
AvaloniaProperty.Register<CountBadge, TimeSpan>(
nameof(MotionDuration));
internal TimeSpan MotionDuration
{
get => GetValue(MotionDurationProperty);
set => SetValue(MotionDurationProperty, value);
}
#endregion
private readonly bool _initialized = false;
private DotBadgeAdorner? _dotBadgeAdorner;
private AdornerLayer? _adornerLayer;
private bool _animating;
static DotBadge()
{
AffectsMeasure<DotBadge>(DecoratedTargetProperty, TextProperty);
AffectsRender<DotBadge>(DotColorProperty, StatusProperty);
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
PrepareAdorner();
HorizontalAlignmentProperty.OverrideDefaultValue<DotBadge>(HorizontalAlignment.Left);
VerticalAlignmentProperty.OverrideDefaultValue<DotBadge>(VerticalAlignment.Top);
}
private DotBadgeAdorner CreateDotBadgeAdorner()
@ -135,7 +111,7 @@ public class DotBadge : Control
private void PrepareAdorner()
{
if (_adornerLayer is null && DecoratedTarget is not null)
if (DecoratedTarget is not null)
{
var dotBadgeAdorner = CreateDotBadgeAdorner();
_adornerLayer = AdornerLayer.GetAdornerLayer(this);
@ -145,71 +121,18 @@ public class DotBadge : Control
return;
}
AdornerLayer.SetAdornedElement(dotBadgeAdorner, this);
AdornerLayer.SetIsClipEnabled(dotBadgeAdorner, false);
_adornerLayer.Children.Add(dotBadgeAdorner);
dotBadgeAdorner.ApplyToTarget(_adornerLayer, this);
}
}
private void PrepareAdornerWithMotion()
{
PrepareAdorner();
if (VisualRoot is null || _animating)
{
return;
}
_animating = true;
var director = Director.Instance;
var motion = new CountBadgeZoomBadgeIn();
motion.ConfigureOpacity(MotionDuration);
motion.ConfigureRenderTransform(MotionDuration);
_dotBadgeAdorner!.AnimationRenderTransformOrigin = motion.MotionRenderTransformOrigin;
var motionActor = new MotionActor(_dotBadgeAdorner, motion);
motionActor.DispatchInSceneLayer = false;
motionActor.Completed += (sender, args) =>
{
_dotBadgeAdorner.AnimationRenderTransformOrigin = null;
_animating = false;
};
director?.Schedule(motionActor);
}
private void HideAdorner()
{
// 这里需要抛出异常吗?
if (_adornerLayer is null || _dotBadgeAdorner is null)
if ( _dotBadgeAdorner is null)
{
return;
}
_adornerLayer.Children.Remove(_dotBadgeAdorner);
_adornerLayer = null;
}
private void HideAdornerWithMotion()
{
if (VisualRoot is null || _animating)
{
return;
}
_animating = true;
var director = Director.Instance;
var motion = new CountBadgeZoomBadgeOut();
motion.ConfigureOpacity(MotionDuration);
motion.ConfigureRenderTransform(MotionDuration);
_dotBadgeAdorner!.AnimationRenderTransformOrigin = motion.MotionRenderTransformOrigin;
var motionActor = new MotionActor(_dotBadgeAdorner, motion);
motionActor.DispatchInSceneLayer = false;
motionActor.Completed += (sender, args) =>
{
HideAdorner();
_dotBadgeAdorner.AnimationRenderTransformOrigin = null;
_animating = false;
};
director?.Schedule(motionActor);
_dotBadgeAdorner.DetachFromTarget(_adornerLayer);
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
@ -217,7 +140,7 @@ public class DotBadge : Control
base.OnDetachedFromVisualTree(e);
HideAdorner();
}
private void SetupTokenBindings()
{
if (_dotBadgeAdorner is not null)
@ -226,8 +149,6 @@ public class DotBadge : Control
BindUtils.RelayBind(this, TextProperty, _dotBadgeAdorner, DotBadgeAdorner.TextProperty);
BindUtils.RelayBind(this, OffsetProperty, _dotBadgeAdorner, DotBadgeAdorner.OffsetProperty);
}
TokenResourceBinder.CreateTokenBinding(this, MotionDurationProperty, GlobalTokenResourceKey.MotionDurationSlow);
}
private void HandleDecoratedTargetChanged()
@ -240,7 +161,7 @@ public class DotBadge : Control
((ISetLogicalParent)_dotBadgeAdorner).SetParent(this);
VisualChildren.Add(_dotBadgeAdorner);
}
else if (DecoratedTarget is not null)
else
{
_dotBadgeAdorner.IsAdornerMode = true;
VisualChildren.Add(DecoratedTarget);
@ -256,6 +177,10 @@ public class DotBadge : Control
{
CreateDotBadgeAdorner();
}
else
{
PrepareAdorner();
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
@ -280,37 +205,17 @@ public class DotBadge : Control
}
else if (e.Property == BadgeIsVisibleProperty)
{
var badgeIsVisible = e.GetNewValue<bool>();
if (badgeIsVisible)
if (BadgeIsVisible)
{
if (_adornerLayer is not null)
{
return;
}
if (DecoratedTarget is not null)
{
PrepareAdornerWithMotion();
}
else
{
PrepareAdorner();
}
PrepareAdorner();
}
else
{
if (DecoratedTarget is not null)
{
HideAdornerWithMotion();
}
else
{
HideAdorner();
}
HideAdorner();
}
}
if (_initialized)
if (VisualRoot is not null)
{
if (e.Property == DecoratedTargetProperty)
{
@ -342,4 +247,5 @@ public class DotBadge : Control
_dotBadgeAdorner!.BadgeDotColor = new SolidColorBrush(color);
}
}
}

View File

@ -1,20 +1,32 @@
using AtomUI.Theme.Styling;
using AtomUI.Controls.Badge;
using AtomUI.MotionScene;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.Styling;
namespace AtomUI.Controls;
internal class DotBadgeAdorner : Control
internal class DotBadgeAdorner : TemplatedControl
{
public static readonly StyledProperty<IBrush?> BadgeDotColorProperty =
AvaloniaProperty.Register<DotBadgeAdorner, IBrush?>(
nameof(BadgeDotColor));
public static readonly DirectProperty<DotBadgeAdorner, DotBadgeStatus?> StatusProperty =
AvaloniaProperty.RegisterDirect<DotBadgeAdorner, DotBadgeStatus?>(
nameof(Status),
o => o.Status,
(o, v) => o.Status = v);
internal IBrush? BadgeDotColor
{
get => GetValue(BadgeDotColorProperty);
set => SetValue(BadgeDotColorProperty, value);
}
private DotBadgeStatus? _status;
@ -44,30 +56,6 @@ internal class DotBadgeAdorner : Control
o => o.IsAdornerMode,
(o, v) => o.IsAdornerMode = v);
internal static readonly StyledProperty<IBrush?> BadgeDotColorProperty =
AvaloniaProperty.Register<DotBadgeAdorner, IBrush?>(
nameof(BadgeDotColor));
internal static readonly StyledProperty<double> DotSizeProperty =
AvaloniaProperty.Register<DotBadgeAdorner, double>(
nameof(DotSize));
internal static readonly StyledProperty<double> StatusSizeProperty =
AvaloniaProperty.Register<DotBadgeAdorner, double>(
nameof(StatusSize));
internal static readonly StyledProperty<IBrush?> BadgeShadowColorProperty =
AvaloniaProperty.Register<DotBadgeAdorner, IBrush?>(
nameof(BadgeShadowColor));
private static readonly StyledProperty<double> BadgeShadowSizeProperty =
AvaloniaProperty.Register<DotBadgeAdorner, double>(
nameof(BadgeShadowSize));
private static readonly StyledProperty<double> BadgeTextMarginInlineProperty =
AvaloniaProperty.Register<DotBadgeAdorner, double>(
nameof(BadgeTextMarginInline));
public static readonly StyledProperty<Point> OffsetProperty =
AvaloniaProperty.Register<DotBadgeAdorner, Point>(
nameof(Offset));
@ -85,256 +73,151 @@ internal class DotBadgeAdorner : Control
get => GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value);
}
#region
public double DotSize
internal static readonly StyledProperty<TimeSpan> MotionDurationProperty =
AvaloniaProperty.Register<DotBadgeAdorner, TimeSpan>(
nameof(MotionDuration));
internal TimeSpan MotionDuration
{
get => GetValue(DotSizeProperty);
set => SetValue(DotSizeProperty, value);
get => GetValue(MotionDurationProperty);
set => SetValue(MotionDurationProperty, value);
}
public double StatusSize
{
get => GetValue(StatusSizeProperty);
set => SetValue(StatusSizeProperty, value);
}
internal IBrush? BadgeDotColor
{
get => GetValue(BadgeDotColorProperty);
set => SetValue(BadgeDotColorProperty, value);
}
internal IBrush? BadgeShadowColor
{
get => GetValue(BadgeShadowColorProperty);
set => SetValue(BadgeShadowColorProperty, value);
}
public double BadgeShadowSize
{
get => GetValue(BadgeShadowSizeProperty);
set => SetValue(BadgeShadowSizeProperty, value);
}
public double BadgeTextMarginInline
{
get => GetValue(BadgeTextMarginInlineProperty);
set => SetValue(BadgeTextMarginInlineProperty, value);
}
private bool _initialized;
private Label? _textLabel;
private BoxShadows _boxShadows;
// 不知道为什么这个值会被 AdornerLayer 重写
// 非常不优美,但是能工作
internal RelativePoint? AnimationRenderTransformOrigin;
#endregion
private MotionActorControl? _indicatorMotionActor;
private CancellationTokenSource? _motionCancellationTokenSource;
static DotBadgeAdorner()
{
AffectsMeasure<DotBadge>(TextProperty, IsAdornerModeProperty);
AffectsRender<DotBadge>(BadgeDotColorProperty, OffsetProperty);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
TokenResourceBinder.CreateTokenBinding(this, MotionDurationProperty, GlobalTokenResourceKey.MotionDurationMid);
SetupBadgeColor();
_indicatorMotionActor = e.NameScope.Get<MotionActorControl>(DotBadgeAdornerTheme.IndicatorMotionActorPart);
}
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);
}
}
public sealed override void ApplyTemplate()
private void ApplyHideMotion(Action completedAction)
{
base.ApplyTemplate();
if (!_initialized)
if (_indicatorMotionActor is not null)
{
_textLabel = new Label
var zoomBadgeOutMotionConfig = BadgeMotionFactory.BuildBadgeZoomBadgeOutMotion(MotionDuration, null,
FillMode.Forward);
_motionCancellationTokenSource?.Cancel();
_motionCancellationTokenSource = new CancellationTokenSource();
MotionInvoker.Invoke(_indicatorMotionActor, zoomBadgeOutMotionConfig, null, () =>
{
Content = Text,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Padding = new Thickness(0)
};
((ISetLogicalParent)_textLabel).SetParent(this);
VisualChildren.Add(_textLabel);
BuildBoxShadow();
_initialized = true;
completedAction();
}, _motionCancellationTokenSource.Token);
}
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnAttachedToLogicalTree(e);
BuildStyles();
}
private void BuildStyles()
{
if (Styles.Count == 0)
base.OnPropertyChanged(change);
if (VisualRoot is not null)
{
BuildBadgeColorStyle();
}
}
private void BuildBadgeColorStyle()
{
var commonStyle = new Style(selector => selector.OfType<DotBadgeAdorner>());
commonStyle.Add(BadgeTextMarginInlineProperty, GlobalTokenResourceKey.MarginXS);
commonStyle.Add(BadgeDotColorProperty, BadgeTokenResourceKey.BadgeColor);
commonStyle.Add(DotSizeProperty, BadgeTokenResourceKey.DotSize);
commonStyle.Add(StatusSizeProperty, BadgeTokenResourceKey.StatusSize);
commonStyle.Add(BadgeShadowSizeProperty, BadgeTokenResourceKey.BadgeShadowSize);
commonStyle.Add(BadgeShadowColorProperty, BadgeTokenResourceKey.BadgeShadowColor);
var errorStatusStyle =
new Style(selector => selector.Nesting().PropertyEquals(StatusProperty, DotBadgeStatus.Error));
errorStatusStyle.Add(BadgeDotColorProperty, GlobalTokenResourceKey.ColorError);
commonStyle.Add(errorStatusStyle);
var successStatusStyle =
new Style(selector => selector.Nesting().PropertyEquals(StatusProperty, DotBadgeStatus.Success));
successStatusStyle.Add(BadgeDotColorProperty, GlobalTokenResourceKey.ColorSuccess);
commonStyle.Add(successStatusStyle);
var warningStatusStyle =
new Style(selector => selector.Nesting().PropertyEquals(StatusProperty, DotBadgeStatus.Warning));
warningStatusStyle.Add(BadgeDotColorProperty, GlobalTokenResourceKey.ColorWarning);
commonStyle.Add(warningStatusStyle);
var defaultStatusStyle =
new Style(selector => selector.Nesting().PropertyEquals(StatusProperty, DotBadgeStatus.Default));
defaultStatusStyle.Add(BadgeDotColorProperty, GlobalTokenResourceKey.ColorTextPlaceholder);
commonStyle.Add(defaultStatusStyle);
var processingStatusStyle = new Style(selector =>
selector.Nesting().PropertyEquals(StatusProperty, DotBadgeStatus.Processing));
processingStatusStyle.Add(BadgeDotColorProperty, GlobalTokenResourceKey.ColorInfo);
commonStyle.Add(processingStatusStyle);
Styles.Add(commonStyle);
}
protected override Size MeasureOverride(Size availableSize)
{
var targetWidth = 0d;
var targetHeight = 0d;
if (IsAdornerMode)
{
targetWidth = availableSize.Width;
targetHeight = availableSize.Height;
}
else
{
var textSize = base.MeasureOverride(availableSize);
targetWidth += StatusSize;
targetWidth += textSize.Width;
targetHeight += Math.Max(textSize.Height, StatusSize);
if (textSize.Width > 0)
if (change.Property == StatusProperty)
{
targetWidth += BadgeTextMarginInline;
SetupBadgeColor();
}
}
}
private void SetupBadgeColor()
{
if (Status is not null)
{
if (Status == DotBadgeStatus.Error)
{
TokenResourceBinder.CreateGlobalTokenBinding(this, BadgeDotColorProperty,
GlobalTokenResourceKey.ColorError);
}
else if (Status == DotBadgeStatus.Success)
{
TokenResourceBinder.CreateGlobalTokenBinding(this, BadgeDotColorProperty,
GlobalTokenResourceKey.ColorSuccess);
}
else if (Status == DotBadgeStatus.Warning)
{
TokenResourceBinder.CreateGlobalTokenBinding(this, BadgeDotColorProperty,
GlobalTokenResourceKey.ColorWarning);
}
else if (Status == DotBadgeStatus.Processing)
{
TokenResourceBinder.CreateGlobalTokenBinding(this, BadgeDotColorProperty,
GlobalTokenResourceKey.ColorInfo);
}
else if (Status == DotBadgeStatus.Default)
{
TokenResourceBinder.CreateGlobalTokenBinding(this, BadgeDotColorProperty,
GlobalTokenResourceKey.ColorTextPlaceholder);
}
}
return new Size(targetWidth, targetHeight);
}
protected override Size ArrangeOverride(Size finalSize)
{
if (!IsAdornerMode)
var size = base.ArrangeOverride(finalSize);
if (IsAdornerMode && _indicatorMotionActor is not null)
{
double textOffsetX = 0;
if (IsAdornerMode)
{
textOffsetX += DotSize;
}
else
{
textOffsetX += StatusSize;
}
textOffsetX += BadgeTextMarginInline;
var textRect = new Rect(new Point(textOffsetX, 0), _textLabel!.DesiredSize);
_textLabel.Arrange(textRect);
var offsetX = Offset.X;
var offsetY = Offset.Y;
var dotSize = _indicatorMotionActor.Bounds.Size;
offsetX += dotSize.Width / 3;
offsetY += dotSize.Height / 3;
_indicatorMotionActor.Arrange(new Rect(new Point(offsetX, -offsetY), dotSize));
}
return finalSize;
return size;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
internal void ApplyToTarget(AdornerLayer? adornerLayer, Control adorned)
{
base.OnPropertyChanged(e);
if (e.Property == IsAdornerModeProperty)
if (adornerLayer is null)
{
var newValue = e.GetNewValue<bool>();
if (_textLabel is not null)
{
_textLabel.IsVisible = !newValue;
}
}
else if (e.Property == BadgeShadowSizeProperty ||
e.Property == BadgeShadowColorProperty)
{
BuildBoxShadow();
return;
}
adornerLayer.Children.Remove(this);
AdornerLayer.SetAdornedElement(this, adorned);
AdornerLayer.SetIsClipEnabled(this, false);
adornerLayer.Children.Add(this);
_motionCancellationTokenSource?.Cancel();
_motionCancellationTokenSource = new CancellationTokenSource();
ApplyShowMotion();
}
public override void Render(DrawingContext context)
internal void DetachFromTarget(AdornerLayer? adornerLayer)
{
var dotSize = 0d;
if (IsAdornerMode)
if (adornerLayer is null)
{
dotSize = DotSize;
}
else
{
dotSize = StatusSize;
}
var offsetX = 0d;
var offsetY = 0d;
if (IsAdornerMode)
{
offsetX = DesiredSize.Width - dotSize / 2;
offsetY = -dotSize / 2;
offsetX -= Offset.X;
offsetY += Offset.Y;
}
else
{
offsetY = (DesiredSize.Height - dotSize) / 2;
}
var dotRect = new Rect(new Point(offsetX, offsetY), new Size(dotSize, dotSize));
if (RenderTransform is not null)
{
Point origin;
if (AnimationRenderTransformOrigin.HasValue)
{
origin = AnimationRenderTransformOrigin.Value.ToPixels(dotRect.Size);
}
else
{
origin = RenderTransformOrigin.ToPixels(dotRect.Size);
}
var offset = Matrix.CreateTranslation(new Point(origin.X + offsetX, origin.Y + offsetY));
var renderTransform = -offset * RenderTransform.Value * offset;
context.PushTransform(renderTransform);
}
context.DrawRectangle(BadgeDotColor, null, dotRect, dotSize, dotSize, _boxShadows);
}
private void BuildBoxShadow()
{
if (BadgeShadowColor is not null)
{
_boxShadows = new BoxShadows(new BoxShadow
{
OffsetX = 0,
OffsetY = 0,
Blur = 0,
Spread = BadgeShadowSize,
Color = ((SolidColorBrush)BadgeShadowColor).Color
});
return;
}
ApplyHideMotion(() => adornerLayer.Children.Remove(this));
}
}

View File

@ -0,0 +1,105 @@
using AtomUI.MotionScene;
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.Layout;
using Avalonia.Styling;
namespace AtomUI.Controls.Badge;
[ControlThemeProvider]
internal class DotBadgeAdornerTheme : BaseControlTheme
{
internal const string IndicatorMotionActorPart = "PART_IndicatorMotionActor";
internal const string IndicatorPart = "PART_Indicator";
internal const string LabelPart = "PART_Label";
internal const string RootLayoutPart = "PART_RootLayout";
public DotBadgeAdornerTheme()
: base(typeof(DotBadgeAdorner))
{
}
protected override IControlTemplate? BuildControlTemplate()
{
return new FuncControlTemplate<DotBadgeAdorner>((adorner, scope) =>
{
var layout = new DockPanel()
{
Name = RootLayoutPart,
LastChildFill = true,
ClipToBounds = false
};
BuildIndicator(layout, scope);
BuildLabel(layout, scope);
return layout;
});
}
private void BuildIndicator(DockPanel layout, INameScope scope)
{
var indicatorMotionActor = new MotionActorControl()
{
Name = IndicatorMotionActorPart,
ClipToBounds = false,
UseRenderTransform = true
};
indicatorMotionActor.RegisterInNameScope(scope);
var indicator = new DotBadgeIndicator()
{
Name = IndicatorPart
};
indicator.RegisterInNameScope(scope);
DockPanel.SetDock(indicatorMotionActor, Dock.Left);
CreateTemplateParentBinding(indicator, DotBadgeIndicator.BadgeDotColorProperty, DotBadgeAdorner.BadgeDotColorProperty);
indicatorMotionActor.Child = indicator;
layout.Children.Add(indicatorMotionActor);
}
private void BuildLabel(DockPanel layout, INameScope scope)
{
var label = new Label()
{
Name = LabelPart
};
label.RegisterInNameScope(scope);
CreateTemplateParentBinding(label, Label.ContentProperty, DotBadgeAdorner.TextProperty);
CreateTemplateParentBinding(label, Label.IsVisibleProperty, DotBadgeAdorner.IsAdornerModeProperty,
BindingMode.Default,
BoolConverters.Not);
layout.Children.Add(label);
}
protected override void BuildStyles()
{
var commonStyle = new Style(selector => selector.Nesting());
commonStyle.Add(DotBadgeAdorner.ClipToBoundsProperty, false);
commonStyle.Add(DotBadgeAdorner.BadgeDotColorProperty, BadgeTokenResourceKey.BadgeColor);
var inAdornerStyle = new Style(selector => selector.Nesting().PropertyEquals(DotBadgeAdorner.IsAdornerModeProperty, true));
var layoutStyle = new Style(selector => selector.Nesting().Template().Name(RootLayoutPart));
layoutStyle.Add(DockPanel.HorizontalAlignmentProperty, HorizontalAlignment.Right);
layoutStyle.Add(DockPanel.VerticalAlignmentProperty, VerticalAlignment.Top);
inAdornerStyle.Add(layoutStyle);
commonStyle.Add(inAdornerStyle);
var labelStyle = new Style(selector => selector.Nesting().Template().Name(LabelPart));
labelStyle.Add(Label.HorizontalAlignmentProperty, HorizontalAlignment.Left);
labelStyle.Add(Label.VerticalAlignmentProperty, VerticalAlignment.Center);
labelStyle.Add(Label.HorizontalContentAlignmentProperty, HorizontalAlignment.Center);
labelStyle.Add(Label.VerticalContentAlignmentProperty, VerticalAlignment.Center);
labelStyle.Add(Label.MarginProperty, BadgeTokenResourceKey.DotBadgeLabelMargin);
commonStyle.Add(labelStyle);
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(IndicatorMotionActorPart));
indicatorStyle.Add(MotionActorControl.WidthProperty, BadgeTokenResourceKey.DotSize);
indicatorStyle.Add(MotionActorControl.HeightProperty, BadgeTokenResourceKey.DotSize);
commonStyle.Add(indicatorStyle);
Add(commonStyle);
}
}

View File

@ -0,0 +1,90 @@
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
namespace AtomUI.Controls;
internal class DotBadgeIndicator : Control
{
internal static readonly StyledProperty<IBrush?> BadgeDotColorProperty =
AvaloniaProperty.Register<DotBadgeIndicator, IBrush?>(
nameof(BadgeDotColor));
internal static readonly StyledProperty<IBrush?> BadgeShadowColorProperty =
AvaloniaProperty.Register<DotBadgeIndicator, IBrush?>(
nameof(BadgeShadowColor));
internal static readonly StyledProperty<double> BadgeShadowSizeProperty =
AvaloniaProperty.Register<DotBadgeIndicator, double>(
nameof(BadgeShadowSize));
internal IBrush? BadgeDotColor
{
get => GetValue(BadgeDotColorProperty);
set => SetValue(BadgeDotColorProperty, value);
}
internal IBrush? BadgeShadowColor
{
get => GetValue(BadgeShadowColorProperty);
set => SetValue(BadgeShadowColorProperty, value);
}
public double BadgeShadowSize
{
get => GetValue(BadgeShadowSizeProperty);
set => SetValue(BadgeShadowSizeProperty, value);
}
private BoxShadows _boxShadows;
static DotBadgeIndicator()
{
AffectsRender<DotBadgeIndicator>(BadgeDotColorProperty, BadgeShadowSizeProperty);
}
public sealed override void ApplyTemplate()
{
base.ApplyTemplate();
BuildBoxShadow();
TokenResourceBinder.CreateTokenBinding(this, BadgeShadowSizeProperty, BadgeTokenResourceKey.BadgeShadowSize);
TokenResourceBinder.CreateTokenBinding(this, BadgeShadowColorProperty, BadgeTokenResourceKey.BadgeShadowColor);
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == BadgeShadowSizeProperty ||
e.Property == BadgeShadowColorProperty)
{
BuildBoxShadow(true);
}
}
public override void Render(DrawingContext context)
{
context.DrawRectangle(BadgeDotColor, null, new Rect(Bounds.Size), Bounds.Width / 2, Bounds.Height / 2,
_boxShadows);
}
private void BuildBoxShadow(bool force = false)
{
if (_boxShadows == default || force)
{
if (BadgeShadowColor is not null)
{
_boxShadows = new BoxShadows(new BoxShadow
{
OffsetX = 0,
OffsetY = 0,
Blur = 0,
Spread = BadgeShadowSize,
Color = ((SolidColorBrush)BadgeShadowColor).Color
});
}
}
}
}

View File

@ -1,5 +1,5 @@
using AtomUI.Controls.Primitives;
using AtomUI.Controls.Utils;
using AtomUI.Controls.Utils;
using AtomUI.MotionScene;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;

View File

@ -1,5 +1,5 @@
using AtomUI.Controls.Primitives;
using AtomUI.Controls.Utils;
using AtomUI.Controls.Utils;
using AtomUI.MotionScene;
using AtomUI.Theme.Data;
using AtomUI.Theme.Styling;
using AtomUI.Utils;

View File

@ -1,4 +1,5 @@
using AtomUI.Controls.Primitives;
using AtomUI.MotionScene;
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;

View File

@ -8,6 +8,8 @@
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AddOnDecoratedInnerBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AlertTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ArrowDecoratedBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.Badge.CountBadgeAdornerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.Badge.DotBadgeAdornerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ButtonSpinnerInnerBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ButtonSpinnerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.DefaultButtonTheme());

View File

@ -64,6 +64,7 @@ namespace AtomUI.Theme.Styling
public static readonly TokenResourceKey BadgeRibbonCornerTransform = new TokenResourceKey("Badge.BadgeRibbonCornerTransform", "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 DotBadgeLabelMargin = new TokenResourceKey("Badge.DotBadgeLabelMargin", "AtomUI.Token");
}
public static class ButtonSpinnerTokenResourceKey

View File

@ -1,6 +1,5 @@
using AtomUI.Controls.Primitives;
using AtomUI.Controls.Utils;
using AtomUI.Icon;
using AtomUI.Icon;
using AtomUI.MotionScene;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;

View File

@ -1,4 +1,5 @@
using AtomUI.Controls.Primitives;
using AtomUI.MotionScene;
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using AtomUI.Utils;

View File

@ -1,4 +1,5 @@
using AtomUI.Controls.Primitives;
using AtomUI.MotionScene;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using AtomUI.Utils;

View File

@ -1,10 +1,9 @@
using System.Windows.Input;
using AtomUI.Controls.Primitives;
using AtomUI.Controls.Utils;
using AtomUI.Data;
using AtomUI.Icon;
using AtomUI.Input;
using AtomUI.Media;
using AtomUI.MotionScene;
using AtomUI.Theme.Styling;
using AtomUI.Theme.Utils;
using AtomUI.Utils;
@ -21,7 +20,6 @@ using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Media.Transformation;
using Avalonia.Rendering;
using Avalonia.VisualTree;

View File

@ -1,5 +1,4 @@
using AtomUI.Controls.Primitives;
using AtomUI.Controls.Utils;
using AtomUI.MotionScene;
using AtomUI.Theme.Styling;
using AtomUI.Utils;
using Avalonia;
@ -241,7 +240,7 @@ public class NotificationCard : ContentControl
return;
}
MotionConfig? motionConfig;
MotionConfigX? motionConfig;
if (Position == NotificationPosition.TopLeft || Position == NotificationPosition.BottomLeft)
{
motionConfig = MotionFactory.BuildMoveLeftInMotion(AnimationMaxOffsetX, _openCloseMotionDuration, new CubicEaseOut(),
@ -276,7 +275,7 @@ public class NotificationCard : ContentControl
{
return;
}
MotionConfig? motionConfig;
MotionConfigX? motionConfig;
if (Position == NotificationPosition.TopLeft || Position == NotificationPosition.BottomLeft)
{
motionConfig = MotionFactory.BuildMoveLeftOutMotion(AnimationMaxOffsetX, _openCloseMotionDuration, new CubicEaseIn(),

View File

@ -1,5 +1,6 @@
using AtomUI.Controls.Primitives;
using AtomUI.Controls.Utils;
using AtomUI.MotionScene;
using AtomUI.Theme;
using AtomUI.Theme.Styling;
using AtomUI.Utils;

View File

@ -246,9 +246,9 @@ public class Separator : AvaloniaSeparator
if (lineMinWidth > remainWidth)
{
// 字过多
titleWidth = finalSize.Width - lineMinWidth;
titleWidth = Math.Max(finalSize.Width - lineMinWidth, lineMinWidth);
}
// 处理完成之后,字的宽度一定在 width 范围内
// 计算位置
if (TitlePosition == SeparatorTitlePosition.Left)
@ -267,7 +267,7 @@ public class Separator : AvaloniaSeparator
var rightDelta = titleRect.Right - finalSize.Width;
if (MathUtils.GreaterThan(rightDelta, 0))
{
titleRect = titleRect.WithWidth(finalSize.Width - titleRect.Left);
titleRect = titleRect.WithWidth(Math.Max(finalSize.Width - titleRect.Left, lineMinWidth));
}
}
else if (TitlePosition == SeparatorTitlePosition.Right)
@ -297,7 +297,6 @@ public class Separator : AvaloniaSeparator
new Size(titleWidth, finalSize.Height));
}
}
return titleRect;
}

View File

@ -1,4 +1,5 @@
using AtomUI.Controls.Utils;
using AtomUI.MotionScene;
using AtomUI.Theme;
using Avalonia;
using Avalonia.Animation;

View File

@ -1,7 +1,4 @@
using System.Globalization;
using System.Reflection;
using AtomUI.Controls.MotionScene;
using AtomUI.MotionScene;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
@ -279,7 +276,6 @@ internal class ThemeManager : Styles, IThemeManager
internal void Configure()
{
RegisterServices();
RegisterControlThemes();
BuildLanguageResources();
}
@ -330,13 +326,7 @@ internal class ThemeManager : Styles, IThemeManager
{
ControlTokenTypes.Add(tokenType);
}
private void RegisterServices()
{
var motionDirector = new Director();
AvaloniaLocator.CurrentMutable.Bind<IDirector>()
.ToConstant(motionDirector);
}
}
public class ThemeOperateEventArgs : EventArgs