Browse Source

Implement ICompositionTransition : ITransition support

xaml_integrated_comp_animations
Max Katz 1 week ago
parent
commit
a975a4d545
  1. 11
      samples/ControlCatalog/Pages/CompositionPage.axaml
  2. 61
      src/Avalonia.Base/Animation/Animatable.cs
  3. 78
      src/Avalonia.Base/Animation/CompositionAnimations/Animations.cs
  4. 6
      src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimation.cs
  5. 10
      src/Avalonia.Base/Animation/CompositionAnimations/ExplicitAnimationCollection.cs
  6. 86
      src/Avalonia.Base/Animation/CompositionAnimations/ImplicitAnimationCollection.cs
  7. 14
      src/Avalonia.Base/Animation/ITransition.cs
  8. 35
      src/Avalonia.Base/Animation/Transitions.cs
  9. 1
      src/Avalonia.Base/Visual.cs
  10. 4
      src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml
  11. 2
      src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml
  12. 4
      src/Avalonia.Themes.Simple/Accents/Base.xaml
  13. 2
      src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml

11
samples/ControlCatalog/Pages/CompositionPage.axaml

@ -5,14 +5,13 @@
<TabControl>
<TabItem Header="Implicit animations">
<StackPanel>
<StackPanel.Resources>
<ImplicitAnimationCollection x:Key="ImplAnimations">
<OffsetCompositionAnimation Duration="0:0:0.4"/>
</ImplicitAnimationCollection>
</StackPanel.Resources>
<StackPanel.Styles>
<Style Selector="ItemsControl ContentPresenter">
<Setter Property="Animations.ImplicitAnimations" Value="{DynamicResource ImplAnimations}"/>
<Setter Property="Transitions">
<Transitions>
<OffsetCompositionAnimation Duration="0:0:0.4"/>
</Transitions>
</Setter>
</Style>
</StackPanel.Styles>
<Grid ColumnDefinitions="*,10,40" Margin="0 0 40 0">

61
src/Avalonia.Base/Animation/Animatable.cs

@ -5,6 +5,7 @@ using System.Collections.Specialized;
using System.Linq;
using Avalonia.Data;
using Avalonia.PropertyStore;
using Avalonia.Rendering.Composition;
#nullable enable
@ -223,36 +224,68 @@ namespace Avalonia.Animation
}
_transitionState ??= new Dictionary<ITransition, TransitionState>();
var compTransitionsChanged = false;
for (var i = 0; i < items.Count; ++i)
{
if (items[i] is IPropertyTransition t)
switch (items[i])
{
_transitionState.Add(t, new TransitionState
{
BaseValue = GetAnimationBaseValue(t.Property),
});
case ICompositionTransition ct:
compTransitionsChanged = true;
ct.AnimationInvalidated += CompositionOnAnimationInvalidated;
break;
case IPropertyTransition t:
_transitionState.Add(t, new TransitionState
{
BaseValue = GetAnimationBaseValue(t.Property),
});
break;
}
}
if (compTransitionsChanged)
{
InvalidateCompositionTransitions();
}
}
private void RemoveTransitions(IList items)
{
if (_transitionState is null)
{
return;
}
var compTransitionsChanged = false;
for (var i = 0; i < items.Count; ++i)
{
var t = (ITransition)items[i]!;
if (_transitionState.TryGetValue(t, out var state))
switch (items[i])
{
state.Instance?.Dispose();
_transitionState.Remove(t);
case ICompositionTransition ct:
compTransitionsChanged = true;
ct.AnimationInvalidated -= CompositionOnAnimationInvalidated;
break;
case IPropertyTransition t when _transitionState?.TryGetValue(t, out var state) == true:
state.Instance?.Dispose();
_transitionState.Remove(t);
break;
}
}
if (compTransitionsChanged)
{
InvalidateCompositionTransitions();
}
}
private void CompositionOnAnimationInvalidated(object? sender, EventArgs e)
{
InvalidateCompositionTransitions();
}
private protected void InvalidateCompositionTransitions()
{
if (this is Visual { CompositionVisual: { } compVisual } visual
&& Transitions is { } transitions)
{
compVisual.ImplicitAnimations = transitions.GetImplicitAnimations(visual);
}
}
private object? GetAnimationBaseValue(AvaloniaProperty property)

78
src/Avalonia.Base/Animation/CompositionAnimations/Animations.cs

@ -5,27 +5,27 @@ namespace Avalonia.Animation
{
public static class Animations
{
public static readonly AttachedProperty<ImplicitAnimationCollection?> ImplicitAnimationsProperty =
AvaloniaProperty.RegisterAttached<Visual, ImplicitAnimationCollection?>(
"ImplicitAnimations",
typeof(Animations));
// public static readonly AttachedProperty<ImplicitAnimationCollection?> ImplicitAnimationsProperty =
// AvaloniaProperty.RegisterAttached<Visual, ImplicitAnimationCollection?>(
// "ImplicitAnimations",
// typeof(Animations));
public static readonly AttachedProperty<ExplicitAnimationCollection?> ExplicitAnimationsProperty =
AvaloniaProperty.RegisterAttached<Visual, ExplicitAnimationCollection?>(
"ExplicitAnimations",
typeof(Animations));
public static void SetImplicitAnimations(Visual visual, ImplicitAnimationCollection? value)
{
visual?.SetValue(ImplicitAnimationsProperty, value);
}
//
// public static void SetImplicitAnimations(Visual visual, ImplicitAnimationCollection? value)
// {
// visual?.SetValue(ImplicitAnimationsProperty, value);
// }
public static void SetExplicitAnimations(Visual visual, ExplicitAnimationCollection? value)
{
visual?.SetValue(ExplicitAnimationsProperty, value);
}
public static ImplicitAnimationCollection? GetImplicitAnimations(Visual visual) => visual.GetValue(ImplicitAnimationsProperty);
// public static ImplicitAnimationCollection? GetImplicitAnimations(Visual visual) => visual.GetValue(ImplicitAnimationsProperty);
public static ExplicitAnimationCollection? GetExplicitAnimations(Visual visual) => visual.GetValue(ExplicitAnimationsProperty);
@ -43,7 +43,7 @@ namespace Avalonia.Animation
static Animations()
{
ImplicitAnimationsProperty.Changed.AddClassHandler<Visual>(OnAnimationsPropertyChanged);
//ImplicitAnimationsProperty.Changed.AddClassHandler<Visual>(OnAnimationsPropertyChanged);
ExplicitAnimationsProperty.Changed.AddClassHandler<Visual>(OnAnimationsPropertyChanged);
EnableAnimationsProperty.Changed.AddClassHandler<Visual>(OnAnimationsPropertyChanged);
}
@ -55,23 +55,24 @@ namespace Avalonia.Animation
Invalidate(visual);
}
if (args.Property == ImplicitAnimationsProperty)
{
if (args.OldValue is ImplicitAnimationCollection oldImplicitSet)
{
oldImplicitSet.Invalidated -= (s, e) => UpdateAnimations(s as AvaloniaList<CompositionAnimation>, visual);
visual.AttachedToVisualTree -= AttachedToVisualTree;
}
if (args.NewValue is ImplicitAnimationCollection newImplicitSet)
{
newImplicitSet.Invalidated += (s, e) => UpdateAnimations(s as AvaloniaList<CompositionAnimation>, visual);
UpdateAnimations(newImplicitSet, visual);
visual.AttachedToVisualTree += AttachedToVisualTree;
}
}
else if (args.Property == ExplicitAnimationsProperty)
// if (args.Property == ImplicitAnimationsProperty)
// {
// // if (args.OldValue is ImplicitAnimationCollection oldImplicitSet)
// // {
// // oldImplicitSet.Invalidated -= (s, e) => UpdateAnimations(s as AvaloniaList<CompositionAnimation>, visual);
// // visual.AttachedToVisualTree -= AttachedToVisualTree;
// // }
// //
// //
// // if (args.NewValue is ImplicitAnimationCollection newImplicitSet)
// // {
// // newImplicitSet.Invalidated += (s, e) => UpdateAnimations(s as AvaloniaList<CompositionAnimation>, visual);
// // UpdateAnimations(newImplicitSet, visual);
// // visual.AttachedToVisualTree += AttachedToVisualTree;
// // }
// }
// else
if (args.Property == ExplicitAnimationsProperty)
{
if (args.OldValue is ExplicitAnimationCollection oldExplicitSet)
{
@ -99,7 +100,7 @@ namespace Avalonia.Animation
if (visual == null)
return;
UpdateAnimations(GetImplicitAnimations(visual), visual);
//UpdateAnimations(GetImplicitAnimations(visual), visual);
var explicitAnimationCollection = GetExplicitAnimations(visual);
explicitAnimationCollection?.Detach(visual);
UpdateAnimations(explicitAnimationCollection, visual);
@ -107,15 +108,16 @@ namespace Avalonia.Animation
private static void UpdateAnimations(AvaloniaList<CompositionAnimation>? collections, Visual visual)
{
if (collections is ImplicitAnimationCollection implicitCollection)
{
if (ElementComposition.GetElementVisual(visual) is { } compositionVisual)
{
compositionVisual.ImplicitAnimations =
GetEnableAnimations(visual) ? implicitCollection.GetAnimations(visual) : null;
}
}
else if (collections is ExplicitAnimationCollection explicitCollection)
// if (collections is ImplicitAnimationCollection implicitCollection)
// {
// if (ElementComposition.GetElementVisual(visual) is { } compositionVisual)
// {
// compositionVisual.ImplicitAnimations =
// GetEnableAnimations(visual) ? implicitCollection.GetAnimations(visual) : null;
// }
// }
// else
if (collections is ExplicitAnimationCollection explicitCollection)
{
if (ElementComposition.GetElementVisual(visual) is { } compositionVisual && GetEnableAnimations(visual))
{

6
src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimation.cs

@ -10,9 +10,9 @@ using Avalonia.Rendering.Composition.Animations;
namespace Avalonia.Animation
{
public abstract class CompositionAnimation : AvaloniaObject
public abstract class CompositionAnimation : AvaloniaObject, ICompositionTransition
{
internal event EventHandler? AnimationInvalidated;
public event EventHandler? AnimationInvalidated;
public static readonly StyledProperty<bool> IsEnabledProperty = AvaloniaProperty.Register<CompositionAnimation, bool>(
nameof(IsEnabled), defaultValue: true);
@ -74,7 +74,7 @@ namespace Avalonia.Animation
set => SetValue(StopBehaviorProperty, value);
}
internal Rendering.Composition.Animations.CompositionAnimation? GetCompositionAnimationInternal(Visual parent)
Rendering.Composition.Animations.CompositionAnimation? ICompositionTransition.GetCompositionAnimation(Visual parent)
{
return !IsEnabled ? null : GetCompositionAnimation(parent);
}

10
src/Avalonia.Base/Animation/CompositionAnimations/ExplicitAnimationCollection.cs

@ -87,11 +87,11 @@ namespace Avalonia.Animation
private static void AttachAnimation(Visual visual, CompositionAnimation animation)
{
if (animation.GetCompositionAnimationInternal(visual) is KeyFrameAnimation newAnimation
&& newAnimation.Target is { } target)
{
animation.Attach(visual, newAnimation);
}
// if (animation.GetCompositionAnimationInternal(visual) is KeyFrameAnimation newAnimation
// && newAnimation.Target is { } target)
// {
// animation.Attach(visual, newAnimation);
// }
}
}
}

86
src/Avalonia.Base/Animation/CompositionAnimations/ImplicitAnimationCollection.cs

@ -1,86 +0,0 @@
using System;
using System.Collections.Specialized;
using Avalonia.Collections;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Animations;
namespace Avalonia.Animation
{
public class ImplicitAnimationCollection : AvaloniaList<CompositionAnimation>
{
internal event EventHandler? Invalidated;
public ImplicitAnimationCollection()
{
this.CollectionChanged += OnCollectionChanged;
}
private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.OldItems is { } oldItems)
{
foreach (var oldItem in oldItems)
{
if (oldItem is CompositionAnimation compositionAnimation)
{
compositionAnimation.AnimationInvalidated -= InvalidateAnimations;
}
}
}
if (e.NewItems is { } newItems)
{
foreach (var newItem in newItems)
{
if (newItem is CompositionAnimation compositionAnimation)
{
compositionAnimation.AnimationInvalidated += InvalidateAnimations;
}
}
}
OnAnimationInvalidated();
void InvalidateAnimations(object? sender, EventArgs e)
{
OnAnimationInvalidated();
}
}
private void OnAnimationInvalidated()
{
Invalidated?.Invoke(this, EventArgs.Empty);
}
internal Rendering.Composition.Animations.ImplicitAnimationCollection? GetAnimations(Visual visual)
{
var compositor = ElementComposition.GetElementVisual(visual)?.Compositor;
if (compositor == null)
return null;
var animationCollection = compositor.CreateImplicitAnimationCollection();
foreach (var animation in this)
{
ICompositionAnimationBase? group = null;
if (animation.GetCompositionAnimationInternal(visual) is
{ } newAnimation && newAnimation.Target is { } target)
{
group = animationCollection.TryGetValue(target, out var value) ?
value :
compositor.CreateAnimationGroup();
if (group is CompositionAnimationGroup animationGroup)
animationGroup.Add(newAnimation);
if (group != null)
animationCollection[target] = group;
}
}
return animationCollection;
}
}
}

14
src/Avalonia.Base/Animation/ITransition.cs

@ -11,6 +11,20 @@ namespace Avalonia.Animation
{
}
[NotClientImplementable, PrivateApi]
public interface ICompositionTransition : ITransition
{
/// <summary>
/// Occurs when the transition is invalidated and needs to be re-applied.
/// </summary>
event EventHandler? AnimationInvalidated;
/// <summary>
/// Gets the composition animation for the specified parent visual.
/// </summary>
Rendering.Composition.Animations.CompositionAnimation? GetCompositionAnimation(Visual parent);
}
[NotClientImplementable, PrivateApi]
public interface IPropertyTransition : ITransition
{

35
src/Avalonia.Base/Animation/Transitions.cs

@ -1,5 +1,7 @@
using System;
using Avalonia.Collections;
using Avalonia.Rendering.Composition;
using Avalonia.Rendering.Composition.Animations;
using Avalonia.Threading;
namespace Avalonia.Animation
@ -28,5 +30,38 @@ namespace Avalonia.Animation
throw new InvalidOperationException($"Cannot animate direct property {property} on {display}.");
}
}
internal ImplicitAnimationCollection? GetImplicitAnimations(Visual visual)
{
var compositor = ElementComposition.GetElementVisual(visual)?.Compositor;
if (compositor == null)
return null;
var animationCollection = compositor.CreateImplicitAnimationCollection();
foreach (var transition in this)
{
if (transition is ICompositionTransition compositionTransition)
{
ICompositionAnimationBase? group = null;
if (compositionTransition.GetCompositionAnimation(visual) is { Target: { } target } newAnimation)
{
group = animationCollection.TryGetValue(target, out var value) ?
value :
compositor.CreateAnimationGroup();
if (group is CompositionAnimationGroup animationGroup)
animationGroup.Add(newAnimation);
if (group != null)
animationCollection[target] = group;
}
}
}
return animationCollection.Count == 0 ? null : animationCollection;
}
}
}

1
src/Avalonia.Base/Visual.cs

@ -527,6 +527,7 @@ namespace Avalonia
if (_visualRoot.Renderer is IRendererWithCompositor compositingRenderer)
{
AttachToCompositor(compositingRenderer.Compositor);
InvalidateCompositionTransitions();
}
InvalidateMirrorTransform();
UpdateIsEffectivelyVisible(_visualParent.IsEffectivelyVisible);

4
src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml

@ -54,7 +54,7 @@
<!-- Moved from Menu.xaml -->
<Thickness x:Key="MenuBarItemPadding">12,0,12,0</Thickness>
<ImplicitAnimationCollection x:Key="RefreshVisualizerAnimations">
<Transitions x:Key="RefreshVisualizerTransitions">
<RotationAngleCompositionAnimation Duration="0:0:0.1">
<ExpressionKeyFrame Easing="LinearEasing" Value="this.FinalValue" NormalizedProgressKey="1.0" />
</RotationAngleCompositionAnimation>
@ -67,7 +67,7 @@
<ScaleCompositionAnimation Duration="0:0:0.1">
<ExpressionKeyFrame Easing="LinearEasing" Value="this.FinalValue" NormalizedProgressKey="1.0" />
</ScaleCompositionAnimation>
</ImplicitAnimationCollection>
</Transitions>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary x:Key="Default">

2
src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml

@ -14,7 +14,7 @@
<Setter Property="Content">
<Template>
<PathIcon Name="PART_Icon"
Animations.ImplicitAnimations="{DynamicResource RefreshVisualizerAnimations}"
Transitions="{DynamicResource RefreshVisualizerTransitions}"
Data="M18.6195264,3.31842271 C19.0080059,3.31842271 19.3290603,3.60710385 19.3798716,3.9816481 L19.3868766,4.08577298 L19.3868766,6.97963208 C19.3868766,7.36811161 19.0981955,7.68916605 18.7236513,7.73997735 L18.6195264,7.74698235 L15.7256673,7.74698235 C15.3018714,7.74698235 14.958317,7.40342793 14.958317,6.97963208 C14.958317,6.59115255 15.2469981,6.27009811 15.6215424,6.21928681 L15.7256673,6.21228181 L16.7044011,6.21182461 C13.7917384,3.87107476 9.52212532,4.05209336 6.81933829,6.75488039 C3.92253872,9.65167996 3.92253872,14.34832 6.81933829,17.2451196 C9.71613786,20.1419192 14.4127779,20.1419192 17.3095775,17.2451196 C19.0725398,15.4821573 19.8106555,12.9925923 19.3476248,10.58925 C19.2674502,10.173107 19.5398064,9.77076216 19.9559494,9.69058758 C20.3720923,9.610413 20.7744372,9.88276918 20.8546118,10.2989121 C21.4129973,13.1971899 20.5217103,16.2033812 18.3947747,18.3303168 C14.8986373,21.8264542 9.23027854,21.8264542 5.73414113,18.3303168 C2.23800371,14.8341794 2.23800371,9.16582064 5.73414113,5.66968323 C9.05475132,2.34907304 14.3349409,2.18235834 17.8523166,5.16953912 L17.8521761,4.08577298 C17.8521761,3.66197713 18.1957305,3.31842271 18.6195264,3.31842271 Z"
Width="{DynamicResource RefreshVisualizerIndicatorSize}"
Height="{DynamicResource RefreshVisualizerIndicatorSize}">

4
src/Avalonia.Themes.Simple/Accents/Base.xaml

@ -153,7 +153,7 @@
<sys:Double x:Key="IconElementThemeWidth">20</sys:Double>
<sys:Double x:Key="TextControlPlaceholderOpacity">0.5</sys:Double>
<ImplicitAnimationCollection x:Key="RefreshVisualizerAnimations">
<Transitions x:Key="RefreshVisualizerTransitions">
<RotationAngleCompositionAnimation Duration="0:0:0.1">
<ExpressionKeyFrame Easing="LinearEasing" Value="this.FinalValue" NormalizedProgressKey="1.0" />
</RotationAngleCompositionAnimation>
@ -166,5 +166,5 @@
<ScaleCompositionAnimation Duration="0:0:0.1">
<ExpressionKeyFrame Easing="LinearEasing" Value="this.FinalValue" NormalizedProgressKey="1.0" />
</ScaleCompositionAnimation>
</ImplicitAnimationCollection>
</Transitions>
</ResourceDictionary>

2
src/Avalonia.Themes.Simple/Controls/RefreshVisualizer.xaml

@ -16,7 +16,7 @@
<Setter Property="Content">
<Template>
<PathIcon Name="PART_Icon"
Animations.ImplicitAnimations="{DynamicResource RefreshVisualizerAnimations}"
Transitions="{DynamicResource RefreshVisualizerTransitions}"
Data="M18.6195264,3.31842271 C19.0080059,3.31842271 19.3290603,3.60710385 19.3798716,3.9816481 L19.3868766,4.08577298 L19.3868766,6.97963208 C19.3868766,7.36811161 19.0981955,7.68916605 18.7236513,7.73997735 L18.6195264,7.74698235 L15.7256673,7.74698235 C15.3018714,7.74698235 14.958317,7.40342793 14.958317,6.97963208 C14.958317,6.59115255 15.2469981,6.27009811 15.6215424,6.21928681 L15.7256673,6.21228181 L16.7044011,6.21182461 C13.7917384,3.87107476 9.52212532,4.05209336 6.81933829,6.75488039 C3.92253872,9.65167996 3.92253872,14.34832 6.81933829,17.2451196 C9.71613786,20.1419192 14.4127779,20.1419192 17.3095775,17.2451196 C19.0725398,15.4821573 19.8106555,12.9925923 19.3476248,10.58925 C19.2674502,10.173107 19.5398064,9.77076216 19.9559494,9.69058758 C20.3720923,9.610413 20.7744372,9.88276918 20.8546118,10.2989121 C21.4129973,13.1971899 20.5217103,16.2033812 18.3947747,18.3303168 C14.8986373,21.8264542 9.23027854,21.8264542 5.73414113,18.3303168 C2.23800371,14.8341794 2.23800371,9.16582064 5.73414113,5.66968323 C9.05475132,2.34907304 14.3349409,2.18235834 17.8523166,5.16953912 L17.8521761,4.08577298 C17.8521761,3.66197713 18.1957305,3.31842271 18.6195264,3.31842271 Z"
Width="24"
Height="24">

Loading…
Cancel
Save