@@ -8,6 +8,7 @@
|
|||||||
xmlns:u="https://irihi.tech/ursa"
|
xmlns:u="https://irihi.tech/ursa"
|
||||||
xmlns:vm="using:Ursa.Demo.ViewModels"
|
xmlns:vm="using:Ursa.Demo.ViewModels"
|
||||||
xmlns:iri="https://irihi.tech/shared"
|
xmlns:iri="https://irihi.tech/shared"
|
||||||
|
xmlns:views="using:Ursa.Demo.Pages"
|
||||||
d:DesignHeight="450"
|
d:DesignHeight="450"
|
||||||
d:DesignWidth="800"
|
d:DesignWidth="800"
|
||||||
x:CompileBindings="True"
|
x:CompileBindings="True"
|
||||||
@@ -39,7 +40,8 @@
|
|||||||
IsHorizontalCollapsed="{Binding #collapse.IsChecked, Mode=OneWay}"
|
IsHorizontalCollapsed="{Binding #collapse.IsChecked, Mode=OneWay}"
|
||||||
ItemsSource="{Binding MenuItems}"
|
ItemsSource="{Binding MenuItems}"
|
||||||
SelectedItem="{Binding SelectedMenuItem}"
|
SelectedItem="{Binding SelectedMenuItem}"
|
||||||
SubMenuBinding="{Binding Children}">
|
SubMenuBinding="{Binding Children}"
|
||||||
|
Classes="enable_animation">
|
||||||
<u:NavMenu.Styles>
|
<u:NavMenu.Styles>
|
||||||
<Style x:DataType="vm:MenuItem" Selector="u|NavMenuItem">
|
<Style x:DataType="vm:MenuItem" Selector="u|NavMenuItem">
|
||||||
<Setter Property="IsSeparator" Value="{Binding IsSeparator}" />
|
<Setter Property="IsSeparator" Value="{Binding IsSeparator}" />
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
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;
|
||||||
|
|
||||||
namespace Ursa.Demo.Pages;
|
namespace Ursa.Demo.Pages;
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,15 @@
|
|||||||
<Setter Property="Width" Value="{Binding $self.CollapseWidth}" />
|
<Setter Property="Width" Value="{Binding $self.CollapseWidth}" />
|
||||||
<Setter Property="Grid.IsSharedSizeScope" Value="False" />
|
<Setter Property="Grid.IsSharedSizeScope" Value="False" />
|
||||||
</Style>
|
</Style>
|
||||||
|
<Style Selector="^.enable_animation">
|
||||||
|
<Setter Property="u:SizeAnimationHelper.TriggerAvaloniaProperty"
|
||||||
|
Value="{x:Static u:NavMenu.IsHorizontalCollapsedProperty}">
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="u:SizeAnimationHelper.CreateAnimation"
|
||||||
|
Value="{DynamicResource NavMenuWidthAnimationGenerator}">
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="u:SizeAnimationHelper.EnableWHAnimation" Value="True"></Setter>
|
||||||
|
</Style>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
|
|
||||||
<ControlTemplate x:Key="DefaultNavMenuItemTemplate" TargetType="u:NavMenuItem">
|
<ControlTemplate x:Key="DefaultNavMenuItemTemplate" TargetType="u:NavMenuItem">
|
||||||
@@ -134,13 +143,15 @@
|
|||||||
<Transitions>
|
<Transitions>
|
||||||
<DoubleTransition Easing="QuadraticEaseIn" Property="Height" Duration="0.25" />
|
<DoubleTransition Easing="QuadraticEaseIn" Property="Height" Duration="0.25" />
|
||||||
<DoubleTransition Easing="QuadraticEaseOut" Property="Opacity" Duration="0.25" />
|
<DoubleTransition Easing="QuadraticEaseOut" Property="Opacity" Duration="0.25" />
|
||||||
<TransformOperationsTransition Easing="QuadraticEaseInOut" Property="RenderTransform" Duration="0.25" />
|
<TransformOperationsTransition Easing="QuadraticEaseInOut" Property="RenderTransform"
|
||||||
|
Duration="0.25" />
|
||||||
</Transitions>
|
</Transitions>
|
||||||
</ItemsPresenter.Transitions>
|
</ItemsPresenter.Transitions>
|
||||||
</ItemsPresenter>
|
</ItemsPresenter>
|
||||||
<LayoutTransformControl.Transitions>
|
<LayoutTransformControl.Transitions>
|
||||||
<Transitions>
|
<Transitions>
|
||||||
<TransformOperationsTransition Easing="QuadraticEaseInOut" Property="LayoutTransform" Duration="0.15" Delay="0.1" />
|
<TransformOperationsTransition Easing="QuadraticEaseInOut" Property="LayoutTransform"
|
||||||
|
Duration="0.15" Delay="0.1" />
|
||||||
</Transitions>
|
</Transitions>
|
||||||
</LayoutTransformControl.Transitions>
|
</LayoutTransformControl.Transitions>
|
||||||
</LayoutTransformControl>
|
</LayoutTransformControl>
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ public class SemiTheme : Styles
|
|||||||
public SemiTheme(IServiceProvider? provider = null)
|
public SemiTheme(IServiceProvider? provider = null)
|
||||||
{
|
{
|
||||||
AvaloniaXamlLoader.Load(provider, this);
|
AvaloniaXamlLoader.Load(provider, this);
|
||||||
|
Resources.MergedDictionaries.Add(new SizeAnimations.DefaultSizeAnimations());
|
||||||
|
Resources.MergedDictionaries.Add(new SizeAnimations.NavMenuSizeAnimations());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ThemeVariant Aquatic => new(nameof(Aquatic), ThemeVariant.Dark);
|
public static ThemeVariant Aquatic => new(nameof(Aquatic), ThemeVariant.Dark);
|
||||||
|
|||||||
105
src/Ursa.Themes.Semi/SizeAnimations/DefaultSizeAnimations.cs
Normal file
105
src/Ursa.Themes.Semi/SizeAnimations/DefaultSizeAnimations.cs
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
using Avalonia.Animation;
|
||||||
|
using Avalonia.Animation.Easings;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using Ursa.Helpers;
|
||||||
|
|
||||||
|
namespace Ursa.Themes.Semi.SizeAnimations;
|
||||||
|
|
||||||
|
public partial class DefaultSizeAnimations : ResourceDictionary
|
||||||
|
{
|
||||||
|
public const string WidthAnimationGeneratorKey = "WidthAnimationGenerator";
|
||||||
|
public const string HeightAnimationGeneratorKey = "HeightAnimationGenerator";
|
||||||
|
public const string WidthHeightAnimationGeneratorKey = "WidthHeightAnimationGenerator";
|
||||||
|
|
||||||
|
public DefaultSizeAnimations()
|
||||||
|
{
|
||||||
|
Add(WidthAnimationGeneratorKey, WidthAnimationGenerator);
|
||||||
|
Add(HeightAnimationGeneratorKey, HeightAnimationGenerator);
|
||||||
|
Add(WidthHeightAnimationGeneratorKey, WidthHeightAnimationGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SizeAnimationHelperAnimationGeneratorDelegate WidthAnimationGenerator =
|
||||||
|
(_, oldDesiredSize, newDesiredSize) => 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 KeyFrame
|
||||||
|
{
|
||||||
|
Cue = new Cue(1.0),
|
||||||
|
Setters =
|
||||||
|
{
|
||||||
|
new Setter(Layoutable.WidthProperty, newDesiredSize.Width)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly SizeAnimationHelperAnimationGeneratorDelegate HeightAnimationGenerator =
|
||||||
|
(_, oldDesiredSize, newDesiredSize) => new Animation
|
||||||
|
{
|
||||||
|
Duration = TimeSpan.FromMilliseconds(300),
|
||||||
|
Easing = new CubicEaseInOut(),
|
||||||
|
FillMode = FillMode.None,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new KeyFrame
|
||||||
|
{
|
||||||
|
Cue = new Cue(0.0),
|
||||||
|
Setters =
|
||||||
|
{
|
||||||
|
new Setter(Layoutable.HeightProperty, oldDesiredSize.Height)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new KeyFrame
|
||||||
|
{
|
||||||
|
Cue = new Cue(1.0),
|
||||||
|
Setters =
|
||||||
|
{
|
||||||
|
new Setter(Layoutable.HeightProperty, newDesiredSize.Height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly SizeAnimationHelperAnimationGeneratorDelegate WidthHeightAnimationGenerator =
|
||||||
|
(_, oldDesiredSize, newDesiredSize) => 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
52
src/Ursa.Themes.Semi/SizeAnimations/NavMenuSizeAnimations.cs
Normal file
52
src/Ursa.Themes.Semi/SizeAnimations/NavMenuSizeAnimations.cs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
using Avalonia.Animation;
|
||||||
|
using Avalonia.Animation.Easings;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Layout;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
using Ursa.Helpers;
|
||||||
|
|
||||||
|
namespace Ursa.Themes.Semi.SizeAnimations;
|
||||||
|
|
||||||
|
public class NavMenuSizeAnimations : ResourceDictionary
|
||||||
|
{
|
||||||
|
public const string NavMenuWidthAnimationGeneratorKey = "NavMenuWidthAnimationGenerator";
|
||||||
|
|
||||||
|
public NavMenuSizeAnimations()
|
||||||
|
{
|
||||||
|
Add(NavMenuWidthAnimationGeneratorKey, NavMenuWidthAnimationGenerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly SizeAnimationHelperAnimationGeneratorDelegate NavMenuWidthAnimationGenerator =
|
||||||
|
(_, oldDesiredSize, newDesiredSize) =>
|
||||||
|
{
|
||||||
|
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(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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
175
src/Ursa/Helpers/SizeAnimationHelper.cs
Normal file
175
src/Ursa/Helpers/SizeAnimationHelper.cs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user