diff --git a/src/AtomUI.Controls/Utils/Motion/CollapseMotionFactory.cs b/src/AtomUI.Base/MotionScene/CollapseMotionFactory.cs similarity index 93% rename from src/AtomUI.Controls/Utils/Motion/CollapseMotionFactory.cs rename to src/AtomUI.Base/MotionScene/CollapseMotionFactory.cs index 4939e49..1ebe96a 100644 --- a/src/AtomUI.Controls/Utils/Motion/CollapseMotionFactory.cs +++ b/src/AtomUI.Base/MotionScene/CollapseMotionFactory.cs @@ -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); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Primitives/MotionActorControl.cs b/src/AtomUI.Base/MotionScene/MotionActorControl.cs similarity index 95% rename from src/AtomUI.Controls/Primitives/MotionActorControl.cs rename to src/AtomUI.Base/MotionScene/MotionActorControl.cs index 1dd88e7..ff10357 100644 --- a/src/AtomUI.Controls/Primitives/MotionActorControl.cs +++ b/src/AtomUI.Base/MotionScene/MotionActorControl.cs @@ -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 MotionTransformProperty = AvaloniaProperty.Register(nameof(MotionTransform)); + + public static readonly StyledProperty UseRenderTransformProperty = + AvaloniaProperty.Register(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 diff --git a/src/AtomUI.Controls/Utils/Motion/MotionFactory.cs b/src/AtomUI.Base/MotionScene/MotionFactory.cs similarity index 60% rename from src/AtomUI.Controls/Utils/Motion/MotionFactory.cs rename to src/AtomUI.Base/MotionScene/MotionFactory.cs index 497254e..935995d 100644 --- a/src/AtomUI.Controls/Utils/Motion/MotionFactory.cs +++ b/src/AtomUI.Base/MotionScene/MotionFactory.cs @@ -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 Animations { get; } - public MotionConfig(RelativePoint renderTransformOrigin, IList animations) + public MotionConfigX(RelativePoint renderTransformOrigin, IList 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); diff --git a/src/AtomUI.Controls/Utils/Motion/MotionInvoker.cs b/src/AtomUI.Base/MotionScene/MotionInvoker.cs similarity index 77% rename from src/AtomUI.Controls/Utils/Motion/MotionInvoker.cs rename to src/AtomUI.Base/MotionScene/MotionInvoker.cs index 2b6bc94..7d2b579 100644 --- a/src/AtomUI.Controls/Utils/Motion/MotionInvoker.cs +++ b/src/AtomUI.Base/MotionScene/MotionInvoker.cs @@ -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) diff --git a/src/AtomUI.Controls/Utils/Motion/MotionTransformOptionsAnimator.cs b/src/AtomUI.Base/MotionScene/MotionTransformOptionsAnimator.cs similarity index 95% rename from src/AtomUI.Controls/Utils/Motion/MotionTransformOptionsAnimator.cs rename to src/AtomUI.Base/MotionScene/MotionTransformOptionsAnimator.cs index 3dde5bb..2299eb4 100644 --- a/src/AtomUI.Controls/Utils/Motion/MotionTransformOptionsAnimator.cs +++ b/src/AtomUI.Base/MotionScene/MotionTransformOptionsAnimator.cs @@ -2,7 +2,7 @@ using Avalonia.Media; using Avalonia.Media.Transformation; -namespace AtomUI.Controls.Utils; +namespace AtomUI.MotionScene; internal class MotionTransformOptionsAnimator : InterpolatingAnimator { diff --git a/src/AtomUI.Controls/Utils/Motion/MoveMotionFactory.cs b/src/AtomUI.Base/MotionScene/MoveMotionFactory.cs similarity index 94% rename from src/AtomUI.Controls/Utils/Motion/MoveMotionFactory.cs rename to src/AtomUI.Base/MotionScene/MoveMotionFactory.cs index ee4ff66..fec1766 100644 --- a/src/AtomUI.Controls/Utils/Motion/MoveMotionFactory.cs +++ b/src/AtomUI.Base/MotionScene/MoveMotionFactory.cs @@ -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); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Utils/Motion/SlideMotionFactory.cs b/src/AtomUI.Base/MotionScene/SlideMotionFactory.cs similarity index 92% rename from src/AtomUI.Controls/Utils/Motion/SlideMotionFactory.cs rename to src/AtomUI.Base/MotionScene/SlideMotionFactory.cs index 29796bd..2eefc49 100644 --- a/src/AtomUI.Controls/Utils/Motion/SlideMotionFactory.cs +++ b/src/AtomUI.Base/MotionScene/SlideMotionFactory.cs @@ -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); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Badge/BadgeMotionFactory.cs b/src/AtomUI.Controls/Badge/BadgeMotionFactory.cs new file mode 100644 index 0000000..c174116 --- /dev/null +++ b/src/AtomUI.Controls/Badge/BadgeMotionFactory.cs @@ -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(); + 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(); + 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(); + 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(); + 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); + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Badge/BadgeToken.cs b/src/AtomUI.Controls/Badge/BadgeToken.cs index 0597347..4a93d0b 100644 --- a/src/AtomUI.Controls/Badge/BadgeToken.cs +++ b/src/AtomUI.Controls/Badge/BadgeToken.cs @@ -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); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Badge/CountBadge.cs b/src/AtomUI.Controls/Badge/CountBadge.cs index 4e24ddc..88678f8 100644 --- a/src/AtomUI.Controls/Badge/CountBadge.cs +++ b/src/AtomUI.Controls/Badge/CountBadge.cs @@ -98,9 +98,7 @@ public class CountBadge : Control get => GetValue(BadgeIsVisibleProperty); set => SetValue(BadgeIsVisibleProperty, value); } - - - + #endregion #region 内部属性定义 diff --git a/src/AtomUI.Controls/Badge/CountBadgeAdornerTheme.cs b/src/AtomUI.Controls/Badge/CountBadgeAdornerTheme.cs new file mode 100644 index 0000000..f925db4 --- /dev/null +++ b/src/AtomUI.Controls/Badge/CountBadgeAdornerTheme.cs @@ -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)) + { + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Badge/DotBadge.cs b/src/AtomUI.Controls/Badge/DotBadge.cs index a0553d7..69783ed 100644 --- a/src/AtomUI.Controls/Badge/DotBadge.cs +++ b/src/AtomUI.Controls/Badge/DotBadge.cs @@ -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 MotionDurationProperty = - AvaloniaProperty.Register( - 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(DecoratedTargetProperty, TextProperty); AffectsRender(DotColorProperty, StatusProperty); - } - - protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) - { - base.OnAttachedToVisualTree(e); - PrepareAdorner(); + HorizontalAlignmentProperty.OverrideDefaultValue(HorizontalAlignment.Left); + VerticalAlignmentProperty.OverrideDefaultValue(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(); - 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); } } + } \ No newline at end of file diff --git a/src/AtomUI.Controls/Badge/DotBadgeAdorner.cs b/src/AtomUI.Controls/Badge/DotBadgeAdorner.cs index ecb9761..5b40466 100644 --- a/src/AtomUI.Controls/Badge/DotBadgeAdorner.cs +++ b/src/AtomUI.Controls/Badge/DotBadgeAdorner.cs @@ -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 BadgeDotColorProperty = + AvaloniaProperty.Register( + nameof(BadgeDotColor)); + public static readonly DirectProperty StatusProperty = AvaloniaProperty.RegisterDirect( 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 BadgeDotColorProperty = - AvaloniaProperty.Register( - nameof(BadgeDotColor)); - - internal static readonly StyledProperty DotSizeProperty = - AvaloniaProperty.Register( - nameof(DotSize)); - - internal static readonly StyledProperty StatusSizeProperty = - AvaloniaProperty.Register( - nameof(StatusSize)); - - internal static readonly StyledProperty BadgeShadowColorProperty = - AvaloniaProperty.Register( - nameof(BadgeShadowColor)); - - private static readonly StyledProperty BadgeShadowSizeProperty = - AvaloniaProperty.Register( - nameof(BadgeShadowSize)); - - private static readonly StyledProperty BadgeTextMarginInlineProperty = - AvaloniaProperty.Register( - nameof(BadgeTextMarginInline)); - public static readonly StyledProperty OffsetProperty = AvaloniaProperty.Register( 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 MotionDurationProperty = + AvaloniaProperty.Register( + 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(TextProperty, IsAdornerModeProperty); - AffectsRender(BadgeDotColorProperty, OffsetProperty); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + TokenResourceBinder.CreateTokenBinding(this, MotionDurationProperty, GlobalTokenResourceKey.MotionDurationMid); + SetupBadgeColor(); + _indicatorMotionActor = e.NameScope.Get(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()); - 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(); - 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)); } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Badge/DotBadgeAdornerTheme.cs b/src/AtomUI.Controls/Badge/DotBadgeAdornerTheme.cs new file mode 100644 index 0000000..1fceee2 --- /dev/null +++ b/src/AtomUI.Controls/Badge/DotBadgeAdornerTheme.cs @@ -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((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); + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Badge/DotBadgeIndicator.cs b/src/AtomUI.Controls/Badge/DotBadgeIndicator.cs new file mode 100644 index 0000000..1eda028 --- /dev/null +++ b/src/AtomUI.Controls/Badge/DotBadgeIndicator.cs @@ -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 BadgeDotColorProperty = + AvaloniaProperty.Register( + nameof(BadgeDotColor)); + + internal static readonly StyledProperty BadgeShadowColorProperty = + AvaloniaProperty.Register( + nameof(BadgeShadowColor)); + + internal static readonly StyledProperty BadgeShadowSizeProperty = + AvaloniaProperty.Register( + 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(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 + }); + } + } + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Collapse/CollapseItem.cs b/src/AtomUI.Controls/Collapse/CollapseItem.cs index 88e0ba6..3a3e9eb 100644 --- a/src/AtomUI.Controls/Collapse/CollapseItem.cs +++ b/src/AtomUI.Controls/Collapse/CollapseItem.cs @@ -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; diff --git a/src/AtomUI.Controls/Expander/Expander.cs b/src/AtomUI.Controls/Expander/Expander.cs index 8338c5e..e125faa 100644 --- a/src/AtomUI.Controls/Expander/Expander.cs +++ b/src/AtomUI.Controls/Expander/Expander.cs @@ -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; diff --git a/src/AtomUI.Controls/Expander/ExpanderTheme.cs b/src/AtomUI.Controls/Expander/ExpanderTheme.cs index bb9ac5b..932453e 100644 --- a/src/AtomUI.Controls/Expander/ExpanderTheme.cs +++ b/src/AtomUI.Controls/Expander/ExpanderTheme.cs @@ -1,4 +1,5 @@ using AtomUI.Controls.Primitives; +using AtomUI.MotionScene; using AtomUI.Theme; using AtomUI.Theme.Styling; using AtomUI.Theme.Utils; diff --git a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.ControlThemeRegisterGenerator/ControlThemeRegister.g.cs b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.ControlThemeRegisterGenerator/ControlThemeRegister.g.cs index 1404804..99eb1f3 100644 --- a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.ControlThemeRegisterGenerator/ControlThemeRegister.g.cs +++ b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.ControlThemeRegisterGenerator/ControlThemeRegister.g.cs @@ -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()); diff --git a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs index 1acb49b..caf4ebe 100644 --- a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs +++ b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs @@ -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 diff --git a/src/AtomUI.Controls/Message/MessageCard.cs b/src/AtomUI.Controls/Message/MessageCard.cs index 8d0f983..8f58b1f 100644 --- a/src/AtomUI.Controls/Message/MessageCard.cs +++ b/src/AtomUI.Controls/Message/MessageCard.cs @@ -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; diff --git a/src/AtomUI.Controls/Message/MessageCardTheme.cs b/src/AtomUI.Controls/Message/MessageCardTheme.cs index 15e777a..feacabc 100644 --- a/src/AtomUI.Controls/Message/MessageCardTheme.cs +++ b/src/AtomUI.Controls/Message/MessageCardTheme.cs @@ -1,4 +1,5 @@ using AtomUI.Controls.Primitives; +using AtomUI.MotionScene; using AtomUI.Theme; using AtomUI.Theme.Styling; using AtomUI.Utils; diff --git a/src/AtomUI.Controls/NavMenu/InlineNavMenuItemTheme.cs b/src/AtomUI.Controls/NavMenu/InlineNavMenuItemTheme.cs index 90aade4..8a50680 100644 --- a/src/AtomUI.Controls/NavMenu/InlineNavMenuItemTheme.cs +++ b/src/AtomUI.Controls/NavMenu/InlineNavMenuItemTheme.cs @@ -1,4 +1,5 @@ using AtomUI.Controls.Primitives; +using AtomUI.MotionScene; using AtomUI.Theme.Styling; using AtomUI.Theme.Utils; using AtomUI.Utils; diff --git a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs index 12f1a76..e361d65 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs @@ -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; diff --git a/src/AtomUI.Controls/Notifications/NotificationCard.cs b/src/AtomUI.Controls/Notifications/NotificationCard.cs index 7dec147..503fc40 100644 --- a/src/AtomUI.Controls/Notifications/NotificationCard.cs +++ b/src/AtomUI.Controls/Notifications/NotificationCard.cs @@ -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(), diff --git a/src/AtomUI.Controls/Notifications/NotificationCardTheme.cs b/src/AtomUI.Controls/Notifications/NotificationCardTheme.cs index 92215c0..2717a2e 100644 --- a/src/AtomUI.Controls/Notifications/NotificationCardTheme.cs +++ b/src/AtomUI.Controls/Notifications/NotificationCardTheme.cs @@ -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; diff --git a/src/AtomUI.Controls/Separator/Separator.cs b/src/AtomUI.Controls/Separator/Separator.cs index 50aff6b..9e1c841 100644 --- a/src/AtomUI.Controls/Separator/Separator.cs +++ b/src/AtomUI.Controls/Separator/Separator.cs @@ -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; } diff --git a/src/AtomUI.Controls/ThemeManagerExtensions.cs b/src/AtomUI.Controls/ThemeManagerExtensions.cs index 44cc406..181aa0b 100644 --- a/src/AtomUI.Controls/ThemeManagerExtensions.cs +++ b/src/AtomUI.Controls/ThemeManagerExtensions.cs @@ -1,4 +1,5 @@ using AtomUI.Controls.Utils; +using AtomUI.MotionScene; using AtomUI.Theme; using Avalonia; using Avalonia.Animation; diff --git a/src/AtomUI.Theme/ThemeManager.cs b/src/AtomUI.Theme/ThemeManager.cs index e034c61..c465e7c 100644 --- a/src/AtomUI.Theme/ThemeManager.cs +++ b/src/AtomUI.Theme/ThemeManager.cs @@ -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() - .ToConstant(motionDirector); - } + } public class ThemeOperateEventArgs : EventArgs