From bb7873ef66df2206df10be0f886d418e0c62fe1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=9B=E5=B0=98=E7=A9=BA=E5=BF=A7?= Date: Sat, 9 Aug 2025 23:00:15 +0800 Subject: [PATCH] feat:Implements subtle animations for the NavMenu in a non-intrusive way. --- demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs | 11 +++ .../Controls/NavMenu/WHAnimationHelper.cs | 78 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 src/Ursa/Controls/NavMenu/WHAnimationHelper.cs diff --git a/demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs b/demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs index 6e78ad8..ec9ec24 100644 --- a/demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs +++ b/demo/Ursa.Demo/Pages/NavMenuDemo.axaml.cs @@ -1,11 +1,22 @@ using Avalonia.Controls; +using Avalonia.Interactivity; +using Ursa.Controls; namespace Ursa.Demo.Pages; public partial class NavMenuDemo : UserControl { + private readonly WHAnimationHelper _animationHelper; + public NavMenuDemo() { InitializeComponent(); + _animationHelper = new WHAnimationHelper(menu, NavMenu.IsHorizontalCollapsedProperty); + } + + protected override void OnLoaded(RoutedEventArgs e) + { + base.OnLoaded(e); + _animationHelper.Start(); } } \ No newline at end of file diff --git a/src/Ursa/Controls/NavMenu/WHAnimationHelper.cs b/src/Ursa/Controls/NavMenu/WHAnimationHelper.cs new file mode 100644 index 0000000..649a8d6 --- /dev/null +++ b/src/Ursa/Controls/NavMenu/WHAnimationHelper.cs @@ -0,0 +1,78 @@ +using Avalonia; +using Avalonia.Animation; +using Avalonia.Animation.Easings; +using Avalonia.Controls; +using Avalonia.Layout; +using Avalonia.Styling; +using Avalonia.Threading; +using Avalonia.VisualTree; + +namespace Ursa.Controls; + +public class WHAnimationHelper(Control control, AvaloniaProperty property) +{ + private CancellationTokenSource? _cancellationTokenSource; + + ~WHAnimationHelper() + { + _cancellationTokenSource?.Dispose(); + } + + public void Stop() + { + control.PropertyChanged -= AnimationTargetOnPropertyChanged; + } + + public void Start() + { + control.PropertyChanged += AnimationTargetOnPropertyChanged; + } + + private void AnimationTargetOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) + { + if (sender as Control != control || + e.Property != property || + control.IsLoaded is false || + control.IsVisible is false) return; + _cancellationTokenSource?.Cancel(); + _cancellationTokenSource?.Dispose(); + _cancellationTokenSource = new CancellationTokenSource(); + + var oldValue = control.DesiredSize; + control.UpdateLayout(); + var newValue = control.DesiredSize; + newValue = newValue.WithWidth(newValue.Width + 20); + control.InvalidateArrange(); + var animation = CreateAnimation(oldValue, newValue); + animation.RunAsync(control, _cancellationTokenSource.Token); + } + + protected virtual Animation CreateAnimation(Size oldValue, Size newValue) + { + return new Animation + { + Duration = TimeSpan.FromMilliseconds(300), + Easing = new CubicEaseInOut(), + FillMode = FillMode.None, + Children = + { + new KeyFrame + { + Cue = new Cue(0.0), + Setters = + { + new Setter(Layoutable.WidthProperty, oldValue.Width) + } + }, + new KeyFrame + { + Cue = new Cue(1.0), + Setters = + { + new Setter(Layoutable.WidthProperty, newValue.Width) + } + } + } + }; + } +} \ No newline at end of file