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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Transformation; using Avalonia.Media.Transformation;
namespace AtomUI.Controls.Utils; namespace AtomUI.MotionScene;
internal class MotionTransformOptionsAnimator : InterpolatingAnimator<TransformOperations> 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;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling; using Avalonia.Styling;
namespace AtomUI.Controls.Utils; namespace AtomUI.MotionScene;
internal static partial class MotionFactory internal static partial class MotionFactory
{ {
public static MotionConfig BuildMoveDownInMotion(double offset, public static MotionConfigX BuildMoveDownInMotion(double offset,
TimeSpan duration, TimeSpan duration,
Easing? easing = null, Easing? easing = null,
FillMode fillMode = FillMode.None) FillMode fillMode = FillMode.None)
@ -89,10 +88,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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, TimeSpan duration,
Easing? easing = null, Easing? easing = null,
FillMode fillMode = FillMode.None) FillMode fillMode = FillMode.None)
@ -173,10 +172,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new QuinticEaseOut(); easing ??= new QuinticEaseOut();
@ -254,10 +253,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new QuinticEaseIn(); easing ??= new QuinticEaseIn();
@ -345,10 +344,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new QuinticEaseOut(); easing ??= new QuinticEaseOut();
@ -429,10 +428,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new QuinticEaseIn(); easing ??= new QuinticEaseIn();
@ -509,10 +508,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new QuinticEaseOut(); easing ??= new QuinticEaseOut();
@ -590,10 +589,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new QuinticEaseIn(); easing ??= new QuinticEaseIn();
@ -671,6 +670,6 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0, 0, RelativeUnit.Relative);
animations.Add(animation); 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;
using Avalonia.Animation.Easings; using Avalonia.Animation.Easings;
using Avalonia.Styling; using Avalonia.Styling;
namespace AtomUI.Controls.Utils; namespace AtomUI.MotionScene;
internal static partial class MotionFactory 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new CubicEaseOut(); easing ??= new CubicEaseOut();
@ -63,10 +62,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new CubicEaseIn(); easing ??= new CubicEaseIn();
@ -122,10 +121,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new CubicEaseOut(); easing ??= new CubicEaseOut();
@ -181,10 +180,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(1.0, 1.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(1.0, 1.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new CubicEaseIn(); easing ??= new CubicEaseIn();
@ -240,10 +239,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(1.0, 1.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(1.0, 1.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new CubicEaseOut(); easing ??= new CubicEaseOut();
@ -299,10 +298,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new CubicEaseIn(); easing ??= new CubicEaseIn();
@ -358,10 +357,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(0.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new CubicEaseOut(); easing ??= new CubicEaseOut();
@ -417,10 +416,10 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(1.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(1.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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) FillMode fillMode = FillMode.None)
{ {
easing ??= new CubicEaseIn(); easing ??= new CubicEaseIn();
@ -476,6 +475,6 @@ internal static partial class MotionFactory
transformOrigin = new RelativePoint(1.0, 0.0, RelativeUnit.Relative); transformOrigin = new RelativePoint(1.0, 0.0, RelativeUnit.Relative);
animations.Add(animation); 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 Transform? BadgeRibbonCornerTransform { get; set; }
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; }
#endregion #endregion
@ -90,5 +91,6 @@ internal class BadgeToken : AbstractControlDesignToken
BadgeRibbonCornerTransform = new ScaleTransform(1, 0.75); BadgeRibbonCornerTransform = new ScaleTransform(1, 0.75);
BadgeRibbonCornerDarkenAmount = 15; BadgeRibbonCornerDarkenAmount = 15;
BadgeRibbonTextPadding = new Thickness(_globalToken.PaddingXS, 0); 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); get => GetValue(BadgeIsVisibleProperty);
set => SetValue(BadgeIsVisibleProperty, value); set => SetValue(BadgeIsVisibleProperty, value);
} }
#endregion #endregion
#region #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.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.Utils;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Layout;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
@ -83,38 +79,18 @@ public class DotBadge : Control
get => GetValue(BadgeIsVisibleProperty); get => GetValue(BadgeIsVisibleProperty);
set => SetValue(BadgeIsVisibleProperty, value); set => SetValue(BadgeIsVisibleProperty, value);
} }
#endregion #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 DotBadgeAdorner? _dotBadgeAdorner;
private AdornerLayer? _adornerLayer; private AdornerLayer? _adornerLayer;
private bool _animating;
static DotBadge() static DotBadge()
{ {
AffectsMeasure<DotBadge>(DecoratedTargetProperty, TextProperty); AffectsMeasure<DotBadge>(DecoratedTargetProperty, TextProperty);
AffectsRender<DotBadge>(DotColorProperty, StatusProperty); AffectsRender<DotBadge>(DotColorProperty, StatusProperty);
} HorizontalAlignmentProperty.OverrideDefaultValue<DotBadge>(HorizontalAlignment.Left);
VerticalAlignmentProperty.OverrideDefaultValue<DotBadge>(VerticalAlignment.Top);
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
PrepareAdorner();
} }
private DotBadgeAdorner CreateDotBadgeAdorner() private DotBadgeAdorner CreateDotBadgeAdorner()
@ -135,7 +111,7 @@ public class DotBadge : Control
private void PrepareAdorner() private void PrepareAdorner()
{ {
if (_adornerLayer is null && DecoratedTarget is not null) if (DecoratedTarget is not null)
{ {
var dotBadgeAdorner = CreateDotBadgeAdorner(); var dotBadgeAdorner = CreateDotBadgeAdorner();
_adornerLayer = AdornerLayer.GetAdornerLayer(this); _adornerLayer = AdornerLayer.GetAdornerLayer(this);
@ -145,71 +121,18 @@ public class DotBadge : Control
return; return;
} }
AdornerLayer.SetAdornedElement(dotBadgeAdorner, this); dotBadgeAdorner.ApplyToTarget(_adornerLayer, this);
AdornerLayer.SetIsClipEnabled(dotBadgeAdorner, false);
_adornerLayer.Children.Add(dotBadgeAdorner);
} }
} }
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() private void HideAdorner()
{ {
// 这里需要抛出异常吗? // 这里需要抛出异常吗?
if (_adornerLayer is null || _dotBadgeAdorner is null) if ( _dotBadgeAdorner is null)
{ {
return; return;
} }
_dotBadgeAdorner.DetachFromTarget(_adornerLayer);
_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);
} }
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
@ -217,7 +140,7 @@ public class DotBadge : Control
base.OnDetachedFromVisualTree(e); base.OnDetachedFromVisualTree(e);
HideAdorner(); HideAdorner();
} }
private void SetupTokenBindings() private void SetupTokenBindings()
{ {
if (_dotBadgeAdorner is not null) if (_dotBadgeAdorner is not null)
@ -226,8 +149,6 @@ public class DotBadge : Control
BindUtils.RelayBind(this, TextProperty, _dotBadgeAdorner, DotBadgeAdorner.TextProperty); BindUtils.RelayBind(this, TextProperty, _dotBadgeAdorner, DotBadgeAdorner.TextProperty);
BindUtils.RelayBind(this, OffsetProperty, _dotBadgeAdorner, DotBadgeAdorner.OffsetProperty); BindUtils.RelayBind(this, OffsetProperty, _dotBadgeAdorner, DotBadgeAdorner.OffsetProperty);
} }
TokenResourceBinder.CreateTokenBinding(this, MotionDurationProperty, GlobalTokenResourceKey.MotionDurationSlow);
} }
private void HandleDecoratedTargetChanged() private void HandleDecoratedTargetChanged()
@ -240,7 +161,7 @@ public class DotBadge : Control
((ISetLogicalParent)_dotBadgeAdorner).SetParent(this); ((ISetLogicalParent)_dotBadgeAdorner).SetParent(this);
VisualChildren.Add(_dotBadgeAdorner); VisualChildren.Add(_dotBadgeAdorner);
} }
else if (DecoratedTarget is not null) else
{ {
_dotBadgeAdorner.IsAdornerMode = true; _dotBadgeAdorner.IsAdornerMode = true;
VisualChildren.Add(DecoratedTarget); VisualChildren.Add(DecoratedTarget);
@ -256,6 +177,10 @@ public class DotBadge : Control
{ {
CreateDotBadgeAdorner(); CreateDotBadgeAdorner();
} }
else
{
PrepareAdorner();
}
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
@ -280,37 +205,17 @@ public class DotBadge : Control
} }
else if (e.Property == BadgeIsVisibleProperty) else if (e.Property == BadgeIsVisibleProperty)
{ {
var badgeIsVisible = e.GetNewValue<bool>(); if (BadgeIsVisible)
if (badgeIsVisible)
{ {
if (_adornerLayer is not null) PrepareAdorner();
{
return;
}
if (DecoratedTarget is not null)
{
PrepareAdornerWithMotion();
}
else
{
PrepareAdorner();
}
} }
else else
{ {
if (DecoratedTarget is not null) HideAdorner();
{
HideAdornerWithMotion();
}
else
{
HideAdorner();
}
} }
} }
if (_initialized) if (VisualRoot is not null)
{ {
if (e.Property == DecoratedTargetProperty) if (e.Property == DecoratedTargetProperty)
{ {
@ -342,4 +247,5 @@ public class DotBadge : Control
_dotBadgeAdorner!.BadgeDotColor = new SolidColorBrush(color); _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;
using Avalonia.Animation;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Layout; using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Styling;
namespace AtomUI.Controls; 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 = public static readonly DirectProperty<DotBadgeAdorner, DotBadgeStatus?> StatusProperty =
AvaloniaProperty.RegisterDirect<DotBadgeAdorner, DotBadgeStatus?>( AvaloniaProperty.RegisterDirect<DotBadgeAdorner, DotBadgeStatus?>(
nameof(Status), nameof(Status),
o => o.Status, o => o.Status,
(o, v) => o.Status = v); (o, v) => o.Status = v);
internal IBrush? BadgeDotColor
{
get => GetValue(BadgeDotColorProperty);
set => SetValue(BadgeDotColorProperty, value);
}
private DotBadgeStatus? _status; private DotBadgeStatus? _status;
@ -44,30 +56,6 @@ internal class DotBadgeAdorner : Control
o => o.IsAdornerMode, o => o.IsAdornerMode,
(o, v) => o.IsAdornerMode = v); (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 = public static readonly StyledProperty<Point> OffsetProperty =
AvaloniaProperty.Register<DotBadgeAdorner, Point>( AvaloniaProperty.Register<DotBadgeAdorner, Point>(
nameof(Offset)); nameof(Offset));
@ -85,256 +73,151 @@ internal class DotBadgeAdorner : Control
get => GetValue(OffsetProperty); get => GetValue(OffsetProperty);
set => SetValue(OffsetProperty, value); 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); get => GetValue(MotionDurationProperty);
set => SetValue(DotSizeProperty, value); set => SetValue(MotionDurationProperty, value);
} }
public double StatusSize #endregion
{
get => GetValue(StatusSizeProperty); private MotionActorControl? _indicatorMotionActor;
set => SetValue(StatusSizeProperty, value); private CancellationTokenSource? _motionCancellationTokenSource;
}
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;
static DotBadgeAdorner() static DotBadgeAdorner()
{ {
AffectsMeasure<DotBadge>(TextProperty, IsAdornerModeProperty); 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 (_indicatorMotionActor is not null)
if (!_initialized)
{ {
_textLabel = new Label var zoomBadgeOutMotionConfig = BadgeMotionFactory.BuildBadgeZoomBadgeOutMotion(MotionDuration, null,
FillMode.Forward);
_motionCancellationTokenSource?.Cancel();
_motionCancellationTokenSource = new CancellationTokenSource();
MotionInvoker.Invoke(_indicatorMotionActor, zoomBadgeOutMotionConfig, null, () =>
{ {
Content = Text, completedAction();
HorizontalAlignment = HorizontalAlignment.Left, }, _motionCancellationTokenSource.Token);
VerticalAlignment = VerticalAlignment.Center,
HorizontalContentAlignment = HorizontalAlignment.Center,
VerticalContentAlignment = VerticalAlignment.Center,
Padding = new Thickness(0)
};
((ISetLogicalParent)_textLabel).SetParent(this);
VisualChildren.Add(_textLabel);
BuildBoxShadow();
_initialized = true;
} }
} }
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{ {
base.OnAttachedToLogicalTree(e); base.OnPropertyChanged(change);
BuildStyles(); if (VisualRoot is not null)
}
private void BuildStyles()
{
if (Styles.Count == 0)
{ {
BuildBadgeColorStyle(); if (change.Property == StatusProperty)
}
}
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)
{ {
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) protected override Size ArrangeOverride(Size finalSize)
{ {
if (!IsAdornerMode) var size = base.ArrangeOverride(finalSize);
if (IsAdornerMode && _indicatorMotionActor is not null)
{ {
double textOffsetX = 0; var offsetX = Offset.X;
if (IsAdornerMode) var offsetY = Offset.Y;
{ var dotSize = _indicatorMotionActor.Bounds.Size;
textOffsetX += DotSize; offsetX += dotSize.Width / 3;
} offsetY += dotSize.Height / 3;
else _indicatorMotionActor.Arrange(new Rect(new Point(offsetX, -offsetY), dotSize));
{
textOffsetX += StatusSize;
}
textOffsetX += BadgeTextMarginInline;
var textRect = new Rect(new Point(textOffsetX, 0), _textLabel!.DesiredSize);
_textLabel.Arrange(textRect);
} }
return size;
return finalSize;
} }
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e) internal void ApplyToTarget(AdornerLayer? adornerLayer, Control adorned)
{ {
base.OnPropertyChanged(e); if (adornerLayer is null)
if (e.Property == IsAdornerModeProperty)
{ {
var newValue = e.GetNewValue<bool>(); return;
if (_textLabel is not null)
{
_textLabel.IsVisible = !newValue;
}
}
else if (e.Property == BadgeShadowSizeProperty ||
e.Property == BadgeShadowColorProperty)
{
BuildBoxShadow();
} }
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 (adornerLayer is null)
if (IsAdornerMode)
{ {
dotSize = DotSize; return;
}
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
});
} }
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.Theme.Styling;
using AtomUI.Utils; using AtomUI.Utils;
using Avalonia; 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.Data;
using AtomUI.Theme.Styling; using AtomUI.Theme.Styling;
using AtomUI.Utils; using AtomUI.Utils;

View File

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

View File

@ -8,6 +8,8 @@
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AddOnDecoratedInnerBoxTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AddOnDecoratedInnerBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AlertTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.AlertTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ArrowDecoratedBoxTheme()); 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.ButtonSpinnerInnerBoxTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ButtonSpinnerTheme()); ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.ButtonSpinnerTheme());
ThemeManager.Current.RegisterControlTheme(new AtomUI.Controls.DefaultButtonTheme()); 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 BadgeRibbonCornerTransform = new TokenResourceKey("Badge.BadgeRibbonCornerTransform", "AtomUI.Token");
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 class ButtonSpinnerTokenResourceKey public static class ButtonSpinnerTokenResourceKey

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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