Merge pull request #609 from WCKYWCKF/pr5

Add Animation to NavMenu
This commit is contained in:
Dong Bin
2025-08-20 01:27:47 +08:00
committed by GitHub
7 changed files with 388 additions and 32 deletions

View File

@@ -0,0 +1,175 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices.ComTypes;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Styling;
namespace Ursa.Helpers;
public delegate Animation SizeAnimationHelperAnimationGeneratorDelegate(Control animationTargetControl,
Size oldDesiredSize,
Size newDesiredSize);
public class SizeAnimationHelper : AvaloniaObject
{
public static readonly AttachedProperty<SizeAnimationHelperAnimationGeneratorDelegate> CreateAnimationProperty =
AvaloniaProperty
.RegisterAttached<SizeAnimationHelper, Control, SizeAnimationHelperAnimationGeneratorDelegate>(
"CreateAnimation", CreateAnimationPropertyDefaultValue);
internal static readonly AttachedProperty<CancellationTokenSource?> AnimationCancellationTokenSourceProperty =
AvaloniaProperty.RegisterAttached<SizeAnimationHelper, Control, CancellationTokenSource?>(
"AnimationCancellationTokenSource");
public static readonly AttachedProperty<AvaloniaProperty?> TriggerAvaloniaPropertyProperty =
AvaloniaProperty.RegisterAttached<SizeAnimationHelper, Control, AvaloniaProperty?>("TriggerAvaloniaProperty");
public static readonly AttachedProperty<bool> EnableWHAnimationProperty =
AvaloniaProperty.RegisterAttached<SizeAnimationHelper, Control, bool>("EnableWHAnimation");
static SizeAnimationHelper()
{
EnableWHAnimationProperty.Changed.AddClassHandler<Control>(OnPropertyChanged);
}
private static Animation CreateAnimationPropertyDefaultValue(Control animationTargetControl, Size oldDesiredSize,
Size newDesiredSize)
{
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, oldDesiredSize.Width),
new Setter(Layoutable.HeightProperty, oldDesiredSize.Height)
}
},
new KeyFrame
{
Cue = new Cue(1.0),
Setters =
{
new Setter(Layoutable.WidthProperty, newDesiredSize.Width),
new Setter(Layoutable.HeightProperty, newDesiredSize.Height)
}
}
}
};
}
public static void SetCreateAnimation(Control obj, SizeAnimationHelperAnimationGeneratorDelegate value)
{
obj.SetValue(CreateAnimationProperty, value);
}
public static SizeAnimationHelperAnimationGeneratorDelegate GetCreateAnimation(Control obj)
{
return obj.GetValue(CreateAnimationProperty);
}
internal static void SetAnimationCancellationTokenSource(Control obj, CancellationTokenSource? value)
{
obj.SetValue(AnimationCancellationTokenSourceProperty, value);
}
internal static CancellationTokenSource? GetAnimationCancellationTokenSource(Control obj)
{
return obj.GetValue(AnimationCancellationTokenSourceProperty);
}
public static void SetTriggerAvaloniaProperty(Control obj, AvaloniaProperty? value)
{
obj.SetValue(TriggerAvaloniaPropertyProperty, value);
}
public static AvaloniaProperty? GetTriggerAvaloniaProperty(Control obj)
{
return obj.GetValue(TriggerAvaloniaPropertyProperty);
}
public static void SetEnableWHAnimation(Control obj, bool value)
{
obj.SetValue(EnableWHAnimationProperty, value);
}
public static bool GetEnableWHAnimation(Control obj)
{
return obj.GetValue(EnableWHAnimationProperty);
}
private static void OnPropertyChanged(Control obj, AvaloniaPropertyChangedEventArgs change)
{
if (change.Property != EnableWHAnimationProperty) return;
_ = change.NewValue is bool value ? value : throw new ArgumentNullException();
if (value)
{
var triggerProperty = GetTriggerAvaloniaProperty(obj);
if (triggerProperty == null)
{
throw new InvalidOperationException(
"SizeAnimationHelper requires TriggerAvaloniaProperty to be set when EnableWHAnimation is true.");
}
if (triggerProperty == Visual.BoundsProperty ||
triggerProperty == Layoutable.DesiredSizeProperty)
{
throw new InvalidOperationException(
"SizeAnimationHelper does not support Visual.BoundsProperty or Layoutable.DesiredSizeProperty as trigger property.");
}
if (obj.IsLoaded)
{
obj.PropertyChanged += AnimationTargetOnPropertyChanged;
}
else
{
obj.Loaded += ObjOnLoaded;
}
}
else
{
obj.Loaded -= ObjOnLoaded;
obj.PropertyChanged -= AnimationTargetOnPropertyChanged;
}
void ObjOnLoaded(object? sender, RoutedEventArgs e)
{
obj.PropertyChanged += AnimationTargetOnPropertyChanged;
obj.Loaded -= ObjOnLoaded;
}
}
private static void AnimationTargetOnPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
if (sender is not Control control ||
GetEnableWHAnimation(control) is false ||
e.Property != GetTriggerAvaloniaProperty(control) ||
e.Property == Visual.BoundsProperty ||
control.IsLoaded is false ||
control.IsVisible is false) return;
var cancellationTokenSource = GetAnimationCancellationTokenSource(control);
cancellationTokenSource?.Cancel();
cancellationTokenSource?.Dispose();
cancellationTokenSource = new CancellationTokenSource();
SetAnimationCancellationTokenSource(control, cancellationTokenSource);
var oldValue = control.DesiredSize;
control.UpdateLayout();
var newValue = control.DesiredSize;
control.InvalidateArrange();
var animation = GetCreateAnimation(control)(control, oldValue, newValue);
animation.RunAsync(control, cancellationTokenSource.Token);
}
}