diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml b/samples/ControlCatalog/Pages/CompositionPage.axaml index 70e42f14a8..69804b1aed 100644 --- a/samples/ControlCatalog/Pages/CompositionPage.axaml +++ b/samples/ControlCatalog/Pages/CompositionPage.axaml @@ -5,14 +5,13 @@ - - - - - diff --git a/src/Avalonia.Base/Animation/Animatable.cs b/src/Avalonia.Base/Animation/Animatable.cs index 1bc6f0aeef..13f637b6b1 100644 --- a/src/Avalonia.Base/Animation/Animatable.cs +++ b/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(); + 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) diff --git a/src/Avalonia.Base/Animation/CompositionAnimations/Animations.cs b/src/Avalonia.Base/Animation/CompositionAnimations/Animations.cs index 4aac951886..576cb60394 100644 --- a/src/Avalonia.Base/Animation/CompositionAnimations/Animations.cs +++ b/src/Avalonia.Base/Animation/CompositionAnimations/Animations.cs @@ -5,27 +5,27 @@ namespace Avalonia.Animation { public static class Animations { - public static readonly AttachedProperty ImplicitAnimationsProperty = - AvaloniaProperty.RegisterAttached( - "ImplicitAnimations", - typeof(Animations)); + // public static readonly AttachedProperty ImplicitAnimationsProperty = + // AvaloniaProperty.RegisterAttached( + // "ImplicitAnimations", + // typeof(Animations)); public static readonly AttachedProperty ExplicitAnimationsProperty = AvaloniaProperty.RegisterAttached( "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(OnAnimationsPropertyChanged); + //ImplicitAnimationsProperty.Changed.AddClassHandler(OnAnimationsPropertyChanged); ExplicitAnimationsProperty.Changed.AddClassHandler(OnAnimationsPropertyChanged); EnableAnimationsProperty.Changed.AddClassHandler(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, visual); - visual.AttachedToVisualTree -= AttachedToVisualTree; - } - - - if (args.NewValue is ImplicitAnimationCollection newImplicitSet) - { - newImplicitSet.Invalidated += (s, e) => UpdateAnimations(s as AvaloniaList, 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, visual); + // // visual.AttachedToVisualTree -= AttachedToVisualTree; + // // } + // // + // // + // // if (args.NewValue is ImplicitAnimationCollection newImplicitSet) + // // { + // // newImplicitSet.Invalidated += (s, e) => UpdateAnimations(s as AvaloniaList, 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? 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)) { diff --git a/src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimation.cs b/src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimation.cs index 4a87c220f9..afe87b77bd 100644 --- a/src/Avalonia.Base/Animation/CompositionAnimations/CompositionAnimation.cs +++ b/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 IsEnabledProperty = AvaloniaProperty.Register( 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); } diff --git a/src/Avalonia.Base/Animation/CompositionAnimations/ExplicitAnimationCollection.cs b/src/Avalonia.Base/Animation/CompositionAnimations/ExplicitAnimationCollection.cs index 1b973027f9..344c0552b0 100644 --- a/src/Avalonia.Base/Animation/CompositionAnimations/ExplicitAnimationCollection.cs +++ b/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); + // } } } } diff --git a/src/Avalonia.Base/Animation/CompositionAnimations/ImplicitAnimationCollection.cs b/src/Avalonia.Base/Animation/CompositionAnimations/ImplicitAnimationCollection.cs deleted file mode 100644 index ec58e31bcd..0000000000 --- a/src/Avalonia.Base/Animation/CompositionAnimations/ImplicitAnimationCollection.cs +++ /dev/null @@ -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 - { - 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; - } - } -} diff --git a/src/Avalonia.Base/Animation/ITransition.cs b/src/Avalonia.Base/Animation/ITransition.cs index 14973d554f..f0bfa6f920 100644 --- a/src/Avalonia.Base/Animation/ITransition.cs +++ b/src/Avalonia.Base/Animation/ITransition.cs @@ -11,6 +11,20 @@ namespace Avalonia.Animation { } + [NotClientImplementable, PrivateApi] + public interface ICompositionTransition : ITransition + { + /// + /// Occurs when the transition is invalidated and needs to be re-applied. + /// + event EventHandler? AnimationInvalidated; + + /// + /// Gets the composition animation for the specified parent visual. + /// + Rendering.Composition.Animations.CompositionAnimation? GetCompositionAnimation(Visual parent); + } + [NotClientImplementable, PrivateApi] public interface IPropertyTransition : ITransition { diff --git a/src/Avalonia.Base/Animation/Transitions.cs b/src/Avalonia.Base/Animation/Transitions.cs index 0855fd3a63..d0c01eb788 100644 --- a/src/Avalonia.Base/Animation/Transitions.cs +++ b/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; + } } } diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs index ee960cee1e..1621c70181 100644 --- a/src/Avalonia.Base/Visual.cs +++ b/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); diff --git a/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml index af3baef19a..a59c99de5c 100644 --- a/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml +++ b/src/Avalonia.Themes.Fluent/Accents/BaseResources.xaml @@ -54,7 +54,7 @@ 12,0,12,0 - + @@ -67,7 +67,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml b/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml index d0fe5a3346..a7cee77aaf 100644 --- a/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/RefreshVisualizer.xaml @@ -14,7 +14,7 @@