Skip to content

Commit ca6f772

Browse files
Clean-up and simplify Shadow Animations, add support for animating all shadows
1 parent 8647919 commit ca6f772

File tree

11 files changed

+184
-56
lines changed

11 files changed

+184
-56
lines changed

Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Shadows/AnimatedCardShadowXaml.bind

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,63 @@
1313

1414
<Page.Resources>
1515
<media:AttachedCardShadow x:Key="CommonShadow" Offset="4" CornerRadius="0"/>
16+
17+
<ani:AnimationSet x:Key="ShadowEnterAnimation">
18+
<ani:OffsetDropShadowAnimation To="12"/>
19+
</ani:AnimationSet>
20+
21+
<ani:AnimationSet x:Key="ShadowExitAnimation">
22+
<ani:OffsetDropShadowAnimation To="4"/>
23+
</ani:AnimationSet>
24+
25+
<ani:AnimationSet x:Key="ShadowPopAnimation" IsSequential="True">
26+
<ani:TranslationAnimation To="-8" Duration="0:0:1"/>
27+
<ani:OffsetDropShadowAnimation To="16" Duration="0:0:2" Target="{StaticResource CommonShadow}"/>
28+
<ani:OffsetDropShadowAnimation To="4" Delay="0:0:0.5" Duration="0:0:2" Target="{StaticResource CommonShadow}"/>
29+
<ani:TranslationAnimation To="0" Duration="0:0:1"/>
30+
</ani:AnimationSet>
1631
</Page.Resources>
1732

1833
<Grid>
19-
<Image x:Name="ImageWithShadow"
20-
ui:Effects.Shadow="{StaticResource CommonShadow}"
34+
<Grid.RowDefinitions>
35+
<RowDefinition/>
36+
<RowDefinition/>
37+
</Grid.RowDefinitions>
38+
<Grid.ColumnDefinitions>
39+
<ColumnDefinition/>
40+
<ColumnDefinition/>
41+
</Grid.ColumnDefinitions>
42+
<Image ui:Effects.Shadow="{StaticResource CommonShadow}"
2143
Height="100" Width="100"
2244
Source="ms-appx:///Assets/Photos/Owl.jpg">
2345
<interactivity:Interaction.Behaviors>
2446
<interactions:EventTriggerBehavior EventName="PointerEntered">
25-
<behaviors:StartAnimationAction Animation="{Binding ElementName=ShadowEnterAnimation}"/>
47+
<behaviors:StartAnimationAction Animation="{StaticResource ShadowEnterAnimation}"/>
2648
</interactions:EventTriggerBehavior>
2749
<interactions:EventTriggerBehavior EventName="PointerExited">
28-
<behaviors:StartAnimationAction Animation="{Binding ElementName=ShadowExitAnimation}"/>
50+
<behaviors:StartAnimationAction Animation="{StaticResource ShadowExitAnimation}"/>
51+
</interactions:EventTriggerBehavior>
52+
</interactivity:Interaction.Behaviors>
53+
</Image>
54+
<Image ui:Effects.Shadow="{StaticResource CommonShadow}"
55+
Height="100" Width="100"
56+
Grid.Column="1"
57+
Source="ms-appx:///Assets/Photos/Owl.jpg">
58+
<interactivity:Interaction.Behaviors>
59+
<interactions:EventTriggerBehavior EventName="PointerEntered">
60+
<behaviors:StartAnimationAction Animation="{StaticResource ShadowEnterAnimation}"/>
61+
</interactions:EventTriggerBehavior>
62+
<interactions:EventTriggerBehavior EventName="PointerExited">
63+
<behaviors:StartAnimationAction Animation="{StaticResource ShadowExitAnimation}"/>
2964
</interactions:EventTriggerBehavior>
3065
</interactivity:Interaction.Behaviors>
31-
<ani:Explicit.Animations>
32-
<ani:AnimationSet x:Name="ShadowEnterAnimation">
33-
<ani:OffsetDropShadowAnimation From="4" To="12" Target="{Binding ElementName=ImageWithShadow}"/>
34-
</ani:AnimationSet>
35-
36-
<ani:AnimationSet x:Name="ShadowExitAnimation">
37-
<ani:OffsetDropShadowAnimation From="12" To="4" Target="{Binding ElementName=ImageWithShadow}"/>
38-
</ani:AnimationSet>
39-
</ani:Explicit.Animations>
4066
</Image>
67+
<Button Grid.Row="1" Grid.ColumnSpan="2" HorizontalAlignment="Center" VerticalAlignment="Top" Content="Click Me">
68+
<interactivity:Interaction.Behaviors>
69+
<interactions:EventTriggerBehavior EventName="Click">
70+
<behaviors:StartAnimationAction Animation="{StaticResource ShadowPopAnimation}"/>
71+
</interactions:EventTriggerBehavior>
72+
</interactivity:Interaction.Behaviors>
73+
</Button>
4174
</Grid>
4275
</Page>

Microsoft.Toolkit.Uwp.UI.Animations/Properties/AssemblyInfo.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@
44

55
using System.Runtime.CompilerServices;
66

7-
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Media")]
7+
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Behaviors")]
8+
[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Media")]

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

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
2121
/// properties. This can differ from <typeparamref name="TKeyFrame"/> to facilitate XAML parsing.
2222
/// </typeparam>
2323
/// <typeparam name="TKeyFrame">The actual type of keyframe values in use.</typeparam>
24-
public abstract class ShadowAnimation<TShadow, TValue, TKeyFrame> : Animation<TValue, TKeyFrame>
25-
where TShadow : FrameworkElement
24+
public abstract class ShadowAnimation<TShadow, TValue, TKeyFrame> : Animation<TValue, TKeyFrame>, IAttachedTimeline
25+
where TShadow : AttachedShadowBase
2626
where TKeyFrame : unmanaged
2727
{
2828
/// <summary>
@@ -46,21 +46,12 @@ public TShadow? Target
4646
/// <inheritdoc/>
4747
public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint)
4848
{
49-
if (Target is not TShadow target)
50-
{
51-
static AnimationBuilder ThrowTargetNullException() => throw new ArgumentNullException("The target element is null, make sure to set the Target property");
52-
53-
return ThrowTargetNullException();
54-
}
55-
56-
var shadowBase = Effects.GetShadow(Target);
57-
if (shadowBase == null)
58-
{
59-
static AnimationBuilder ThrowArgumentNullException() => throw new ArgumentNullException("The target's shadow is null, make sure to set the Target property to an element with a Shadow");
60-
61-
return ThrowArgumentNullException();
62-
}
49+
throw new NotSupportedException();
50+
}
6351

52+
/// <inheritdoc/>
53+
public AnimationBuilder AppendToBuilder(AnimationBuilder builder, UIElement parent, TimeSpan? delayHint = null, TimeSpan? durationHint = null, EasingType? easingTypeHint = null, EasingMode? easingModeHint = null)
54+
{
6455
if (ExplicitTarget is not string explicitTarget)
6556
{
6657
static AnimationBuilder ThrowArgumentNullException()
@@ -72,20 +63,52 @@ static AnimationBuilder ThrowArgumentNullException()
7263
return ThrowArgumentNullException();
7364
}
7465

75-
var shadow = shadowBase.GetElementContext(Target).Shadow;
66+
if (Target is TShadow allShadows)
67+
{
68+
// in this case we'll animate all the shadows being used.
69+
foreach (var context in allShadows.GetElementContextEnumerable()) //// TODO: Find better way!!!
70+
{
71+
NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
72+
explicitTarget,
73+
Delay ?? delayHint ?? DefaultDelay,
74+
Duration ?? durationHint ?? DefaultDuration,
75+
Repeat,
76+
DelayBehavior);
7677

77-
NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
78-
explicitTarget,
79-
Delay ?? delayHint ?? DefaultDelay,
80-
Duration ?? durationHint ?? DefaultDuration,
81-
Repeat,
82-
DelayBehavior);
78+
AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
8379

84-
AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
80+
CompositionAnimation animation = keyFrameBuilder.GetAnimation(context.Shadow, out _);
8581

86-
CompositionAnimation animation = keyFrameBuilder.GetAnimation(shadow, out _);
82+
builder.ExternalAnimation(context.Shadow, animation);
83+
}
84+
85+
return builder;
86+
}
87+
else
88+
{
89+
var shadowBase = Effects.GetShadow(parent as FrameworkElement);
90+
if (shadowBase == null)
91+
{
92+
static AnimationBuilder ThrowArgumentNullException() => throw new ArgumentNullException("The target's shadow is null, make sure to set the Target property to an element with a Shadow");
93+
94+
return ThrowArgumentNullException();
95+
}
96+
97+
var shadow = shadowBase.GetElementContext((FrameworkElement)parent).Shadow;
98+
99+
NormalizedKeyFrameAnimationBuilder<TKeyFrame>.Composition keyFrameBuilder = new(
100+
explicitTarget,
101+
Delay ?? delayHint ?? DefaultDelay,
102+
Duration ?? durationHint ?? DefaultDuration,
103+
Repeat,
104+
DelayBehavior);
87105

88-
return builder.ExternalAnimation(shadow, animation);
106+
AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint);
107+
108+
CompositionAnimation animation = keyFrameBuilder.GetAnimation(shadow, out _);
109+
110+
return builder.ExternalAnimation(shadow, animation);
111+
}
89112
}
90113
}
91114
}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,15 @@ public async Task StartAsync(UIElement element, CancellationToken token)
119119
{
120120
foreach (object node in this)
121121
{
122-
if (node is ITimeline timeline)
122+
if (node is IAttachedTimeline attachedTimeline)
123+
{
124+
var builder = AnimationBuilder.Create();
125+
126+
attachedTimeline.AppendToBuilder(builder, element);
127+
128+
await builder.StartAsync(element, token);
129+
}
130+
else if (node is ITimeline timeline)
123131
{
124132
var builder = AnimationBuilder.Create();
125133

@@ -166,6 +174,9 @@ public async Task StartAsync(UIElement element, CancellationToken token)
166174
{
167175
switch (node)
168176
{
177+
case IAttachedTimeline attachedTimeline:
178+
builder = attachedTimeline.AppendToBuilder(builder, element);
179+
break;
169180
case ITimeline timeline:
170181
builder = timeline.AppendToBuilder(builder);
171182
break;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using Windows.UI.Xaml;
7+
using Windows.UI.Xaml.Media.Animation;
8+
9+
namespace Microsoft.Toolkit.Uwp.UI.Animations
10+
{
11+
/// <summary>
12+
/// An interface representing a XAML model for a custom animation that requires a specific parent <see cref="UIElement"/> context.
13+
/// </summary>
14+
public interface IAttachedTimeline
15+
{
16+
/// <summary>
17+
/// Appends the current animation to a target <see cref="AnimationBuilder"/> instance.
18+
/// This method is used when the current <see cref="ITimeline"/> instance is explicitly triggered.
19+
/// </summary>
20+
/// <param name="builder">The target <see cref="AnimationBuilder"/> instance to schedule the animation on.</param>
21+
/// <param name="parent">The parent <see cref="UIElement"/> this animation will be started on.</param>
22+
/// <param name="delayHint">A hint for the animation delay, if present.</param>
23+
/// <param name="durationHint">A hint for the animation duration, if present.</param>
24+
/// <param name="easingTypeHint">A hint for the easing type, if present.</param>
25+
/// <param name="easingModeHint">A hint for the easing mode, if present.</param>
26+
/// <returns>The same <see cref="AnimationBuilder"/> instance as <paramref name="builder"/>.</returns>
27+
AnimationBuilder AppendToBuilder(
28+
AnimationBuilder builder,
29+
UIElement parent,
30+
TimeSpan? delayHint = null,
31+
TimeSpan? durationHint = null,
32+
EasingType? easingTypeHint = null,
33+
EasingMode? easingModeHint = null);
34+
}
35+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
1313
public interface ITimeline
1414
{
1515
/// <summary>
16-
/// Appens the current animation to a target <see cref="AnimationBuilder"/> instance.
16+
/// Appends the current animation to a target <see cref="AnimationBuilder"/> instance.
1717
/// This method is used when the current <see cref="ITimeline"/> instance is explicitly triggered.
1818
/// </summary>
1919
/// <param name="builder">The target <see cref="AnimationBuilder"/> instance to schedule the animation on.</param>

Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Shadows/OffsetDropShadowAnimation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations
1111
/// <summary>
1212
/// An offset animation working on the composition layer.
1313
/// </summary>
14-
public sealed class OffsetDropShadowAnimation : ShadowAnimation<FrameworkElement, string, Vector3>
14+
public sealed class OffsetDropShadowAnimation : ShadowAnimation<AttachedShadowBase, string, Vector3>
1515
{
1616
/// <inheritdoc/>
1717
protected override string ExplicitTarget => nameof(DropShadow.Offset);

Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/StartAnimationAction.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,19 @@ public object Execute(object sender, object parameter)
5858
ThrowArgumentNullException();
5959
}
6060

61+
UIElement parent = null;
62+
6163
if (TargetObject is not null)
6264
{
6365
Animation.Start(TargetObject);
6466
}
67+
else if (Animation.ParentReference?.TryGetTarget(out parent) == true) //// TODO: Tidy... apply same pattern to Activities?
68+
{
69+
Animation.Start(parent);
70+
}
6571
else
6672
{
67-
Animation.Start();
73+
Animation.Start(sender as UIElement);
6874
}
6975

7076
return null!;

Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/StopAnimationAction.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,19 @@ public object Execute(object sender, object parameter)
5858
ThrowArgumentNullException();
5959
}
6060

61+
UIElement parent = null;
62+
6163
if (TargetObject is not null)
6264
{
6365
Animation.Stop(TargetObject);
6466
}
67+
else if (Animation.ParentReference?.TryGetTarget(out parent) == true) //// TODO: Tidy...
68+
{
69+
Animation.Stop(parent);
70+
}
6571
else
6672
{
67-
Animation.Stop();
73+
Animation.Stop(sender as UIElement);
6874
}
6975

7076
return null!;

Microsoft.Toolkit.Uwp.UI/Shadows/AttachedDropShadow.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,22 +123,22 @@ private static void OnCastToPropertyChanged(DependencyObject d, DependencyProper
123123
// Need to remove all old children from previous container if it's changed
124124
if (prevContainer != null && prevContainer != shadow._container)
125125
{
126-
foreach (var context in shadow.ShadowElementContextTable)
126+
foreach (var context in shadow.GetElementContextEnumerable())
127127
{
128-
if (context.Value.IsInitialized &&
129-
prevContainer.Children.Contains(context.Value.SpriteVisual))
128+
if (context.IsInitialized &&
129+
prevContainer.Children.Contains(context.SpriteVisual))
130130
{
131-
prevContainer.Children.Remove(context.Value.SpriteVisual);
131+
prevContainer.Children.Remove(context.SpriteVisual);
132132
}
133133
}
134134
}
135135

136136
// Make sure all child shadows are hooked into container
137-
foreach (var context in shadow.ShadowElementContextTable)
137+
foreach (var context in shadow.GetElementContextEnumerable())
138138
{
139-
if (context.Value.IsInitialized)
139+
if (context.IsInitialized)
140140
{
141-
shadow.SetElementChildVisual(context.Value);
141+
shadow.SetElementChildVisual(context);
142142
}
143143
}
144144

@@ -154,12 +154,12 @@ private void CastToElement_SizeChanged(object sender, SizeChangedEventArgs e)
154154
{
155155
// Don't use sender or 'e' here as related to container element not
156156
// element for shadow, grab values off context. (Also may be null from internal call.)
157-
foreach (var context in ShadowElementContextTable)
157+
foreach (var context in GetElementContextEnumerable())
158158
{
159-
if (context.Value.IsInitialized)
159+
if (context.IsInitialized)
160160
{
161161
// TODO: Should we use ActualWidth/Height instead of RenderSize?
162-
OnSizeChanged(context.Value, context.Value.Element.RenderSize, context.Value.Element.RenderSize);
162+
OnSizeChanged(context, context.Element.RenderSize, context.Element.RenderSize);
163163
}
164164
}
165165
}

0 commit comments

Comments
 (0)