Add Motion for Expander Control

This commit is contained in:
polarboy 2024-09-29 19:43:32 +08:00
parent b6fcc74be3
commit 8a2092cc0f
9 changed files with 115 additions and 42 deletions

View File

@ -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);

View File

@ -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<AnimationTargetPanel>(ExpanderTheme.ContentAnimationTargetPart);
_motionActor = e.NameScope.Find<MotionActorControl>(ExpanderTheme.ContentMotionActorPart);
_headerDecorator = e.NameScope.Find<Border>(ExpanderTheme.HeaderDecoratorPart);
_expandButton = e.NameScope.Find<IconButton>(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()

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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<Animation>();
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<Animation>();
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);

View File

@ -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;