From 9ba4534f2681f57abe647115cacd96f4fa3910fc Mon Sep 17 00:00:00 2001 From: polarboy Date: Sun, 29 Sep 2024 19:43:32 +0800 Subject: [PATCH] Add Motion for Expander Control --- src/AtomUI.Controls/Collapse/CollapseItem.cs | 1 - src/AtomUI.Controls/Expander/Expander.cs | 104 +++++++++++++++--- src/AtomUI.Controls/Expander/ExpanderTheme.cs | 16 +-- src/AtomUI.Controls/Message/MessageCard.cs | 2 - src/AtomUI.Controls/NavMenu/NavMenuItem.cs | 1 - .../Notifications/NotificationCard.cs | 2 - .../Primitives/MotionActorControl.cs | 5 +- .../Utils/Motion/CollapseMotionFactory.cs | 24 ++-- .../Utils/Motion/SlideMotionFactory.cs | 2 - 9 files changed, 115 insertions(+), 42 deletions(-) diff --git a/src/AtomUI.Controls/Collapse/CollapseItem.cs b/src/AtomUI.Controls/Collapse/CollapseItem.cs index 6ec9226c7..88e0ba6c2 100644 --- a/src/AtomUI.Controls/Collapse/CollapseItem.cs +++ b/src/AtomUI.Controls/Collapse/CollapseItem.cs @@ -310,7 +310,6 @@ public class CollapseItem : HeaderedContentControl, ISelectable InAnimating = true; var slideDownInMotionConfig = MotionFactory.BuildSlideUpInMotion(MotionDuration, new CubicEaseOut(), FillMode.Forward); - _motionActor.RenderTransformOrigin = slideDownInMotionConfig.RenderTransformOrigin; MotionInvoker.Invoke(_motionActor, slideDownInMotionConfig, () => { _motionActor.SetCurrentValue(IsVisibleProperty, true); diff --git a/src/AtomUI.Controls/Expander/Expander.cs b/src/AtomUI.Controls/Expander/Expander.cs index bec9c7163..8338c5e80 100644 --- a/src/AtomUI.Controls/Expander/Expander.cs +++ b/src/AtomUI.Controls/Expander/Expander.cs @@ -1,9 +1,11 @@ -using AtomUI.Controls.Utils; -using AtomUI.MotionScene; +using AtomUI.Controls.Primitives; +using AtomUI.Controls.Utils; using AtomUI.Theme.Data; using AtomUI.Theme.Styling; using AtomUI.Utils; using Avalonia; +using Avalonia.Animation; +using Avalonia.Animation.Easings; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; @@ -162,14 +164,16 @@ public class Expander : AvaloniaExpander #endregion - private AnimationTargetPanel? _animationTarget; + private MotionActorControl? _motionActor; private Border? _headerDecorator; private IconButton? _expandButton; + private bool _animating; + private bool _enableAnimation = true; protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _animationTarget = e.NameScope.Find(ExpanderTheme.ContentAnimationTargetPart); + _motionActor = e.NameScope.Find(ExpanderTheme.ContentMotionActorPart); _headerDecorator = e.NameScope.Find(ExpanderTheme.HeaderDecoratorPart); _expandButton = e.NameScope.Find(ExpanderTheme.ExpandButtonPart); TokenResourceBinder.CreateTokenBinding(this, MotionDurationProperty, GlobalTokenResourceKey.MotionDurationSlow); @@ -179,10 +183,19 @@ public class Expander : AvaloniaExpander SetupEffectiveBorderThickness(); SetupExpanderBorderThickness(); SetupIconButton(); + _enableAnimation = false; HandleExpandedChanged(); + _enableAnimation = true; if (_expandButton is not null) { - _expandButton.Click += (sender, args) => { IsExpanded = !IsExpanded; }; + _expandButton.Click += (sender, args) => + { + if (_animating) + { + return; + } + IsExpanded = !IsExpanded; + }; } } @@ -277,6 +290,10 @@ public class Expander : AvaloniaExpander var targetRect = new Rect(_headerDecorator.Bounds.Size); if (targetRect.Contains(position)) { + if (_animating) + { + return; + } IsExpanded = !IsExpanded; } } @@ -285,17 +302,76 @@ public class Expander : AvaloniaExpander private void HandleExpandedChanged() { - if (_animationTarget is not null) + if (IsExpanded) { - if (IsExpanded) - { - _animationTarget.IsVisible = true; - } - else - { - _animationTarget.IsVisible = false; - } + ExpandItemContent(); } + else + { + CollapseItemContent(); + } + } + + private void ExpandItemContent() + { + if (_motionActor is null || _animating) + { + return; + } + + if (!_enableAnimation) + { + _motionActor.IsVisible = true; + return; + } + + _animating = true; + var expandMotionConfig = MotionFactory.BuildExpandMotion(DirectionFromExpandDirection(ExpandDirection), + MotionDuration, + new CubicEaseOut(), + FillMode.Forward); + MotionInvoker.Invoke(_motionActor, expandMotionConfig, () => + { + _motionActor.SetCurrentValue(IsVisibleProperty, true); + }, () => + { + _animating = false; + }); + } + + private void CollapseItemContent() + { + if (_motionActor is null || _animating) + { + return; + } + if (!_enableAnimation) + { + _motionActor.IsVisible = false; + return; + } + + _animating = true; + var slideDownOutMotionConfig = MotionFactory.BuildCollapseMotion(DirectionFromExpandDirection(ExpandDirection), + MotionDuration, + new CubicEaseIn(), + FillMode.Forward); + MotionInvoker.Invoke(_motionActor, slideDownOutMotionConfig, null, () => + { + _motionActor.SetCurrentValue(IsVisibleProperty, false); + _animating = false; + }); + } + + private static Direction DirectionFromExpandDirection(ExpandDirection expandDirection) + { + return expandDirection switch + { + ExpandDirection.Left => Direction.Left, + ExpandDirection.Up => Direction.Top, + ExpandDirection.Right => Direction.Right, + ExpandDirection.Down => Direction.Bottom, + }; } private void SetupEffectiveBorderThickness() diff --git a/src/AtomUI.Controls/Expander/ExpanderTheme.cs b/src/AtomUI.Controls/Expander/ExpanderTheme.cs index 8b811122f..bb9ac5b51 100644 --- a/src/AtomUI.Controls/Expander/ExpanderTheme.cs +++ b/src/AtomUI.Controls/Expander/ExpanderTheme.cs @@ -1,4 +1,4 @@ -using AtomUI.MotionScene; +using AtomUI.Controls.Primitives; using AtomUI.Theme; using AtomUI.Theme.Styling; using AtomUI.Theme.Utils; @@ -30,7 +30,7 @@ internal class ExpanderTheme : BaseControlTheme public const string HeaderDecoratorPart = "PART_HeaderDecorator"; public const string ContentPresenterPart = "PART_ContentPresenter"; public const string AddOnContentPresenterPart = "PART_AddOnContentPresenter"; - public const string ContentAnimationTargetPart = "PART_ContentAnimationTarget"; + public const string ContentMotionActorPart = "PART_ContentMotionActor"; public ExpanderTheme() : base(typeof(Expander)) { @@ -56,17 +56,17 @@ internal class ExpanderTheme : BaseControlTheme }; BuildHeader(mainLayout, scope); - var animationPanel = new AnimationTargetPanel + var motionActor = new MotionActorControl() { - Name = ContentAnimationTargetPart, + Name = ContentMotionActorPart, ClipToBounds = true }; var contentPresenter = new ContentPresenter { Name = ContentPresenterPart }; - animationPanel.SetCurrentValue(Visual.IsVisibleProperty, false); - animationPanel.Children.Add(contentPresenter); + motionActor.SetCurrentValue(Visual.IsVisibleProperty, false); + motionActor.Child = contentPresenter; TokenResourceBinder.CreateGlobalTokenBinding(contentPresenter, ContentPresenter.BorderBrushProperty, GlobalTokenResourceKey.ColorBorder); CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty, @@ -74,8 +74,8 @@ internal class ExpanderTheme : BaseControlTheme CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty, ContentControl.ContentTemplateProperty); - mainLayout.Children.Add(animationPanel); - animationPanel.RegisterInNameScope(scope); + mainLayout.Children.Add(motionActor); + motionActor.RegisterInNameScope(scope); contentPresenter.RegisterInNameScope(scope); frameDecorator.Child = mainLayout; diff --git a/src/AtomUI.Controls/Message/MessageCard.cs b/src/AtomUI.Controls/Message/MessageCard.cs index 960033778..8d0f98303 100644 --- a/src/AtomUI.Controls/Message/MessageCard.cs +++ b/src/AtomUI.Controls/Message/MessageCard.cs @@ -201,7 +201,6 @@ public class MessageCard : TemplatedControl _motionActor.IsVisible = false; var moveUpInMotionConfig = MotionFactory.BuildMoveUpInMotion(AnimationMaxOffsetY, _openCloseMotionDuration, new CubicEaseOut(), FillMode.Forward); - _motionActor.RenderTransformOrigin = moveUpInMotionConfig.RenderTransformOrigin; MotionInvoker.Invoke(_motionActor, moveUpInMotionConfig, () => { _motionActor.IsVisible = true; @@ -215,7 +214,6 @@ public class MessageCard : TemplatedControl { var moveUpOutMotionConfig = MotionFactory.BuildMoveUpOutMotion(AnimationMaxOffsetY, _openCloseMotionDuration, new CubicEaseIn(), FillMode.Forward); - _motionActor.RenderTransformOrigin = moveUpOutMotionConfig.RenderTransformOrigin; MotionInvoker.Invoke(_motionActor, moveUpOutMotionConfig, null, () => { IsClosed = true; diff --git a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs index d8d636a5d..12f1a766e 100644 --- a/src/AtomUI.Controls/NavMenu/NavMenuItem.cs +++ b/src/AtomUI.Controls/NavMenu/NavMenuItem.cs @@ -1080,7 +1080,6 @@ public class NavMenuItem : HeaderedSelectingItemsControl, _animating = true; var slideDownInMotionConfig = MotionFactory.BuildSlideUpInMotion(_openCloseMotionDuration, new CubicEaseOut(), FillMode.Forward); - _childItemsLayoutTransform.RenderTransformOrigin = slideDownInMotionConfig.RenderTransformOrigin; MotionInvoker.Invoke(_childItemsLayoutTransform, slideDownInMotionConfig, () => { _childItemsLayoutTransform.SetCurrentValue(IsVisibleProperty, true); diff --git a/src/AtomUI.Controls/Notifications/NotificationCard.cs b/src/AtomUI.Controls/Notifications/NotificationCard.cs index 9ec2c482f..7dec1479b 100644 --- a/src/AtomUI.Controls/Notifications/NotificationCard.cs +++ b/src/AtomUI.Controls/Notifications/NotificationCard.cs @@ -264,7 +264,6 @@ public class NotificationCard : ContentControl } _motionActor.IsVisible = false; - _motionActor.RenderTransformOrigin = motionConfig.RenderTransformOrigin; MotionInvoker.Invoke(_motionActor, motionConfig, () => { _motionActor.IsVisible = true; @@ -299,7 +298,6 @@ public class NotificationCard : ContentControl FillMode.Forward); } - _motionActor.RenderTransformOrigin = motionConfig.RenderTransformOrigin; MotionInvoker.Invoke(_motionActor, motionConfig, null, () => { IsClosed = true; diff --git a/src/AtomUI.Controls/Primitives/MotionActorControl.cs b/src/AtomUI.Controls/Primitives/MotionActorControl.cs index 149e2d592..1dd88e7d5 100644 --- a/src/AtomUI.Controls/Primitives/MotionActorControl.cs +++ b/src/AtomUI.Controls/Primitives/MotionActorControl.cs @@ -215,7 +215,10 @@ public class MotionActorControl : Decorator } // Perform a measure on the MotionTransformRoot (containing Child) - MotionTransformRoot.Measure(measureSize); + if (MotionTransformRoot.DesiredSize == default) + { + MotionTransformRoot.Measure(measureSize); + } var desiredSize = MotionTransformRoot.DesiredSize; diff --git a/src/AtomUI.Controls/Utils/Motion/CollapseMotionFactory.cs b/src/AtomUI.Controls/Utils/Motion/CollapseMotionFactory.cs index b44f6307f..4939e4902 100644 --- a/src/AtomUI.Controls/Utils/Motion/CollapseMotionFactory.cs +++ b/src/AtomUI.Controls/Utils/Motion/CollapseMotionFactory.cs @@ -2,7 +2,6 @@ using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; -using Avalonia.Media; using Avalonia.Styling; namespace AtomUI.Controls.Utils; @@ -12,7 +11,7 @@ internal static partial class MotionFactory public static MotionConfig BuildCollapseMotion(Direction direction, TimeSpan duration, Easing? easing = null, FillMode fillMode = FillMode.None) { - easing ??= new CircularEaseOut(); + easing ??= new CubicEaseOut(); var animations = new List(); RelativePoint transformOrigin = default; var isHorizontal = direction == Direction.Left || direction == Direction.Right; @@ -85,22 +84,23 @@ internal static partial class MotionFactory endFrame.Setters.Add(scaleYSetter); } } + animation.Children.Add(endFrame); if (direction == Direction.Left) { - transformOrigin = new RelativePoint(0, 0.5, RelativeUnit.Relative); + transformOrigin = new RelativePoint(1, 0.5, RelativeUnit.Relative); } else if (direction == Direction.Right) { - transformOrigin = new RelativePoint(1, 0.5, RelativeUnit.Relative); + transformOrigin = new RelativePoint(0, 0.5, RelativeUnit.Relative); } else if (direction == Direction.Top) { - transformOrigin = new RelativePoint(0.5, 0, RelativeUnit.Relative); + transformOrigin = new RelativePoint(0.5, 1.0, RelativeUnit.Relative); } else { - transformOrigin = new RelativePoint(0.5, 1, RelativeUnit.Relative); + transformOrigin = new RelativePoint(0.5, 0.0, RelativeUnit.Relative); } animations.Add(animation); @@ -110,7 +110,7 @@ internal static partial class MotionFactory public static MotionConfig BuildExpandMotion(Direction direction, TimeSpan duration, Easing? easing = null, FillMode fillMode = FillMode.None) { - easing ??= new CircularEaseOut(); + easing ??= new CubicEaseIn(); var animations = new List(); RelativePoint transformOrigin = default; var isHorizontal = direction == Direction.Left || direction == Direction.Right; @@ -183,22 +183,24 @@ internal static partial class MotionFactory endFrame.Setters.Add(scaleYSetter); } } + + animation.Children.Add(endFrame); if (direction == Direction.Left) { - transformOrigin = new RelativePoint(0, 0.5, RelativeUnit.Relative); + transformOrigin = new RelativePoint(1.0, 0.5, RelativeUnit.Relative); } else if (direction == Direction.Right) { - transformOrigin = new RelativePoint(1, 0.5, RelativeUnit.Relative); + transformOrigin = new RelativePoint(0.0, 0.5, RelativeUnit.Relative); } else if (direction == Direction.Top) { - transformOrigin = new RelativePoint(0.5, 0, RelativeUnit.Relative); + transformOrigin = new RelativePoint(0.5, 1.0, RelativeUnit.Relative); } else { - transformOrigin = new RelativePoint(0.5, 1, RelativeUnit.Relative); + transformOrigin = new RelativePoint(0.5, 1.0, RelativeUnit.Relative); } animations.Add(animation); diff --git a/src/AtomUI.Controls/Utils/Motion/SlideMotionFactory.cs b/src/AtomUI.Controls/Utils/Motion/SlideMotionFactory.cs index f335f4b63..29796bd8b 100644 --- a/src/AtomUI.Controls/Utils/Motion/SlideMotionFactory.cs +++ b/src/AtomUI.Controls/Utils/Motion/SlideMotionFactory.cs @@ -2,8 +2,6 @@ using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; -using Avalonia.Media; -using Avalonia.Media.Transformation; using Avalonia.Styling; namespace AtomUI.Controls.Utils;