Skip to content

Commit e743f04

Browse files
Merge pull request #3843 from Sergio0694/feature/implicit-animation-binding
Feature/implicit animation binding
2 parents 14cbe9f + 4bc18c7 commit e743f04

File tree

5 files changed

+129
-48
lines changed

5 files changed

+129
-48
lines changed

Microsoft.Toolkit.Uwp.UI.Animations/Implicit.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using Windows.Foundation.Collections;
5+
using System;
66
using Windows.UI.Xaml;
77
using Windows.UI.Xaml.Hosting;
88

@@ -130,30 +130,30 @@ public static void SetAnimations(UIElement element, ImplicitAnimationSet value)
130130
/// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for the current event.</param>
131131
private static void OnShowAnimationsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
132132
{
133-
static void OnAnimationsChanged(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
133+
static void OnAnimationsChanged(object sender, EventArgs e)
134134
{
135135
var collection = (ImplicitAnimationSet)sender;
136136

137137
if (collection.ParentReference!.TryGetTarget(out UIElement element))
138138
{
139-
ElementCompositionPreview.SetImplicitShowAnimation(element, collection.GetCompositionAnimationGroup());
139+
ElementCompositionPreview.SetImplicitShowAnimation(element, collection.GetCompositionAnimationGroup(element));
140140
}
141141
}
142142

143143
if (e.OldValue is ImplicitAnimationSet oldCollection)
144144
{
145-
oldCollection.VectorChanged -= OnAnimationsChanged;
145+
oldCollection.AnimationsChanged -= OnAnimationsChanged;
146146
}
147147

148148
if (d is UIElement element &&
149149
e.NewValue is ImplicitAnimationSet collection)
150150
{
151151
collection.ParentReference = new(element);
152-
collection.VectorChanged -= OnAnimationsChanged;
153-
collection.VectorChanged += OnAnimationsChanged;
152+
collection.AnimationsChanged -= OnAnimationsChanged;
153+
collection.AnimationsChanged += OnAnimationsChanged;
154154

155155
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
156-
ElementCompositionPreview.SetImplicitShowAnimation(element, collection.GetCompositionAnimationGroup());
156+
ElementCompositionPreview.SetImplicitShowAnimation(element, collection.GetCompositionAnimationGroup(element));
157157
}
158158
}
159159

@@ -164,30 +164,30 @@ static void OnAnimationsChanged(IObservableVector<DependencyObject> sender, IVec
164164
/// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for the current event.</param>
165165
private static void OnHideAnimationsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
166166
{
167-
static void OnAnimationsChanged(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
167+
static void OnAnimationsChanged(object sender, EventArgs e)
168168
{
169169
var collection = (ImplicitAnimationSet)sender;
170170

171171
if (collection.ParentReference!.TryGetTarget(out UIElement element))
172172
{
173-
ElementCompositionPreview.SetImplicitHideAnimation(element, collection.GetCompositionAnimationGroup());
173+
ElementCompositionPreview.SetImplicitHideAnimation(element, collection.GetCompositionAnimationGroup(element));
174174
}
175175
}
176176

177177
if (e.OldValue is ImplicitAnimationSet oldCollection)
178178
{
179-
oldCollection.VectorChanged -= OnAnimationsChanged;
179+
oldCollection.AnimationsChanged -= OnAnimationsChanged;
180180
}
181181

182182
if (d is UIElement element &&
183183
e.NewValue is ImplicitAnimationSet collection)
184184
{
185185
collection.ParentReference = new(element);
186-
collection.VectorChanged -= OnAnimationsChanged;
187-
collection.VectorChanged += OnAnimationsChanged;
186+
collection.AnimationsChanged -= OnAnimationsChanged;
187+
collection.AnimationsChanged += OnAnimationsChanged;
188188

189189
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
190-
ElementCompositionPreview.SetImplicitHideAnimation(element, collection.GetCompositionAnimationGroup());
190+
ElementCompositionPreview.SetImplicitHideAnimation(element, collection.GetCompositionAnimationGroup(element));
191191
}
192192
}
193193

@@ -198,30 +198,30 @@ static void OnAnimationsChanged(IObservableVector<DependencyObject> sender, IVec
198198
/// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance for the current event.</param>
199199
private static void OnAnimationsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
200200
{
201-
static void OnAnimationsChanged(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
201+
static void OnAnimationsChanged(object sender, EventArgs e)
202202
{
203203
var collection = (ImplicitAnimationSet)sender;
204204

205205
if (collection.ParentReference!.TryGetTarget(out UIElement element))
206206
{
207-
ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = collection.GetImplicitAnimationCollection();
207+
ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = collection.GetImplicitAnimationCollection(element);
208208
}
209209
}
210210

211211
if (e.OldValue is ImplicitAnimationSet oldCollection)
212212
{
213-
oldCollection.VectorChanged -= OnAnimationsChanged;
213+
oldCollection.AnimationsChanged -= OnAnimationsChanged;
214214
}
215215

216216
if (d is UIElement element &&
217217
e.NewValue is ImplicitAnimationSet collection)
218218
{
219219
collection.ParentReference = new(element);
220-
collection.VectorChanged -= OnAnimationsChanged;
221-
collection.VectorChanged += OnAnimationsChanged;
220+
collection.AnimationsChanged -= OnAnimationsChanged;
221+
collection.AnimationsChanged += OnAnimationsChanged;
222222

223223
ElementCompositionPreview.SetIsTranslationEnabled(element, true);
224-
ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = collection.GetImplicitAnimationCollection();
224+
ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = collection.GetImplicitAnimationCollection(element);
225225
}
226226
}
227227
}

Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/Animation.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,14 +91,14 @@ public EasingMode? EasingMode
9191
/// </summary>
9292
public RepeatOption Repeat
9393
{
94-
get => (RepeatOption)GetValue(RepeatOptionProperty);
95-
set => SetValue(RepeatOptionProperty, value);
94+
get => (RepeatOption)GetValue(RepeatProperty);
95+
set => SetValue(RepeatProperty, value);
9696
}
9797

9898
/// <summary>
9999
/// Identifies the <seealso cref="Repeat"/> dependency property.
100100
/// </summary>
101-
public static readonly DependencyProperty RepeatOptionProperty = DependencyProperty.Register(
101+
public static readonly DependencyProperty RepeatProperty = DependencyProperty.Register(
102102
nameof(Repeat),
103103
typeof(RepeatOption),
104104
typeof(Animation),

Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/ImplicitAnimation{TValue,TKeyFrame}.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#nullable enable
66

7+
using System;
78
using Windows.UI.Composition;
89
using Windows.UI.Xaml;
910
using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions;
@@ -17,6 +18,25 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
1718
public abstract class ImplicitAnimation<TValue, TKeyFrame> : Animation<TValue, TKeyFrame>, IImplicitTimeline
1819
where TKeyFrame : unmanaged
1920
{
21+
/// <inheritdoc/>
22+
public event EventHandler? AnimationPropertyChanged;
23+
24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="ImplicitAnimation{TValue, TKeyFrame}"/> class.
26+
/// </summary>
27+
protected ImplicitAnimation()
28+
{
29+
RegisterPropertyChangedCallback(DelayProperty, RaiseAnimationPropertyChanged);
30+
RegisterPropertyChangedCallback(DurationProperty, RaiseAnimationPropertyChanged);
31+
RegisterPropertyChangedCallback(EasingTypeProperty, RaiseAnimationPropertyChanged);
32+
RegisterPropertyChangedCallback(EasingModeProperty, RaiseAnimationPropertyChanged);
33+
RegisterPropertyChangedCallback(RepeatProperty, RaiseAnimationPropertyChanged);
34+
RegisterPropertyChangedCallback(DelayBehaviorProperty, RaiseAnimationPropertyChanged);
35+
RegisterPropertyChangedCallback(ToProperty, RaiseAnimationPropertyChanged);
36+
RegisterPropertyChangedCallback(FromProperty, RaiseAnimationPropertyChanged);
37+
RegisterPropertyChangedCallback(KeyFramesProperty, RaiseAnimationPropertyChanged);
38+
}
39+
2040
/// <summary>
2141
/// Gets or sets the optional implicit target for the animation. This can act as a trigger property for the animation.
2242
/// </summary>
@@ -67,5 +87,15 @@ public CompositionAnimation GetAnimation(UIElement element, out string? target)
6787

6888
return builder.GetAnimation(element.GetVisual(), out _);
6989
}
90+
91+
/// <summary>
92+
/// Raises the <see cref="AnimationPropertyChanged"/> event.
93+
/// </summary>
94+
/// <param name="sender">The instance raising the event.</param>
95+
/// <param name="property">The <see cref="DependencyProperty"/> that was changed.</param>
96+
private void RaiseAnimationPropertyChanged(DependencyObject sender, DependencyProperty property)
97+
{
98+
AnimationPropertyChanged?.Invoke(this, EventArgs.Empty);
99+
}
70100
}
71101
}

Microsoft.Toolkit.Uwp.UI.Animations/Xaml/ImplicitAnimationSet.cs

Lines changed: 69 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,81 @@
66

77
using System;
88
using System.Diagnostics.Contracts;
9+
using Windows.Foundation.Collections;
910
using Windows.UI.Composition;
1011
using Windows.UI.Xaml;
1112
using Windows.UI.Xaml.Hosting;
1213

1314
namespace Microsoft.Toolkit.Uwp.UI.Animations
1415
{
1516
/// <summary>
16-
/// A collection of implicit animations that can be grouped together. This type represents a composite animation
17-
/// (such as <see cref="Windows.UI.Xaml.Media.Animation.Storyboard"/>) that is executed on a given element.
17+
/// A collection of implicit animations that can be assigned to a <see cref="UIElement"/> and configured to be run automatically
18+
/// when the element is either shown or hidden (through <see cref="Implicit.ShowAnimationsProperty"/> and <see cref="Implicit.HideAnimationsProperty"/>),
19+
/// or whenever one of the targeted properties on the underlying <see cref="Visual"/> element changes (through <see cref="Implicit.AnimationsProperty"/>).
20+
/// <para>
21+
/// Animations within an <see cref="ImplicitAnimationSet"/> should be objects implementing the <see cref="IImplicitTimeline"/> interface, such as
22+
/// types inheriting from <see cref="ImplicitAnimation{TValue, TKeyFrame}"/> (eg. <see cref="OpacityAnimation"/>, <see cref="TranslationAnimation"/>,
23+
/// <see cref="OffsetAnimation"/> and <see cref="ScaleAnimation"/>, or custom ones such as <see cref="ScalarAnimation"/> and <see cref="Vector3Animation"/>).
24+
/// Adding incompatible elements cannot be validated at build-time, but will result in a runtime crash.
25+
/// </para>
26+
/// <para>
27+
/// Animations will monitor for changes in real-time to any of their public properties. For instance, if a binding is used to dynamically update the
28+
/// <see cref="Animation{TValue, TKeyFrame}.To"/> or <see cref="Animation{TValue, TKeyFrame}.From"/> properties, the entire animation set will be
29+
/// initialized again and assigned to the underlying <see cref="Visual"/> object for the targeted <see cref="UIElement"/>. This does not currently apply
30+
/// to changes to the <see cref="Animation{TValue, TKeyFrame}.KeyFrames"/> property though (other than the entire property being reassigned). To achieve
31+
/// dynamic updates to animation sets in that case, either leverage expression keyframes or just use code-behind to manually reinitialize the animations.
32+
/// </para>
1833
/// </summary>
34+
/// <remarks>
35+
/// An <see cref="ImplicitAnimationSet"/> instance can only be used on a single <see cref="UIElement"/> target, and it cannot be shared across multiple
36+
/// elements. Attempting to do so will result in a runtime crash. Furthermore, it is recommended not to move <see cref="IImplicitTimeline"/> instances from
37+
/// one <see cref="ImplicitAnimationSet"/> to another, and doing so will add unnecessary runtime overhead over time. If you want to apply the same animations
38+
/// to multiple elements, simply create another <see cref="ImplicitAnimationSet"/> instance and another set of animations with the same properties within it.
39+
/// </remarks>
1940
public sealed class ImplicitAnimationSet : DependencyObjectCollection
2041
{
42+
/// <summary>
43+
/// Raised whenever any configuration change occurrs within the current <see cref="ImplicitAnimationSet"/> instance.
44+
/// </summary>
45+
internal event EventHandler? AnimationsChanged;
46+
47+
/// <summary>
48+
/// Initializes a new instance of the <see cref="ImplicitAnimationSet"/> class.
49+
/// </summary>
50+
public ImplicitAnimationSet()
51+
{
52+
VectorChanged += ImplicitAnimationSetVectorChanged;
53+
}
54+
55+
/// <summary>
56+
/// Registers <see cref="RaiseAnimationsChanged(object, EventArgs)"/> for every added animation.
57+
/// </summary>
58+
/// <param name="sender">The current vector of animations.</param>
59+
/// <param name="event">The <see cref="IVectorChangedEventArgs"/> instance for the current event.</param>
60+
private void ImplicitAnimationSetVectorChanged(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs @event)
61+
{
62+
if (@event.CollectionChange == CollectionChange.ItemInserted ||
63+
@event.CollectionChange == CollectionChange.ItemChanged)
64+
{
65+
IImplicitTimeline item = (IImplicitTimeline)sender[(int)@event.Index];
66+
67+
item.AnimationPropertyChanged -= RaiseAnimationsChanged;
68+
item.AnimationPropertyChanged += RaiseAnimationsChanged;
69+
}
70+
71+
AnimationsChanged?.Invoke(this, EventArgs.Empty);
72+
}
73+
74+
/// <summary>
75+
/// Raises the <see cref="AnimationsChanged"/> event.
76+
/// </summary>
77+
/// <param name="sender">The instance raising the event.</param>
78+
/// <param name="e">The empty <see cref="EventArgs"/> for the event.</param>
79+
private void RaiseAnimationsChanged(object sender, EventArgs e)
80+
{
81+
AnimationsChanged?.Invoke(this, e);
82+
}
83+
2184
/// <summary>
2285
/// Gets or sets the weak reference to the parent that owns the current implicit animation collection.
2386
/// </summary>
@@ -27,11 +90,11 @@ public sealed class ImplicitAnimationSet : DependencyObjectCollection
2790
/// Creates a <see cref="CompositionAnimationGroup"/> for the current collection.
2891
/// This can be used to be assigned to show/hide implicit composition animations.
2992
/// </summary>
93+
/// <param name="parent">The target <see cref="UIElement"/> to which the animations are being applied to.</param>
3094
/// <returns>The <see cref="CompositionAnimationGroup"/> instance to use.</returns>
3195
[Pure]
32-
internal CompositionAnimationGroup GetCompositionAnimationGroup()
96+
internal CompositionAnimationGroup GetCompositionAnimationGroup(UIElement parent)
3397
{
34-
UIElement parent = GetParent();
3598
Compositor compositor = ElementCompositionPreview.GetElementVisual(parent).Compositor;
3699
CompositionAnimationGroup animations = compositor.CreateAnimationGroup();
37100

@@ -47,11 +110,11 @@ internal CompositionAnimationGroup GetCompositionAnimationGroup()
47110
/// Creates an <see cref="ImplicitAnimationCollection"/> for the current collection.
48111
/// This can be used to be assigned to implicit composition animations.
49112
/// </summary>
113+
/// <param name="parent">The target <see cref="UIElement"/> to which the animations are being applied to.</param>
50114
/// <returns>The <see cref="ImplicitAnimationCollection"/> instance to use.</returns>
51115
[Pure]
52-
internal ImplicitAnimationCollection GetImplicitAnimationCollection()
116+
internal ImplicitAnimationCollection GetImplicitAnimationCollection(UIElement parent)
53117
{
54-
UIElement parent = GetParent();
55118
Compositor compositor = ElementCompositionPreview.GetElementVisual(parent).Compositor;
56119
ImplicitAnimationCollection animations = compositor.CreateImplicitAnimationCollection();
57120

@@ -71,25 +134,5 @@ internal ImplicitAnimationCollection GetImplicitAnimationCollection()
71134

72135
return animations;
73136
}
74-
75-
/// <summary>
76-
/// Gets the current parent <see cref="UIElement"/> instance.
77-
/// </summary>
78-
/// <returns>The <see cref="UIElement"/> reference from <see cref="ParentReference"/>.</returns>
79-
/// <exception cref="InvalidOperationException">Thrown if there is no parent available.</exception>
80-
[Pure]
81-
private UIElement GetParent()
82-
{
83-
UIElement? parent = null;
84-
85-
if (ParentReference?.TryGetTarget(out parent) != true)
86-
{
87-
ThrowInvalidOperationException();
88-
}
89-
90-
return parent!;
91-
92-
static void ThrowInvalidOperationException() => throw new InvalidOperationException("The current ImplicitAnimationSet object isn't bound to a parent UIElement instance.");
93-
}
94137
}
95138
}

Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IImplicitTimeline.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
using System;
56
using Windows.UI.Composition;
67
using Windows.UI.Xaml;
78

@@ -14,6 +15,13 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
1415
/// </summary>
1516
public interface IImplicitTimeline
1617
{
18+
/// <summary>
19+
/// Raised whenever a property that influences the animation changes.
20+
/// This event is used by <see cref="ImplicitAnimationSet"/> to update the animations collection
21+
/// assigned to a target <see cref="UIElement"/> when any of the individual animations is modified.
22+
/// </summary>
23+
event EventHandler? AnimationPropertyChanged;
24+
1725
/// <summary>
1826
/// Gets a <see cref="CompositionAnimation"/> from the current node. This animation might
1927
/// be used either as an implicit show/hide animation, or as a direct implicit animation.

0 commit comments

Comments
 (0)