feat:Redesign the usage pattern of WHAnimationHelper once more to deliver a superior axaml editing and consumption experience.
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:vm="using:Ursa.Demo.ViewModels"
|
||||
xmlns:iri="https://irihi.tech/shared"
|
||||
xmlns:views="using:Ursa.Demo.Pages"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:CompileBindings="True"
|
||||
@@ -39,7 +40,10 @@
|
||||
IsHorizontalCollapsed="{Binding #collapse.IsChecked, Mode=OneWay}"
|
||||
ItemsSource="{Binding MenuItems}"
|
||||
SelectedItem="{Binding SelectedMenuItem}"
|
||||
SubMenuBinding="{Binding Children}">
|
||||
SubMenuBinding="{Binding Children}"
|
||||
u:WHAnimationHelper.TriggerAvaloniaProperty="{x:Static u:NavMenu.IsHorizontalCollapsedProperty}"
|
||||
u:WHAnimationHelper.CreateAnimation="{x:Static views:NavMenuDemo.NavMenuAnimation}"
|
||||
u:WHAnimationHelper.EnableWHAnimation="True">
|
||||
<u:NavMenu.Styles>
|
||||
<Style x:DataType="vm:MenuItem" Selector="u|NavMenuItem">
|
||||
<Setter Property="IsSeparator" Value="{Binding IsSeparator}" />
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
using Avalonia.Controls;
|
||||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Styling;
|
||||
using Ursa.Controls;
|
||||
using Ursa.Helpers;
|
||||
|
||||
@@ -7,17 +13,42 @@ namespace Ursa.Demo.Pages;
|
||||
|
||||
public partial class NavMenuDemo : UserControl
|
||||
{
|
||||
private readonly NavMenuAnimationHelper _animationHelper;
|
||||
|
||||
public NavMenuDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
_animationHelper = new NavMenuAnimationHelper(menu);
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
public static WHAnimationHelperCreateAnimationDelegate NavMenuAnimation { get; } =
|
||||
(_, oldDesiredSize, newDesiredSize) =>
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
_animationHelper.Start();
|
||||
if (oldDesiredSize.Width > newDesiredSize.Width)
|
||||
newDesiredSize = newDesiredSize.WithWidth(newDesiredSize.Width + 20);
|
||||
return new Animation
|
||||
{
|
||||
Duration = TimeSpan.FromMilliseconds(300),
|
||||
Easing = new CubicEaseInOut(),
|
||||
FillMode = FillMode.None,
|
||||
Children =
|
||||
{
|
||||
new KeyFrame
|
||||
{
|
||||
Cue = new Cue(0.0),
|
||||
Setters =
|
||||
{
|
||||
new Setter(WidthProperty, oldDesiredSize.Width),
|
||||
new Setter(HeightProperty, oldDesiredSize.Height)
|
||||
}
|
||||
},
|
||||
new KeyFrame
|
||||
{
|
||||
Cue = new Cue(1.0),
|
||||
Setters =
|
||||
{
|
||||
new Setter(WidthProperty, newDesiredSize.Width),
|
||||
new Setter(HeightProperty, newDesiredSize.Height)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Styling;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace Ursa.Helpers;
|
||||
|
||||
public class NavMenuAnimationHelper(NavMenu control) : WHAnimationHelper(control, NavMenu.IsHorizontalCollapsedProperty)
|
||||
{
|
||||
protected override Animation CreateAnimation(Size oldValue, Size newValue)
|
||||
{
|
||||
if (oldValue.Width > newValue.Width)
|
||||
{
|
||||
newValue = newValue.WithWidth(newValue.Width + 20);
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2,51 +2,34 @@
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Styling;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
|
||||
namespace Ursa.Helpers;
|
||||
|
||||
public class WHAnimationHelper(Control control, AvaloniaProperty property)
|
||||
public delegate Animation WHAnimationHelperCreateAnimationDelegate(Control animationTargetControl, Size oldDesiredSize,
|
||||
Size newDesiredSize);
|
||||
|
||||
public class WHAnimationHelper : AvaloniaObject
|
||||
{
|
||||
private CancellationTokenSource? _cancellationTokenSource;
|
||||
public static readonly AttachedProperty<WHAnimationHelperCreateAnimationDelegate> CreateAnimationProperty =
|
||||
AvaloniaProperty
|
||||
.RegisterAttached<WHAnimationHelper, Control, WHAnimationHelperCreateAnimationDelegate>(
|
||||
"CreateAnimation", CreateAnimationPropertyDefaultValue);
|
||||
|
||||
~WHAnimationHelper()
|
||||
{
|
||||
_cancellationTokenSource?.Dispose();
|
||||
}
|
||||
internal static readonly AttachedProperty<CancellationTokenSource?> AnimationCancellationTokenSourceProperty =
|
||||
AvaloniaProperty.RegisterAttached<WHAnimationHelper, Control, CancellationTokenSource?>(
|
||||
"AnimationCancellationTokenSource");
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
control.PropertyChanged -= AnimationTargetOnPropertyChanged;
|
||||
}
|
||||
public static readonly AttachedProperty<AvaloniaProperty?> TriggerAvaloniaPropertyProperty =
|
||||
AvaloniaProperty.RegisterAttached<WHAnimationHelper, Control, AvaloniaProperty?>("TriggerAvaloniaProperty");
|
||||
|
||||
public void Start()
|
||||
{
|
||||
control.PropertyChanged += AnimationTargetOnPropertyChanged;
|
||||
}
|
||||
public static readonly AttachedProperty<bool> EnableWHAnimationProperty =
|
||||
AvaloniaProperty.RegisterAttached<WHAnimationHelper, Control, bool>("EnableWHAnimation");
|
||||
|
||||
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;
|
||||
control.InvalidateArrange();
|
||||
var animation = CreateAnimation(oldValue, newValue);
|
||||
animation.RunAsync(control, _cancellationTokenSource.Token);
|
||||
}
|
||||
|
||||
protected virtual Animation CreateAnimation(Size oldValue, Size newValue)
|
||||
private static Animation CreateAnimationPropertyDefaultValue(Control animationTargetControl, Size oldDesiredSize,
|
||||
Size newDesiredSize)
|
||||
{
|
||||
return new Animation
|
||||
{
|
||||
@@ -60,7 +43,8 @@ public class WHAnimationHelper(Control control, AvaloniaProperty property)
|
||||
Cue = new Cue(0.0),
|
||||
Setters =
|
||||
{
|
||||
new Setter(Layoutable.WidthProperty, oldValue.Width)
|
||||
new Setter(Layoutable.WidthProperty, oldDesiredSize.Width),
|
||||
new Setter(Layoutable.HeightProperty, oldDesiredSize.Height)
|
||||
}
|
||||
},
|
||||
new KeyFrame
|
||||
@@ -68,10 +52,104 @@ public class WHAnimationHelper(Control control, AvaloniaProperty property)
|
||||
Cue = new Cue(1.0),
|
||||
Setters =
|
||||
{
|
||||
new Setter(Layoutable.WidthProperty, newValue.Width)
|
||||
new Setter(Layoutable.WidthProperty, newDesiredSize.Width),
|
||||
new Setter(Layoutable.HeightProperty, newDesiredSize.Height)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
public static void SetCreateAnimation(Control obj, WHAnimationHelperCreateAnimationDelegate value)
|
||||
{
|
||||
obj.SetValue(CreateAnimationProperty, value);
|
||||
}
|
||||
|
||||
public static WHAnimationHelperCreateAnimationDelegate 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)
|
||||
{
|
||||
if (value == obj.GetValue(EnableWHAnimationProperty)) return;
|
||||
obj.SetValue(EnableWHAnimationProperty, value);
|
||||
if (value)
|
||||
{
|
||||
var triggerProperty = GetTriggerAvaloniaProperty(obj);
|
||||
if (triggerProperty == null)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"WHAnimationHelper requires TriggerAvaloniaProperty to be set when EnableWHAnimation is true.");
|
||||
}
|
||||
|
||||
if (triggerProperty == Visual.BoundsProperty ||
|
||||
triggerProperty == Layoutable.DesiredSizeProperty)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"WHAnimationHelper does not support Visual.BoundsProperty or Layoutable.DesiredSizeProperty as trigger property.");
|
||||
}
|
||||
|
||||
obj.Loaded += ObjOnLoaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
obj.Loaded -= ObjOnLoaded;
|
||||
obj.PropertyChanged -= AnimationTargetOnPropertyChanged;
|
||||
}
|
||||
|
||||
void ObjOnLoaded(object? sender, RoutedEventArgs e)
|
||||
{
|
||||
obj.PropertyChanged += AnimationTargetOnPropertyChanged;
|
||||
obj.Loaded -= ObjOnLoaded;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GetEnableWHAnimation(Control obj)
|
||||
{
|
||||
return obj.GetValue(EnableWHAnimationProperty);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user