diff --git a/.editorconfig b/.editorconfig index 50eeccedc3f..36629f6b744 100644 --- a/.editorconfig +++ b/.editorconfig @@ -324,4 +324,7 @@ dotnet_diagnostic.SA1652.severity = none dotnet_diagnostic.SA1629.severity = none # DocumentationTextMustEndWithAPeriod: Let's enable this rule back when we shift to WinUI3 (v8.x). If we do it now, it would mean more than 400 file changes. dotnet_diagnostic.SA1413.severity = none # UseTrailingCommasInMultiLineInitializers: This would also mean a lot of changes at the end of all multiline initializers. It's also debatable if we want this or not. -dotnet_diagnostic.SA1314.severity = none # TypeParameterNamesMustBeginWithT: We do have a few templates that don't start with T. We need to double check that changing this is not a breaking change. If not, we can re-enable this. \ No newline at end of file +dotnet_diagnostic.SA1314.severity = none # TypeParameterNamesMustBeginWithT: We do have a few templates that don't start with T. We need to double check that changing this is not a breaking change. If not, we can re-enable this. +dotnet_diagnostic.SA1000.severity = none # Hide warnings when using the new() expression from C# 9. +dotnet_diagnostic.SA1313.severity = none # Hide warnings for record parameters. +dotnet_diagnostic.SA1101.severity = none # Hide warnings when accessing properties without "this". \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Assets/Llama.mp3 b/Microsoft.Toolkit.Uwp.SampleApp/Assets/Llama.mp3 new file mode 100644 index 00000000000..20fa5514349 Binary files /dev/null and b/Microsoft.Toolkit.Uwp.SampleApp/Assets/Llama.mp3 differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj index d89fc02aa6b..b0e9f84eb42 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj +++ b/Microsoft.Toolkit.Uwp.SampleApp/Microsoft.Toolkit.Uwp.SampleApp.csproj @@ -112,9 +112,6 @@ 10.1901.28001 - - 6.1.0-build.6 - 2.4.2 @@ -135,6 +132,7 @@ + @@ -269,6 +267,7 @@ + @@ -291,7 +290,6 @@ - @@ -300,7 +298,6 @@ - @@ -322,7 +319,6 @@ - @@ -333,18 +329,14 @@ - - - - + - @@ -377,11 +369,7 @@ - - - - @@ -393,13 +381,7 @@ - - - - - - @@ -418,9 +400,7 @@ - - - + Designer @@ -437,8 +417,6 @@ - - Designer @@ -608,6 +586,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -811,9 +810,6 @@ BladePage.xaml - - BlurBehaviorPage.xaml - AlignmentGridPage.xaml @@ -823,9 +819,6 @@ MicrosoftTranslatorPage.xaml - - SaturationBehaviorPage.xaml - TileControlPage.xaml @@ -846,9 +839,6 @@ - - LightBehaviorPage.xaml - LinkedInPage.xaml @@ -864,8 +854,8 @@ PrintHelperPage.xaml - - ReorderGridPage.xaml + + ItemsReorderAnimationPage.xaml RotatorTilePage.xaml @@ -894,12 +884,6 @@ TwitterPage.xaml - - OffsetBehaviorPage.xaml - - - FadeBehaviorPage.xaml - ImageExPage.xaml @@ -913,12 +897,6 @@ WeatherLiveTileAndToastPage.xaml - - RotateBehaviorPage.xaml - - - ScaleBehaviorPage.xaml - RadialGaugePage.xaml @@ -1256,10 +1234,6 @@ Designer MSBuild:Compile - - MSBuild:Compile - Designer - MSBuild:Compile Designer @@ -1276,10 +1250,6 @@ MSBuild:Compile Designer - - Designer - MSBuild:Compile - MSBuild:Compile Designer @@ -1308,10 +1278,6 @@ MSBuild:Compile Designer - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -1332,7 +1298,7 @@ MSBuild:Compile Designer - + Designer MSBuild:Compile @@ -1344,14 +1310,6 @@ MSBuild:Compile Designer - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -1404,14 +1362,6 @@ MSBuild:Compile Designer - - Designer - MSBuild:Compile - - - Designer - MSBuild:Compile - Designer MSBuild:Compile @@ -1486,6 +1436,10 @@ {1ae2cb5c-58a0-4f12-8e6f-2cd4aaadb34c} Microsoft.Toolkit.Uwp.Samples.BackgroundTasks + + {d4ff799d-0df2-495a-adc9-3bbc4aef8971} + Microsoft.Toolkit.Uwp.UI.Behaviors + {daeb9cec-c817-33b2-74b2-bc379380db72} Microsoft.Toolkit.Uwp.UI.Controls.DataGrid diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs b/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs index 0707df494f7..1db3fbd9e7e 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Models/Sample.cs @@ -16,8 +16,6 @@ using System.Text.Json.Serialization; using System.Text.RegularExpressions; using System.Threading.Tasks; -using Microsoft.Toolkit.Graph.Converters; -using Microsoft.Toolkit.Graph.Providers; using Microsoft.Toolkit.Uwp.Helpers; using Microsoft.Toolkit.Uwp.Input.GazeInteraction; using Microsoft.Toolkit.Uwp.SampleApp.Models; @@ -660,16 +658,16 @@ private static Type LookForTypeByName(string typeName) } // Search in Microsoft.Toolkit.Graph.Controls - var graphControlsProxyType = typeof(UserToPersonConverter); - assembly = graphControlsProxyType.GetTypeInfo().Assembly; - - foreach (var typeInfo in assembly.ExportedTypes) - { - if (typeInfo.Name == typeName) - { - return typeInfo; - } - } + //var graphControlsProxyType = typeof(UserToPersonConverter); + //assembly = graphControlsProxyType.GetTypeInfo().Assembly; + + //foreach (var typeInfo in assembly.ExportedTypes) + //{ + // if (typeInfo.Name == typeName) + // { + // return typeInfo; + // } + //} // Search in Microsoft.Toolkit.Uwp.UI.Animations var animationsProxyType = EasingType.Default; diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Pages/About.xaml b/Microsoft.Toolkit.Uwp.SampleApp/Pages/About.xaml index 0607623cd30..bda1ccfa186 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Pages/About.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/Pages/About.xaml @@ -1,158 +1,176 @@  - - - + + + - - + - + Click="RecentSample_Click" + Style="{StaticResource AboutHyperlinkButtonStyle}"> + - - - + + + - - + - + + Text="{x:Bind Published.ToString('MMM d', {x:Null})}" /> - - + - + Style="{StaticResource AboutHyperlinkButtonStyle}"> + - + - + + ShadowOpacity="0" + Color="Black"> - - + + - + - + - + - + - + - - + + - - - - - - + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - + + + + - + Recent Activity - + - + ItemsPanel="{StaticResource ItemsWrapGridHorizontalTemplate}" + ItemsSource="{x:Bind RecentSamples, Mode=OneWay}" /> @@ -161,36 +179,49 @@ Grid.Row="3" Grid.RowSpan="3" Grid.Column="4" - ItemsPanel="{StaticResource ItemsWrapGridHorizontalTemplate}" - animations:Implicit.Animations="{StaticResource ImplicitOffset}"> - - - - + animations:Implicit.Animations="{StaticResource ImplicitOffset}" + ItemsPanel="{StaticResource ItemsWrapGridHorizontalTemplate}"> + + + + - + Release Notes - + + ItemsSource="{x:Bind LandingPageLinks.Resources, Mode=OneWay}"> - - - + + + - + @@ -204,18 +235,25 @@ + animations:Implicit.Animations="{StaticResource ImplicitOffset}" + BorderBrush="{ThemeResource Border-AboutPage-Horizontal}" + BorderThickness="0,1,0,0" /> - - + animations:Implicit.Animations="{StaticResource ImplicitOffset}" + BorderBrush="{ThemeResource Border-AboutPage-Horizontal}" + BorderThickness="0,1,0,0" /> + + Privacy statement @@ -224,12 +262,20 @@ - - + + - - + + @@ -267,7 +313,7 @@ - + @@ -303,7 +349,7 @@ - + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Pages/About.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/Pages/About.xaml.cs index 2040485db8b..6546c73a9a0 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Pages/About.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Pages/About.xaml.cs @@ -151,8 +151,7 @@ private async Task Init() From = 0, To = 1, Duration = TimeSpan.FromMilliseconds(300), - Delay = TimeSpan.FromMilliseconds(counter++ * delay), - SetInitialValueBeforeDelay = true + Delay = TimeSpan.FromMilliseconds(counter++ * delay) }); } } diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Activities/InvokeActionsActivity.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Activities/InvokeActionsActivity.bind new file mode 100644 index 00000000000..94b95d6851e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Activities/InvokeActionsActivity.bind @@ -0,0 +1,54 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Activities/StartAnimationActivity.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Activities/StartAnimationActivity.bind new file mode 100644 index 00000000000..29a56db3b85 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Activities/StartAnimationActivity.bind @@ -0,0 +1,50 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehavior.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/BlurBehavior.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehavior.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/BlurBehavior.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/BlurBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/BlurBehaviorCode.bind new file mode 100644 index 00000000000..8e2c5c48386 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/BlurBehaviorCode.bind @@ -0,0 +1,14 @@ +// XAML UIElement + + +// C# - Blur can be applied to any UIElement. In this case it is an image called ToolkitLogo. +using Microsoft.Toolkit.Uwp.UI.Animations; + +// Create and attacha a sprite visual with an animatable blur effect +var sprite = await PipelineBuilder + .FromBackdrop() + .Blur(0, out EffectAnimation blurAnimation) + .AttachAsync(ToolkitLogo, ToolkitLogo); + +// Start the animation on the applied brush +blurAnimation(sprite.Brush, 32, TimeSpan.FromSeconds(3)); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/BlurBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/BlurBehaviorXaml.bind new file mode 100644 index 00000000000..3425f482504 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/BlurBehaviorXaml.bind @@ -0,0 +1,42 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehavior.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/OffsetBehavior.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehavior.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/OffsetBehavior.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/OffsetBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/OffsetBehaviorCode.bind new file mode 100644 index 00000000000..6a20d6a0e78 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/OffsetBehaviorCode.bind @@ -0,0 +1,12 @@ +// XAML UIElement + + +// C# - Offset can be applied to any UIElement. In this case it is an image called ToolkitLogo. +using Microsoft.Toolkit.Uwp.UI.Animations; + +// Create and start the animation. Note that animating the offset will +// change how the item is also positioned within its parent item. If you +// want to animate the position of an element with respect to its original +// relative position to its parent, use a translation animation instead. +await AnimationBuilder.Create().Offset(to: new Vector2(20, 30), duration: TimeSpan.FromSeconds(3)).StartAsync(ToolkitLogo); + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/OffsetBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/OffsetBehaviorXaml.bind new file mode 100644 index 00000000000..3e46cc4021d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/OffsetBehaviorXaml.bind @@ -0,0 +1,35 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehavior.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/RotateBehavior.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehavior.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/RotateBehavior.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/RotateBehaviorCode.bind similarity index 67% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorCode.bind rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/RotateBehaviorCode.bind index a7f9916c585..d19ef0f5e87 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/RotateBehaviorCode.bind @@ -4,7 +4,4 @@ // C# - Rotate can be applied to any UIElement. In this case it is an image called ToolkitLogo. using Microsoft.Toolkit.Uwp.UI.Animations; -await ToolkitLogo.Rotate(value: 90.0f, - centerX: 0.5f, - centerY: 0.0f, - duration: 10, delay: 0).StartAsync(); +await AnimationBuilder.Create().RotationInDegrees(to: 90, duration: TimeSpan.FromSeconds(3)).StartAsync(ToolkitLogo); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/RotateBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/RotateBehaviorXaml.bind new file mode 100644 index 00000000000..0e7450e72d9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/RotateBehaviorXaml.bind @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehavior.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/SaturationBehavior.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehavior.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/SaturationBehavior.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/SaturationBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/SaturationBehaviorCode.bind new file mode 100644 index 00000000000..5d89f4bb8a3 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/SaturationBehaviorCode.bind @@ -0,0 +1,14 @@ +// XAML UIElement + + +// C# - Saturation can be applied to any UIElement. In this case it is an image called ToolkitLogo. +using Microsoft.Toolkit.Uwp.UI.Animations; + +// Create and attacha a sprite visual with an animatable blur effect +var sprite = await PipelineBuilder + .FromBackdrop() + .Saturation(0, out EffectAnimation saturationAnimation) + .AttachAsync(ToolkitLogo, ToolkitLogo); + +// Start the animation on the applied brush +saturationAnimation(sprite.Brush, 1, TimeSpan.FromSeconds(3)); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/SaturationBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/SaturationBehaviorXaml.bind new file mode 100644 index 00000000000..bd6836b829f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/SaturationBehaviorXaml.bind @@ -0,0 +1,42 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehavior.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/ScaleBehavior.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehavior.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/ScaleBehavior.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/ScaleBehaviorCode.bind similarity index 64% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorCode.bind rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/ScaleBehaviorCode.bind index ebe875e1dca..4b80509233d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/ScaleBehaviorCode.bind @@ -4,8 +4,4 @@ // C# - Scale can be applied to any UIElement. In this case it is an image called ToolkitLogo. using Microsoft.Toolkit.Uwp.UI.Animations; -await ToolkitLogo.Scale(centerX: 0.5f, - centerY: 0.0f, - scaleX: 2.0f, - scaleY: 0.1f, - duration: 10, delay: 0).StartAsync(); +await AnimationBuilder.Create().Scale(to: new Vector2(1.4f), duration: TimeSpan.FromSeconds(3)).StartAsync(ToolkitLogo); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/ScaleBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/ScaleBehaviorXaml.bind new file mode 100644 index 00000000000..acd6ab9b79b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Behaviors/ScaleBehaviorXaml.bind @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/EffectAnimations.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/EffectAnimations.bind new file mode 100644 index 00000000000..1965efbf2b2 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/EffectAnimations.bind @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehavior.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/FadeBehavior.png similarity index 100% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehavior.png rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/FadeBehavior.png diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/FadeBehaviorCode.bind similarity index 64% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorCode.bind rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/FadeBehaviorCode.bind index 09ff45a2829..f915f00e0a3 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/FadeBehaviorCode.bind @@ -4,5 +4,6 @@ // C# - Fade can be applied to any UIElement. In this case it is an image called ToolkitLogo. using Microsoft.Toolkit.Uwp.UI.Animations; -await ToolkitLogo.Fade(value: 0.5f, duration: 10, delay: 0).StartAsync(); +// Create and start the animation +await AnimationBuilder.Create().Opacity(to: 0.5, duration: TimeSpan.FromSeconds(3)).StartAsync(ToolkitLogo); diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/FadeBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/FadeBehaviorXaml.bind new file mode 100644 index 00000000000..1a0f0eb9081 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Animations/Effects/FadeBehaviorXaml.bind @@ -0,0 +1,36 @@ + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorCode.bind deleted file mode 100644 index b901a0f9cc1..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorCode.bind +++ /dev/null @@ -1,8 +0,0 @@ -// XAML UIElement - - -// C# - Blur can be applied to any UIElement. In this case it is an image called ToolkitLogo. -using Microsoft.Toolkit.Uwp.UI.Animations; - -await ToolkitLogo.Blur(value: 10, duration: 10, delay: 0).StartAsync(); - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorPage.xaml deleted file mode 100644 index 8c6ef1f0512..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorPage.xaml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorPage.xaml.cs deleted file mode 100644 index 80e76c0dbd2..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorPage.xaml.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - /// - /// A page that shows how to use the blur behavior. - /// - public sealed partial class BlurBehaviorPage : IXamlRenderListener - { - private Blur _blurBehavior; - - /// - /// Initializes a new instance of the class. - /// - public BlurBehaviorPage() - { - InitializeComponent(); - - SampleController.Current.RegisterNewCommand("Apply", (s, e) => - { - _blurBehavior?.StartAnimation(); - }); - } - - public void OnXamlRendered(FrameworkElement control) - { - if (control.FindChildByName("EffectElement") is FrameworkElement element) - { - var behaviors = Interaction.GetBehaviors(element); - _blurBehavior = behaviors.FirstOrDefault(item => item is Blur) as Blur; - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorXaml.bind deleted file mode 100644 index a81058031ab..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Blur/BlurBehaviorXaml.bind +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorPage.xaml deleted file mode 100644 index 0d85c6bb013..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorPage.xaml +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorPage.xaml.cs deleted file mode 100644 index 6b6608d7079..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorPage.xaml.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - /// - /// A page that shows how to use the opacity behavior. - /// - public sealed partial class FadeBehaviorPage : Page, IXamlRenderListener - { - private Fade _fadeBehavior; - - /// - /// Initializes a new instance of the class. - /// - public FadeBehaviorPage() - { - InitializeComponent(); - - SampleController.Current.RegisterNewCommand("Apply", (s, e) => - { - _fadeBehavior?.StartAnimation(); - }); - } - - public void OnXamlRendered(FrameworkElement control) - { - if (control.FindChildByName("EffectElement") is FrameworkElement element) - { - var behaviors = Interaction.GetBehaviors(element); - _fadeBehavior = behaviors.FirstOrDefault(item => item is Fade) as Fade; - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorXaml.bind deleted file mode 100644 index 2659a3b157a..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Fade/FadeBehaviorXaml.bind +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FadeHeader/FadeHeaderBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FadeHeader/FadeHeaderBehaviorPage.xaml index a2ae6567063..ce25561b9d0 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FadeHeader/FadeHeaderBehaviorPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FadeHeader/FadeHeaderBehaviorPage.xaml @@ -7,12 +7,6 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> - - - - - - - - + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FadeHeader/FadeHeaderBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FadeHeader/FadeHeaderBehaviorXaml.bind index 851181f5164..60ffa5965b2 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FadeHeader/FadeHeaderBehaviorXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/FadeHeader/FadeHeaderBehaviorXaml.bind @@ -3,7 +3,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" - xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Animations.Behaviors" + xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors" mc:Ignorable="d"> @@ -24,7 +24,7 @@ Margin="12" /> diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/IconExtensions/IconExtensionsPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/IconExtensions/IconExtensionsPage.xaml.cs index 0311ad04acf..35971fb6a53 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/IconExtensions/IconExtensionsPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/IconExtensions/IconExtensionsPage.xaml.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; using Windows.UI.Xaml; namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsCode.bind index 71ab3ffa96e..542f84e2fe3 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsCode.bind @@ -19,24 +19,24 @@ extensions:VisualExtensions.CenterPoint="50,50,0"> - - + + - + - - + + - - - - - + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsPage.xaml index 8ae037ba91f..e9a7b685520 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Implicit Animations/ImplicitAnimationsPage.xaml @@ -7,29 +7,36 @@ xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions" xmlns:animations="using:Microsoft.Toolkit.Uwp.UI.Animations" mc:Ignorable="d"> + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ReorderGridAnimation/ReorderGrid.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ItemsReorderAnimation/ItemsReorderAnimation.bind similarity index 90% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ReorderGridAnimation/ReorderGrid.bind rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ItemsReorderAnimation/ItemsReorderAnimation.bind index 5e1f9a3e0d4..f1e5a995f5e 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ReorderGridAnimation/ReorderGrid.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ItemsReorderAnimation/ItemsReorderAnimation.bind @@ -9,7 +9,7 @@ + animations:ItemsReorderAnimation.Duration="@[Duration:TimeSpan:400:0-800]"> - + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ReorderGridAnimation/ReorderGridPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ItemsReorderAnimation/ItemsReorderAnimationPage.xaml.cs similarity index 85% rename from Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ReorderGridAnimation/ReorderGridPage.xaml.cs rename to Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ItemsReorderAnimation/ItemsReorderAnimationPage.xaml.cs index dadb81e7ef4..f55559e57b5 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ReorderGridAnimation/ReorderGridPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ItemsReorderAnimation/ItemsReorderAnimationPage.xaml.cs @@ -8,11 +8,11 @@ namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages { - public sealed partial class ReorderGridPage : Page, IXamlRenderListener + public sealed partial class ItemsReorderAnimationPage : Page, IXamlRenderListener { private GridView imageView; - public ReorderGridPage() + public ItemsReorderAnimationPage() { InitializeComponent(); } diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehavior.png b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehavior.png deleted file mode 100644 index b2235f1b9e4..00000000000 Binary files a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehavior.png and /dev/null differ diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorCode.bind deleted file mode 100644 index 4019e9a2ce7..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorCode.bind +++ /dev/null @@ -1,8 +0,0 @@ -// XAML UIElement - - -// C# - Light can be applied to any UIElement. In this case it is an image called ToolkitLogo. -using Microsoft.Toolkit.Uwp.UI.Animations; - -var lightAnimationSet = ToolkitLogo.Light(distance: 10, duration: 500, delay: 0, color: Colors.Red); -await lightAnimationSet.StartAsync(); \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml deleted file mode 100644 index 276a3166388..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml.cs deleted file mode 100644 index a731da773ad..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorPage.xaml.cs +++ /dev/null @@ -1,47 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - /// - /// A page that shows how to use the light behavior. - /// - public sealed partial class LightBehaviorPage : IXamlRenderListener - { -#pragma warning disable CS0618 // Type or member is obsolete - private Light _lightBehavior; -#pragma warning restore CS0618 // Type or member is obsolete - - /// - /// Initializes a new instance of the class. - /// - public LightBehaviorPage() - { - this.InitializeComponent(); - - SampleController.Current.RegisterNewCommand("Apply", (s, e) => - { - _lightBehavior?.StartAnimation(); - }); - } - - public void OnXamlRendered(FrameworkElement control) - { - if (control.FindChildByName("EffectElement") is FrameworkElement element) - { - var behaviors = Interaction.GetBehaviors(element); -#pragma warning disable CS0618 // Type or member is obsolete - _lightBehavior = behaviors.FirstOrDefault(item => item is Light) as Light; -#pragma warning restore CS0618 // Type or member is obsolete - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorXaml.bind deleted file mode 100644 index 2ca51bf1cfc..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Light/LightBehaviorXaml.bind +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/LoginButton/LoginButtonPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/LoginButton/LoginButtonPage.xaml index cd37f20d156..ca429d0df3f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/LoginButton/LoginButtonPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/LoginButton/LoginButtonPage.xaml @@ -1,19 +1,19 @@  - + - - + + - + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorCode.bind deleted file mode 100644 index 8542d806b02..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorCode.bind +++ /dev/null @@ -1,10 +0,0 @@ -// XAML UIElement - - -// C# - Offset can be applied to any UIElement. In this case it is an image called ToolkitLogo. -using Microsoft.Toolkit.Uwp.UI.Animations; - -await ToolkitLogo.Offset(offsetX: 1.0f, - offsetY: 1.1f, - duration: 10, delay: 0).StartAsync(); - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorPage.xaml deleted file mode 100644 index 25f1c57dc3c..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorPage.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorPage.xaml.cs deleted file mode 100644 index 66b0284957c..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorPage.xaml.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - /// - /// A page that shows how to use the offset behavior. - /// - public sealed partial class OffsetBehaviorPage : IXamlRenderListener - { - private Offset _offsetBehavior; - - /// - /// Initializes a new instance of the class. - /// - public OffsetBehaviorPage() - { - InitializeComponent(); - - SampleController.Current.RegisterNewCommand("Apply", (s, e) => - { - _offsetBehavior?.StartAnimation(); - }); - } - - public void OnXamlRendered(FrameworkElement control) - { - if (control.FindChildByName("EffectElement") is FrameworkElement element) - { - var behaviors = Interaction.GetBehaviors(element); - _offsetBehavior = behaviors.FirstOrDefault(item => item is Offset) as Offset; - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorXaml.bind deleted file mode 100644 index 1a1bbb23e30..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Offset/OffsetBehaviorXaml.bind +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/OnDevice/OnDevicePage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/OnDevice/OnDevicePage.xaml.cs index 9943ccb283d..274c96025f0 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/OnDevice/OnDevicePage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/OnDevice/OnDevicePage.xaml.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; using Windows.UI.Xaml; namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PeoplePicker/PeoplePickerPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PeoplePicker/PeoplePickerPage.xaml index 829819c6c95..c4da0eaa5ae 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PeoplePicker/PeoplePickerPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PeoplePicker/PeoplePickerPage.xaml @@ -7,10 +7,12 @@ xmlns:wgt="using:Microsoft.Toolkit.Graph.Controls" mc:Ignorable="d"> - + - + - + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PersonView/PersonViewPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PersonView/PersonViewPage.xaml index 702efbf97d2..fc19d88e848 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PersonView/PersonViewPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PersonView/PersonViewPage.xaml @@ -5,8 +5,8 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wgt="using:Microsoft.Toolkit.Graph.Controls" mc:Ignorable="d"> - + - + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml index 7a934c95530..b191e57511f 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushPage.xaml @@ -1,57 +1,60 @@ - + - + - + - + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushXaml.bind index 65233b7e255..e37199d4a34 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/PipelineBrush/PipelineBrushXaml.bind @@ -3,8 +3,7 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:brushes="using:Microsoft.Toolkit.Uwp.UI.Media" - xmlns:effects="using:Microsoft.Toolkit.Uwp.UI.Media.Effects" + xmlns:media="using:Microsoft.Toolkit.Uwp.UI.Media" mc:Ignorable="d"> @@ -20,15 +19,15 @@ Grid.Column="2" Height="400"> - - - - - - - - - + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorPage.xaml deleted file mode 100644 index 7dc91aac97d..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorPage.xaml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorPage.xaml.cs deleted file mode 100644 index b814ab90d11..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorPage.xaml.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - /// - /// A page that shows how to use the Rotation behavior. - /// - public sealed partial class RotateBehaviorPage : IXamlRenderListener - { - private Rotate _rotateBehavior; - - /// - /// Initializes a new instance of the class. - /// - public RotateBehaviorPage() - { - InitializeComponent(); - - SampleController.Current.RegisterNewCommand("Apply", (s, e) => - { - _rotateBehavior?.StartAnimation(); - }); - } - - public void OnXamlRendered(FrameworkElement control) - { - if (control.FindChildByName("EffectElement") is FrameworkElement element) - { - var behaviors = Interaction.GetBehaviors(element); - _rotateBehavior = behaviors.FirstOrDefault(item => item is Rotate) as Rotate; - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorXaml.bind deleted file mode 100644 index f633e0139da..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Rotate/RotateBehaviorXaml.bind +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorCode.bind deleted file mode 100644 index e221a771fb0..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorCode.bind +++ /dev/null @@ -1,8 +0,0 @@ -// XAML UIElement - - -// C# - Saturation can be applied to any UIElement. In this case it is an image called ToolkitLogo. -using Microsoft.Toolkit.Uwp.UI.Animations; - -await MyImage.Saturation(value: 10, duration: 10, delay: 0).StartAsync(); - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorPage.xaml deleted file mode 100644 index 275c42f1fcd..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorPage.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorPage.xaml.cs deleted file mode 100644 index d08eb1c3962..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorPage.xaml.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - /// - /// A demonstration page of how you can use the Saturation effect using behaviors. - /// - public sealed partial class SaturationBehaviorPage : IXamlRenderListener - { - private Saturation _saturationBehavior; - - /// - /// Initializes a new instance of the class. - /// - public SaturationBehaviorPage() - { - InitializeComponent(); - - SampleController.Current.RegisterNewCommand("Apply", (s, e) => - { - _saturationBehavior?.StartAnimation(); - }); - } - - public void OnXamlRendered(FrameworkElement control) - { - if (control.FindChildByName("EffectElement") is FrameworkElement element) - { - var behaviors = Interaction.GetBehaviors(element); - _saturationBehavior = behaviors.FirstOrDefault(item => item is Saturation) as Saturation; - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorXaml.bind deleted file mode 100644 index 8ecf1e81dc4..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Saturation/SaturationBehaviorXaml.bind +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorPage.xaml deleted file mode 100644 index 74c1d43d6d6..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorPage.xaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorPage.xaml.cs deleted file mode 100644 index 05b27309f49..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorPage.xaml.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.SampleApp.SamplePages -{ - /// - /// A page that shows how to use the scale behavior. - /// - public sealed partial class ScaleBehaviorPage : IXamlRenderListener - { - private Scale _scaleBehavior; - - /// - /// Initializes a new instance of the class. - /// - public ScaleBehaviorPage() - { - InitializeComponent(); - - SampleController.Current.RegisterNewCommand("Apply", (s, e) => - { - _scaleBehavior?.StartAnimation(); - }); - } - - public void OnXamlRendered(FrameworkElement control) - { - if (control.FindChildByName("EffectElement") is FrameworkElement element) - { - var behaviors = Interaction.GetBehaviors(element); - _scaleBehavior = behaviors.FirstOrDefault(item => item is Scale) as Scale; - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorXaml.bind deleted file mode 100644 index 84ee7671e41..00000000000 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/Scale/ScaleBehaviorXaml.bind +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollHeader/ScrollHeaderCode.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollHeader/ScrollHeaderCode.bind index 8303522e746..9edcd2542b6 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollHeader/ScrollHeaderCode.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollHeader/ScrollHeaderCode.bind @@ -2,34 +2,37 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls" xmlns:models="using:Microsoft.Toolkit.Uwp.SampleApp.Models" + xmlns:interactivity="using:Microsoft.Xaml.Interactivity" + xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> + + + - - - - - - - - + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollHeader/ScrollHeaderPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollHeader/ScrollHeaderPage.xaml index d95346f0b88..44a877fd63d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollHeader/ScrollHeaderPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollHeader/ScrollHeaderPage.xaml @@ -1,19 +1,15 @@  - - - - - - - - - - + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsPage.xaml index 08bf65c2e7c..d4da6b9a6da 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsPage.xaml @@ -1,10 +1,12 @@ - @@ -14,47 +16,49 @@ + extensions:ScrollViewerExtensions.VerticalScrollBarMargin="{Binding MinHeight, ElementName=MyHeaderGrid, Converter={StaticResource DoubleTopThicknessConverter}}"> + + + - - - - - - - - + + + + + + - + + Grid.Column="0" + Width="100" + Height="100" + Margin="0,0,24,0" + Source="ms-appx:///Assets/ToolkitLogo.png" /> + VerticalAlignment="Center" + Text="{Binding Title}" + TextTrimming="CharacterEllipsis" /> @@ -66,45 +70,44 @@ - - + - - + HorizontalAlignment="Center" + VerticalAlignment="Center" + Orientation="Horizontal" + Spacing="4"> + + - - - - + + + + - + Fill="Crimson" + Points="0,0 0,60 60,60 60,0" + Stroke="DarkRed" /> \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsPage.xaml.cs index 8cea51a5441..3c327b172cf 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsPage.xaml.cs @@ -4,6 +4,7 @@ using System.Collections.ObjectModel; using Microsoft.Toolkit.Uwp.SampleApp.Models; +using Microsoft.Toolkit.Uwp.UI.Animations; using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsXaml.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsXaml.bind index 555377bdebe..6b28c95e185 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsXaml.bind +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ScrollViewerExtensions/ScrollViewerExtensionsXaml.bind @@ -5,6 +5,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:interactivity="using:Microsoft.Xaml.Interactivity" + xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors" mc:Ignorable="d"> @@ -15,29 +17,30 @@ + + + - - - - - - - - + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ViewportBehavior/ViewportBehaviorPage.xaml.cs b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ViewportBehavior/ViewportBehaviorPage.xaml.cs index eb09f531121..7ffbbe7edfe 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ViewportBehavior/ViewportBehaviorPage.xaml.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/ViewportBehavior/ViewportBehaviorPage.xaml.cs @@ -6,7 +6,6 @@ using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; -using Microsoft.Toolkit.Uwp.UI.Animations; using Microsoft.Toolkit.Uwp.UI.Behaviors; using Microsoft.Toolkit.Uwp.UI.Extensions; using Microsoft.Xaml.Interactivity; @@ -39,7 +38,7 @@ public void OnXamlRendered(FrameworkElement control) if (control.FindChildByName("EffectElement") is Image effectElement) { _effectElement = effectElement; - _effectElement.Blur(value: 10, duration: 0).Start(); + ////TODO: _effectElement.Blur(value: 10, duration: 0).Start(); } if (control.FindChildByName("EffectElementHost") is FrameworkElement effectElementHost) @@ -72,7 +71,7 @@ private async void EffectElementHost_EnteredViewport(object sender, EventArgs e) { AddLog("Entered viewport"); - await _effectElement.Blur(value: 0, duration: 1500).StartAsync(); + ////TODO: await _effectElement.Blur(value: 0, duration: 1500).StartAsync(); } private void EffectElementHost_EnteringViewport(object sender, EventArgs e) @@ -87,7 +86,7 @@ private async void EffectElementHost_ExitedViewport(object sender, EventArgs e) AddLog("Exited viewport"); _effectElement.Source = null; - await _effectElement.Blur(value: 8, duration: 0).StartAsync(); + ////TODO: await _effectElement.Blur(value: 8, duration: 0).StartAsync(); } private void EffectElementHost_ExitingViewport(object sender, EventArgs e) diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/VisualEffectFactory/VisualEffectFactory.bind b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/VisualEffectFactory/VisualEffectFactory.bind new file mode 100644 index 00000000000..5bc60f63a37 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/VisualEffectFactory/VisualEffectFactory.bind @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml index decdbe391e9..3b7a093050c 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/XamlOnlyPage.xaml @@ -1,9 +1,14 @@  @@ -19,7 +24,36 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json index b7d7a495ffd..5b356756f2d 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json +++ b/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/samples.json @@ -460,87 +460,84 @@ "Name": "Animations", "Icon": "Icons/Animations.png", "Samples": [ + { + "Name": "StartAnimationActivity", + "Subcategory": "Activities", + "About": "Activity for Animations to Start another Animation", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities", + "XamlCodeFile": "/SamplePages/Animations/Activities/StartAnimationActivity.bind", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Fade.md" + }, + { + "Name": "InvokeActionsActivity", + "Subcategory": "Activities", + "About": "Activity chaining Actions from the Behaviors package into an Animation schedule.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations", + "XamlCodeFile": "/SamplePages/Animations/Activities/InvokeActionsActivity.bind", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Fade.md" + }, { "Name": "Fade", - "Type": "FadeBehaviorPage", - "Subcategory": "Behavior", - "About": "Opacity of XAML elements using composition", + "Subcategory": "Effect", + "About": "Opacity of XAML elements using composition animations", "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors", - "CodeFile": "FadeBehaviorCode.bind", - "XamlCodeFile": "FadeBehaviorXaml.bind", - "Icon": "/SamplePages/Fade/FadeBehavior.png", + "CodeFile": "/SamplePages/Animations/Effects/FadeBehaviorCode.bind", + "XamlCodeFile": "/SamplePages/Animations/Effects/FadeBehaviorXaml.bind", + "Icon": "/SamplePages/Animations/Effects/FadeBehavior.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Fade.md" }, { "Name": "Scale", - "Type": "ScaleBehaviorPage", "Subcategory": "Behavior", "About": "Scale of XAML elements using composition", "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors", - "CodeFile": "ScaleBehaviorCode.bind", - "XamlCodeFile": "ScaleBehaviorXaml.bind", - "Icon": "/SamplePages/Scale/scaleBehavior.png", + "CodeFile": "/SamplePages/Animations/Behaviors/ScaleBehaviorCode.bind", + "XamlCodeFile": "/SamplePages/Animations/Behaviors/ScaleBehaviorXaml.bind", + "Icon": "/SamplePages/Animations/Behaviors/ScaleBehavior.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Scale.md" }, { "Name": "Offset", - "Type": "OffsetBehaviorPage", "Subcategory": "Behavior", "About": "Offset of XAML elements using composition", "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors", - "CodeFile": "OffsetBehaviorCode.bind", - "XamlCodeFile": "OffsetBehaviorXaml.bind", - "Icon": "/SamplePages/Offset/offsetBehavior.png", + "CodeFile": "/SamplePages/Animations/Behaviors/OffsetBehaviorCode.bind", + "XamlCodeFile": "/SamplePages/Animations/Behaviors/OffsetBehaviorXaml.bind", + "Icon": "/SamplePages/Animations/Behaviors/OffsetBehavior.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Offset.md" }, { "Name": "Rotate", - "Type": "RotateBehaviorPage", "Subcategory": "Behavior", "About": "Rotation on XAML elements using composition", "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors", - "CodeFile": "RotateBehaviorCode.bind", - "XamlCodeFile": "RotateBehaviorXaml.bind", - "Icon": "/SamplePages/Rotate/rotateBehavior.png", + "CodeFile": "/SamplePages/Animations/Behaviors/RotateBehaviorCode.bind", + "XamlCodeFile": "/SamplePages/Animations/Behaviors/RotateBehaviorXaml.bind", + "Icon": "/SamplePages/Animations/Behaviors/RotateBehavior.png", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Rotate.md" }, { "Name": "Blur", - "Type": "BlurBehaviorPage", "Subcategory": "Behavior", "About": "Blur XAML elements using composition", "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors", - "CodeFile": "BlurBehaviorCode.bind", - "XamlCodeFile": "BlurBehaviorXaml.bind", - "Icon": "/SamplePages/Blur/blurBehavior.png", + "CodeFile": "/SamplePages/Animations/Behaviors/BlurBehaviorCode.bind", + "XamlCodeFile": "/SamplePages/Animations/Behaviors/BlurBehaviorXaml.bind", + "Icon": "/SamplePages/Animations/Behaviors/BlurBehavior.png", "BadgeUpdateVersionRequired": "Anniversary Update required", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Blur.md" }, { "Name": "Saturation", - "Type": "SaturationBehaviorPage", "Subcategory": "Behavior", "About": "Saturate XAML elements using composition", "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors", - "CodeFile": "SaturationBehaviorCode.bind", - "XamlCodeFile": "SaturationBehaviorXaml.bind", - "Icon": "/SamplePages/Saturation/saturationBehavior.png", + "CodeFile": "/SamplePages/Animations/Behaviors/SaturationBehaviorCode.bind", + "XamlCodeFile": "/SamplePages/Animations/Behaviors/SaturationBehaviorXaml.bind", + "Icon": "/SamplePages/Animations/Behaviors/SaturationBehavior.png", "BadgeUpdateVersionRequired": "Anniversary Update required", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Saturation.md" }, - { - "Name": "Light", - "Type": "LightBehaviorPage", - "Subcategory": "Behavior", - "About": "The Light effect will be removed in a future major release", - "BadgeUpdateVersionRequired": "DEPRECATED", - "DeprecatedWarning": "The Light effect will be removed in a future major release", - "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors", - "CodeFile": "LightBehaviorCode.bind", - "XamlCodeFile": "LightBehaviorXaml.bind", - "Icon": "/SamplePages/Light/LightBehavior.png", - "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Light.md" - }, { "Name": "FadeHeader", "Type": "FadeHeaderBehaviorPage", @@ -561,16 +558,24 @@ "CodeUrl": "https://github.com/windows-toolkit/Lottie-Windows" }, { - "Name": "ReorderGridAnimation", - "Type": "ReorderGridPage", + "Name": "ItemsReorderAnimation", + "Type": "ItemsReorderAnimationPage", "Subcategory": "Effect", - "About": "Animates items of a grid when the size changes", + "About": "Animates items of a grid or list control when the size changes", "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Animations", - "XamlCodeFile": "ReorderGrid.bind", - "Icon": "/SamplePages/ReorderGridAnimation/ReorderGrid.png", + "XamlCodeFile": "ItemsReorderAnimation.bind", + "Icon": "/SamplePages/ItemsReorderAnimation/ItemsReorderAnimation.png", "BadgeUpdateVersionRequired": "Anniversary Update required", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/ReorderGrid.md" }, + { + "Name": "EffectAnimations", + "Subcategory": "Effect", + "About": "Effects from the Media package that can be animated from an AnimationSet schedule.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Animations", + "XamlCodeFile": "/SamplePages/Animations/Effects/EffectAnimations.bind", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/animations/Fade.md" + }, { "Name": "Implicit Animations", "Type": "ImplicitAnimationsPage", @@ -1111,6 +1116,15 @@ "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/PipelineBrush.md" }, + { + "Name": "VisualEffectFactory", + "About": "A composition pipeline which can render any custom Win2D/Composition effects chain directly on a Composition visual.", + "CodeUrl": "https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/master/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs", + "XamlCodeFile": "VisualEffectFactory.bind", + "Icon": "/SamplePages/PipelineBrush/PipelineBrush.png", + "ApiCheck": "Windows.UI.Xaml.Media.XamlCompositionBrushBase", + "DocumentationUrl": "https://raw.githubusercontent.com/MicrosoftDocs/WindowsCommunityToolkitDocs/master/docs/brushes/PipelineBrush.md" + }, { "Name": "AcrylicBrush", "Type": "AcrylicBrushPage", diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Shell.SamplePicker.cs b/Microsoft.Toolkit.Uwp.SampleApp/Shell.SamplePicker.cs index b52099921b0..a28da5cbc3e 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Shell.SamplePicker.cs +++ b/Microsoft.Toolkit.Uwp.SampleApp/Shell.SamplePicker.cs @@ -221,15 +221,12 @@ private void ContainerItem_Loaded(object sender, RoutedEventArgs e) { var staggerDelay = TimeSpan.FromMilliseconds(relativeIndex * 30); - var animationCollection = new AnimationCollection() - { - new OpacityAnimation() { From = 0, To = 1, Duration = TimeSpan.FromMilliseconds(400), Delay = staggerDelay, SetInitialValueBeforeDelay = true }, - new ScaleAnimation() { From = "0.9", To = "1", Duration = TimeSpan.FromMilliseconds(400), Delay = staggerDelay } - }; - VisualExtensions.SetNormalizedCenterPoint(itemContainer, "0.5"); - animationCollection.StartAnimation(itemContainer); + AnimationBuilder.Create() + .Opacity(from: 0, to: 1, delay: staggerDelay) + .Scale(from: 0.9, to: 1, delay: staggerDelay) + .Start(itemContainer); } } @@ -238,11 +235,8 @@ private void ItemContainer_PointerExited(object sender, Windows.UI.Xaml.Input.Po var panel = (sender as FrameworkElement).FindDescendant(); if (panel != null) { - var animation = new OpacityAnimation() { To = 0, Duration = TimeSpan.FromMilliseconds(1200) }; - animation.StartAnimation(panel); - - var parentAnimation = new ScaleAnimation() { To = "1", Duration = TimeSpan.FromMilliseconds(1200) }; - parentAnimation.StartAnimation(panel.Parent as UIElement); + AnimationBuilder.Create().Opacity(0, duration: TimeSpan.FromMilliseconds(1200)).Start(panel); + AnimationBuilder.Create().Scale(1, duration: TimeSpan.FromMilliseconds(1200)).Start((UIElement)panel.Parent); } } @@ -254,11 +248,9 @@ private void ItemContainer_PointerEntered(object sender, Windows.UI.Xaml.Input.P if (panel != null) { panel.Visibility = Visibility.Visible; - var animation = new OpacityAnimation() { To = 1, Duration = TimeSpan.FromMilliseconds(600) }; - animation.StartAnimation(panel); - var parentAnimation = new ScaleAnimation() { To = "1.1", Duration = TimeSpan.FromMilliseconds(600) }; - parentAnimation.StartAnimation(panel.Parent as UIElement); + AnimationBuilder.Create().Opacity(1, duration: TimeSpan.FromMilliseconds(600)).Start(panel); + AnimationBuilder.Create().Scale(1.1, duration: TimeSpan.FromMilliseconds(600)).Start((UIElement)panel.Parent); } } } diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml b/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml index 7aa45fcdc86..d878d82ed0c 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/Shell.xaml @@ -1,15 +1,14 @@  + Style="{StaticResource ToolkitNavViewStyle}"> + QueryIcon="Find" + QuerySubmitted="SearchBox_QuerySubmitted" + TextChanged="SearchBox_TextChanged" /> - + - - - + + + + + - + - + - + + ShadowOpacity="0.7" + Color="Black"> - + - + - - - + + - - + + @@ -125,30 +137,29 @@ + Visibility="Collapsed"> - + ShadowOpacity="0.6" + Color="Black"> - - - + + + + + @@ -160,58 +171,79 @@ - - + Text="{Binding About}" + TextWrapping="Wrap" /> - + - - + + - - + + - - + + - - + + diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Styles/Custom/PivotHeaderItemUnderlineStyle.xaml b/Microsoft.Toolkit.Uwp.SampleApp/Styles/Custom/PivotHeaderItemUnderlineStyle.xaml index 73231c861a7..7d7ad535788 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Styles/Custom/PivotHeaderItemUnderlineStyle.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/Styles/Custom/PivotHeaderItemUnderlineStyle.xaml @@ -1,14 +1,17 @@ - - - + + + + + + - - + + - - + + - - + + @@ -43,71 +79,97 @@ + Storyboard.TargetProperty="X" + To="{ThemeResource PivotHeaderItemLockedTranslation}" + Duration="0" /> + Storyboard.TargetProperty="(UIElement.Opacity)" + To="0" + Duration="0" /> - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + @@ -121,25 +183,6 @@ - - - - - - diff --git a/Microsoft.Toolkit.Uwp.SampleApp/Styles/Generic.xaml b/Microsoft.Toolkit.Uwp.SampleApp/Styles/Generic.xaml index a718f6cdf3a..026095d0ae6 100644 --- a/Microsoft.Toolkit.Uwp.SampleApp/Styles/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.SampleApp/Styles/Generic.xaml @@ -1,7 +1,6 @@  - - - + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSet.cs b/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSet.cs deleted file mode 100644 index fa8632942f2..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSet.cs +++ /dev/null @@ -1,634 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Threading.Tasks; -using Windows.UI.Composition; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Hosting; -using Windows.UI.Xaml.Media.Animation; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Defines an object for storing and managing CompositionAnimations for an element - /// - public class AnimationSet : IDisposable - { - private List _animationSets; - - private Compositor _compositor; - private CompositionScopedBatch _batch; - private Dictionary _compositionAnimations; - private List _compositionEffectAnimations; - private Dictionary _directCompositionPropertyChanges; - private List _directCompositionEffectPropertyChanges; - - private Storyboard _storyboard; - private Dictionary _storyboardAnimations; - - private List _animationTasks; - - private TaskCompletionSource _animationTCS; - - private bool _storyboardCompleted; - private bool _compositionCompleted; - - /// - /// Gets or sets a value indicating whether composition must be use even on SDK > 10586 - /// - public static bool UseComposition { get; set; } - - /// - /// Gets the object that backs the XAML element - /// - public Visual Visual { get; private set; } - - /// - /// Gets the - /// - public UIElement Element { get; private set; } - - /// - /// Gets the current state of the AnimationSet - /// - public AnimationSetState State { get; private set; } - - /// - /// Initializes a new instance of the class. - /// - /// The associated element - public AnimationSet(UIElement element) - { - if (element == null) - { - throw new NullReferenceException("Element must not be null"); - } - - var visual = ElementCompositionPreview.GetElementVisual(element); - - if (visual == null) - { - throw new NullReferenceException("Visual must not be null"); - } - - Visual = visual; - if (Visual.Compositor == null) - { - throw new NullReferenceException("Visual must have a compositor"); - } - - Element = element; - State = AnimationSetState.NotStarted; - _compositor = Visual.Compositor; - - _compositionAnimations = new Dictionary(); - _compositionEffectAnimations = new List(); - _directCompositionPropertyChanges = new Dictionary(); - _directCompositionEffectPropertyChanges = new List(); - _animationSets = new List(); - _storyboard = new Storyboard(); - _storyboardAnimations = new Dictionary(); - _animationTasks = new List(); - } - - /// - /// Occurs when all animations have completed - /// - public event EventHandler Completed; - - /// - /// Stats all animations. This method is not awaitable. - /// - public async void Start() - { - await StartAsync(); - } - - /// - /// Starts all animations and returns an awaitable task. - /// - /// A that can be awaited until all animations have completed - public async Task StartAsync() - { - if (_animationTCS == null || _animationTCS.Task.IsCompleted) - { - if (_animationTCS != null && _animationTCS.Task.IsCompleted) - { - foreach (var set in _animationSets) - { - set.State = AnimationSetState.NotStarted; - set._animationTCS = null; - } - } - - State = AnimationSetState.Running; - _animationTCS = new TaskCompletionSource(); - } - else - { - return await _animationTCS.Task; - } - - foreach (var set in _animationSets) - { - if (set.State != AnimationSetState.Completed) - { - var completed = await set.StartAsync(); - - if (!completed) - { - // the animation was stopped - return await _animationTCS.Task; - } - } - } - - foreach (var task in _animationTasks) - { - if (task.Task != null && !task.Task.IsCompleted) - { - await task.Task; - } - - // if _animationSet is stopped while task was running - if (State == AnimationSetState.Stopped) - { - return await _animationTCS.Task; - } - } - - _animationTasks.Clear(); - - foreach (var property in _directCompositionPropertyChanges) - { - typeof(Visual).GetProperty(property.Key).SetValue(Visual, property.Value); - } - - foreach (var definition in _directCompositionEffectPropertyChanges) - { - definition.EffectBrush.Properties.InsertScalar(definition.PropertyName, definition.Value); - } - - if (_compositionAnimations.Count > 0 || _compositionEffectAnimations.Count > 0) - { - if (_batch != null) - { - if (!_batch.IsEnded) - { - _batch.End(); - } - - _batch.Completed -= Batch_Completed; - } - - _batch = _compositor.CreateScopedBatch(CompositionBatchTypes.Animation); - _batch.Completed += Batch_Completed; - - foreach (var anim in _compositionAnimations) - { - Visual.StartAnimation(anim.Key, anim.Value); - } - - foreach (var effect in _compositionEffectAnimations) - { - effect.EffectBrush.StartAnimation(effect.PropertyName, effect.Animation); - } - - _compositionCompleted = false; - _batch.End(); - } - else - { - _compositionCompleted = true; - } - - if (_storyboardAnimations.Count > 0) - { - _storyboardCompleted = false; - - _storyboard.Completed -= Storyboard_Completed; - _storyboard.Completed += Storyboard_Completed; - - _storyboard.Begin(); - } - else - { - _storyboardCompleted = true; - } - - HandleCompleted(); - - return await _animationTCS.Task; - } - - /// - /// Stops all animations. - /// - public void Stop() - { - foreach (var set in _animationSets) - { - if (set.State != AnimationSetState.Completed) - { - set.Stop(); - } - } - - if (_batch != null) - { - if (!_batch.IsEnded) - { - _batch.End(); - } - - _batch.Completed -= Batch_Completed; - } - - foreach (var anim in _compositionAnimations) - { - Visual.StopAnimation(anim.Key); - } - - foreach (var effect in _compositionEffectAnimations) - { - effect.EffectBrush.StopAnimation(effect.PropertyName); - } - - _storyboard.Pause(); - - HandleCompleted(true); - _animationTCS = null; - } - - /// - /// Wait for existing animations to complete before running new animations - /// - /// AnimationSet to allow chaining - public AnimationSet Then() - { - var savedAnimationSet = new AnimationSet(Element); - savedAnimationSet._compositionAnimations = _compositionAnimations; - savedAnimationSet._compositionEffectAnimations = _compositionEffectAnimations; - savedAnimationSet._directCompositionPropertyChanges = _directCompositionPropertyChanges; - savedAnimationSet._directCompositionEffectPropertyChanges = _directCompositionEffectPropertyChanges; - savedAnimationSet._storyboard = _storyboard; - savedAnimationSet._storyboardAnimations = _storyboardAnimations; - - _animationTasks.ForEach(t => t.AnimationSet = savedAnimationSet); - savedAnimationSet._animationTasks = _animationTasks; - - _animationSets.Add(savedAnimationSet); - - _compositionAnimations = new Dictionary(); - _compositionEffectAnimations = new List(); - _directCompositionPropertyChanges = new Dictionary(); - _directCompositionEffectPropertyChanges = new List(); - _storyboard = new Storyboard(); - _storyboardAnimations = new Dictionary(); - _animationTasks = new List(); - - return this; - } - - /// - /// Overwrites the duration on all animations after last Then() - /// to the specified value - /// - /// The duration in milliseconds - /// AnimationSet to allow chaining - public AnimationSet SetDuration(double duration) - { - if (duration <= 0) - { - duration = 1; - } - - return SetDuration(TimeSpan.FromMilliseconds(duration)); - } - - /// - /// Overwrites the duration on all animations after last Then() - /// to the specified value - /// - /// for the duration - /// AnimationSet to allow chaining - public AnimationSet SetDuration(TimeSpan duration) - { - foreach (var task in _animationTasks) - { - task.Duration = duration; - } - - foreach (var anim in _compositionAnimations) - { - var animation = anim.Value as KeyFrameAnimation; - if (animation != null) - { - animation.Duration = duration; - } - } - - foreach (var effect in _compositionEffectAnimations) - { - var animation = effect.Animation as KeyFrameAnimation; - if (animation != null) - { - animation.Duration = duration; - } - } - - foreach (var timeline in _storyboardAnimations) - { - var animation = timeline.Value as DoubleAnimation; - if (animation != null) - { - animation.Duration = duration; - } - } - - return this; - } - - /// - /// Overwrites the duration on all animations to the specified value - /// - /// The duration in milliseconds - /// AnimationSet to allow chaining - public AnimationSet SetDurationForAll(double duration) - { - foreach (var set in _animationSets) - { - set.SetDuration(duration); - } - - return SetDuration(duration); - } - - /// - /// Overwrites the duration on all animations to the specified value - /// - /// for the duration - /// AnimationSet to allow chaining - public AnimationSet SetDurationForAll(TimeSpan duration) - { - foreach (var set in _animationSets) - { - set.SetDuration(duration); - } - - return SetDuration(duration); - } - - /// - /// Overwrites the delay time on all animations after last Then() - /// to the specified value - /// - /// The delay time in milliseconds - /// AnimationSet to allow chaining - public AnimationSet SetDelay(double delayTime) - { - if (delayTime < 0) - { - delayTime = 0; - } - - return SetDelay(TimeSpan.FromMilliseconds(delayTime)); - } - - /// - /// Overwrites the delay time on all animations after last Then() - /// to the specified value - /// - /// for how much to delay - /// AnimationSet to allow chaining - public AnimationSet SetDelay(TimeSpan delayTime) - { - foreach (var task in _animationTasks) - { - task.Delay = delayTime; - } - - foreach (var anim in _compositionAnimations) - { - var animation = anim.Value as KeyFrameAnimation; - if (animation != null) - { - animation.DelayTime = delayTime; - } - } - - foreach (var effect in _compositionEffectAnimations) - { - var animation = effect.Animation as KeyFrameAnimation; - if (animation != null) - { - animation.DelayTime = delayTime; - } - } - - foreach (var timeline in _storyboardAnimations) - { - var animation = timeline.Value as DoubleAnimation; - if (animation != null) - { - animation.BeginTime = delayTime; - } - } - - return this; - } - - /// - /// Overwrites the delay time on all animations to the specified value - /// - /// The delay time in milliseconds - /// AnimationSet to allow chaining - public AnimationSet SetDelayForAll(double delayTime) - { - foreach (var set in _animationSets) - { - set.SetDelay(delayTime); - } - - return SetDelay(delayTime); - } - - /// - /// Overwrites the delay time on all animations to the specified value - /// - /// for how much to delay - /// AnimationSet to allow chaining - public AnimationSet SetDelayForAll(TimeSpan delayTime) - { - foreach (var set in _animationSets) - { - set.SetDelay(delayTime); - } - - return SetDelay(delayTime); - } - - /// - /// Adds a composition animation to be run on - /// - /// The property to be animated on the backing Visual - /// The to be applied - public void AddCompositionAnimation(string propertyName, CompositionAnimation animation) - { - _compositionAnimations[propertyName] = animation; - } - - /// - /// Removes a composition animation from being run on property - /// - /// The property that no longer needs to be animated - public void RemoveCompositionAnimation(string propertyName) - { - if (_compositionAnimations.ContainsKey(propertyName)) - { - _compositionAnimations.Remove(propertyName); - } - } - - /// - /// Adds a composition effect animation to be run on backing - /// - /// The that will have a property animated - /// The animation to be applied - /// The property of the effect to be animated - public void AddCompositionEffectAnimation(CompositionObject effectBrush, CompositionAnimation animation, string propertyName) - { - var effect = new EffectAnimationDefinition() - { - EffectBrush = effectBrush, - Animation = animation, - PropertyName = propertyName - }; - - _compositionEffectAnimations.Add(effect); - } - - /// - /// Adds a composition property that will change instantaneously - /// - /// The property to be animated on the backing Visual - /// The value to be applied - public void AddCompositionDirectPropertyChange(string propertyName, object value) - { - _directCompositionPropertyChanges[propertyName] = value; - } - - /// - /// Removes a composition property change - /// - /// The property that no longer needs to be changed - public void RemoveCompositionDirectPropertyChange(string propertyName) - { - if (_directCompositionPropertyChanges.ContainsKey(propertyName)) - { - _directCompositionPropertyChanges.Remove(propertyName); - } - } - - /// - /// Adds a storyboard animation to be run - /// - /// The property to be animated with Storyboards - /// The timeline object to be added to storyboard - public void AddStoryboardAnimation(string propertyPath, Timeline timeline) - { - if (_storyboardAnimations.ContainsKey(propertyPath)) - { - var previousAnimation = _storyboardAnimations[propertyPath]; - _storyboard.Children.Remove(previousAnimation); - _storyboardAnimations.Remove(propertyPath); - } - - _storyboardAnimations.Add(propertyPath, timeline); - _storyboard.Children.Add(timeline); - - Storyboard.SetTarget(timeline, Element); - Storyboard.SetTargetProperty(timeline, propertyPath); - } - - /// - /// Dispose resources. - /// - public void Dispose() - { - _animationTCS = null; - } - - /// - /// Adds a to the AnimationSet that - /// will run add an animation once completed. Useful when an animation - /// needs to do asynchronous initialization before running - /// - /// The to be added - internal void AddAnimationThroughTask(AnimationTask animationTask) - { - _animationTasks.Add(animationTask); - } - - /// - /// Adds an effect property change to be run on - /// - /// The that will have a property changed - /// The value to be applied - /// The property of the effect to be animated - internal void AddEffectDirectPropertyChange(CompositionObject effectBrush, float value, string propertyName) - { - var definition = new EffectDirectPropertyChangeDefinition() - { - EffectBrush = effectBrush, - Value = value, - PropertyName = propertyName - }; - - _directCompositionEffectPropertyChanges.Add(definition); - } - - private void Storyboard_Completed(object sender, object e) - { - _storyboardCompleted = true; - _storyboard.Completed -= Storyboard_Completed; - HandleCompleted(); - } - - private void Batch_Completed(object sender, CompositionBatchCompletedEventArgs args) - { - _compositionCompleted = true; - _batch.Completed -= Batch_Completed; - HandleCompleted(); - } - - private void HandleCompleted(bool stopped = false) - { - var completed = _storyboardCompleted && _compositionCompleted; - - if (!completed && !stopped) - { - return; - } - - if (_storyboardCompleted && _compositionCompleted) - { - State = AnimationSetState.Completed; - } - else - { - State = AnimationSetState.Stopped; - } - - if (_animationTCS != null && !_animationTCS.Task.IsCompleted) - { - _animationTCS.SetResult(State == AnimationSetState.Completed); - Completed?.Invoke(this, new AnimationSetCompletedEventArgs() { Completed = _storyboardCompleted && _compositionCompleted }); - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSetCompletedEventArgs.cs b/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSetCompletedEventArgs.cs deleted file mode 100644 index 3bad5d6cb13..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSetCompletedEventArgs.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// AnimationSet Completed EventArgs. - /// - public class AnimationSetCompletedEventArgs : EventArgs - { - /// - /// Gets a value indicating whether the animation completed - /// - public bool Completed { get; internal set; } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSetState.cs b/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSetState.cs deleted file mode 100644 index 9157c72c0f2..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/AnimationSetState.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// States of AnimationSet. - /// - public enum AnimationSetState - { - /// - /// The animation has not been started - /// - NotStarted, - - /// - /// The animation has been started and is in progress - /// - Running, - - /// - /// The animation has been started and is stopped - /// - Stopped, - - /// - /// The animation had completed - /// - Completed - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/AnimationTask.cs b/Microsoft.Toolkit.Uwp.UI.Animations/AnimationTask.cs deleted file mode 100644 index d9aefee2beb..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/AnimationTask.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading.Tasks; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Defines which is used by - /// to run animations that require - /// asynchronous initialization - /// - internal class AnimationTask - { - /// - /// Gets or sets that will run before any animation - /// and it will add the animation to the AnimationSet once complete - /// - public Task Task { get; set; } - - /// - /// Gets or sets that will run the animation - /// - public AnimationSet AnimationSet { get; set; } - - /// - /// Gets or sets Duration to be applied to the animation once the task is completed - /// Used when Duration is changed before Task completes - /// - public TimeSpan? Duration { get; set; } - - /// - /// Gets or sets Delay to be applied to the animation once the task is completed - /// Used when Duration is changed before Task completes - /// - public TimeSpan? Delay { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Default.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Default.cs new file mode 100644 index 00000000000..5e15a7de772 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Default.cs @@ -0,0 +1,794 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Numerics; +using Microsoft.Toolkit.Diagnostics; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + public sealed partial class AnimationBuilder + { + /// + /// Adds a new anchor point animation for a single axis to the current schedule. + /// + /// The target anchor point axis to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder AnchorPoint( + Axis axis, + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + AddCompositionAnimationFactory(Properties.Composition.AnchorPoint(axis), (float)to, (float?)from, delay, duration, easingType, easingMode); + + return this; + } + + /// + /// Adds a new anchor point animation for the X and Y axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder AnchorPoint( + Vector2 to, + Vector2? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + AddCompositionAnimationFactory(nameof(Visual.AnchorPoint), to, from, delay, duration, easingType, easingMode); + + return this; + } + + /// + /// Adds a new opacity animation to the current schedule. + /// + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Opacity( + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(nameof(Visual.Opacity), (float)to, (float?)from, delay, duration, easingType, easingMode); + } + else + { + AddXamlAnimationFactory(nameof(UIElement.Opacity), to, from, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new translation animation for a single axis to the current schedule. + /// + /// The target translation axis to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Translation( + Axis axis, + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(Properties.Composition.Translation(axis), (float)to, (float?)from, delay, duration, easingType, easingMode); + } + else + { + AddXamlAnimationFactory(Properties.Xaml.Translation(axis), to, from, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new translation animation for the X and Y axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Translation( + Vector2 to, + Vector2? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(Properties.Composition.TranslationXY(), to, from, delay, duration, easingType, easingMode); + } + else + { + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.TranslateX), to.X, from?.X, delay, duration, easingType, easingMode); + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.TranslateY), to.Y, from?.Y, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new composition translation animation for all axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Translation( + Vector3 to, + Vector3? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + return AddCompositionAnimationFactory(Properties.Composition.Translation(), to, from, delay, duration, easingType, easingMode); + } + + /// + /// Adds a new composition offset animation for a single axis to the current schedule. + /// + /// The target translation axis to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Offset( + Axis axis, + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + return AddCompositionAnimationFactory(Properties.Composition.Offset(axis), (float)to, (float?)from, delay, duration, easingType, easingMode); + } + + /// + /// Adds a new composition offset animation for the X and Y axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Offset( + Vector2 to, + Vector2? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + AddCompositionAnimationFactory(Properties.Composition.OffsetXY(), to, from, delay, duration, easingType, easingMode); + + return this; + } + + /// + /// Adds a new composition offset translation animation for all axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Offset( + Vector3 to, + Vector3? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + return AddCompositionAnimationFactory(nameof(Visual.Offset), to, from, delay, duration, easingType, easingMode); + } + + /// + /// Adds a new uniform scale animation for all axes to the current schedule. + /// + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Scale( + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + Vector3? from3 = from is null ? null : new((float)(double)from); + Vector3 to3 = new((float)to); + + AddCompositionAnimationFactory(nameof(Visual.Scale), to3, from3, delay, duration, easingType, easingMode); + } + else + { + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.ScaleX), to, from, delay, duration, easingType, easingMode); + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.ScaleY), to, from, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new scale animation on a specified axis to the current schedule. + /// + /// The target scale axis to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Scale( + Axis axis, + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(Properties.Composition.Scale(axis), (float)to, (float?)from, delay, duration, easingType, easingMode); + } + else + { + AddXamlTransformDoubleAnimationFactory(Properties.Xaml.Scale(axis), to, from, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new scale animation for the X and Y axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Scale( + Vector2 to, + Vector2? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(Properties.Composition.ScaleXY(), to, from, delay, duration, easingType, easingMode); + } + else + { + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.ScaleX), to.X, from?.X, delay, duration, easingType, easingMode); + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.ScaleY), to.Y, from?.Y, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new scale animation for all axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Scale( + Vector3 to, + Vector3? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + return AddCompositionAnimationFactory(nameof(Visual.Scale), to, from, delay, duration, easingType, easingMode); + } + + /// + /// Adds a new center point animation on a specified axis to the current schedule. + /// + /// The target scale axis to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder CenterPoint( + Axis axis, + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(Properties.Composition.CenterPoint(axis), (float)to, (float?)from, delay, duration, easingType, easingMode); + } + else + { + AddXamlTransformDoubleAnimationFactory(Properties.Xaml.CenterPoint(axis), to, from, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new center point animation for the X and Y axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder CenterPoint( + Vector2 to, + Vector2? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(Properties.Composition.CenterPointXY(), to, from, delay, duration, easingType, easingMode); + } + else + { + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.CenterX), to.X, from?.X, delay, duration, easingType, easingMode); + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.CenterY), to.Y, from?.Y, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new center point animation for all axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder CenterPoint( + Vector3 to, + Vector3? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + return AddCompositionAnimationFactory(nameof(Visual.CenterPoint), to, from, delay, duration, easingType, easingMode); + } + + /// + /// Adds a new rotation animation to the current schedule. + /// + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Rotation( + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(nameof(Visual.RotationAngle), (float)to, (float?)from, delay, duration, easingType, easingMode); + } + else + { + double? fromDegrees = from * Math.PI / 180; + double toDegrees = to * Math.PI / 180; + + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.Rotation), toDegrees, fromDegrees, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new rotation animation in degrees to the current schedule. + /// + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder RotationInDegrees( + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(nameof(Visual.RotationAngleInDegrees), (float)to, (float?)from, delay, duration, easingType, easingMode); + } + else + { + AddXamlTransformDoubleAnimationFactory(nameof(CompositeTransform.Rotation), to, from, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new rotation axis animation to the current schedule. + /// + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder RotationAxis( + Vector3 to, + Vector3? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + return AddCompositionAnimationFactory(nameof(Visual.RotationAxis), to, from, delay, duration, easingType, easingMode); + } + + /// + /// Adds a new orientation animation to the current schedule. + /// + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Orientation( + Quaternion to, + Quaternion? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + return AddCompositionAnimationFactory(nameof(Visual.Orientation), to, from, delay, duration, easingType, easingMode); + } + + /// + /// Adds a new transform animation to the current schedule. + /// + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Transform( + Matrix4x4 to, + Matrix4x4? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + if (!Matrix4x4.Decompose(to, out Vector3 toScale, out Quaternion toRotation, out Vector3 toTranslation)) + { + ThrowHelper.ThrowArgumentException("The destination matrix could not be decomposed"); + } + + Vector3? fromScale = null; + Quaternion? fromRotation = null; + Vector3? fromTranslation = null; + + if (from.HasValue) + { + if (!Matrix4x4.Decompose(from.GetValueOrDefault(), out Vector3 scale3, out Quaternion rotation4, out Vector3 translation3)) + { + ThrowHelper.ThrowArgumentException("The initial matrix could not be decomposed"); + } + + fromScale = scale3; + fromRotation = rotation4; + fromTranslation = translation3; + } + + Scale(toScale, fromScale, delay, duration, easingType, easingMode); + Orientation(toRotation, fromRotation, delay, duration, easingType, easingMode); + Translation(toTranslation, fromTranslation, delay, duration, easingType, easingMode); + + return this; + } + + /// + /// Adds a new clip animation to the current schedule. + /// + /// The clip size to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Clip( + Side side, + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + CompositionClipScalarAnimation animation = new( + Properties.Composition.Clip(side), + (float)to, + (float?)from, + delay ?? DefaultDelay, + duration ?? DefaultDuration, + easingType, + easingMode); + + this.compositionAnimationFactories.Add(animation); + + return this; + } + + /// + /// Adds a new clip animation to the current schedule. + /// + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The current instance. + /// This animation is only available on the composition layer. + public AnimationBuilder Clip( + Thickness to, + Thickness? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + { + this.compositionAnimationFactories.Add(new CompositionClipScalarAnimation( + nameof(InsetClip.LeftInset), + (float)to.Left, + (float?)from?.Left, + delay ?? DefaultDelay, + duration ?? DefaultDuration, + easingType, + easingMode)); + + this.compositionAnimationFactories.Add(new CompositionClipScalarAnimation( + nameof(InsetClip.TopInset), + (float)to.Top, + (float?)from?.Top, + delay ?? DefaultDelay, + duration ?? DefaultDuration, + easingType, + easingMode)); + + this.compositionAnimationFactories.Add(new CompositionClipScalarAnimation( + nameof(InsetClip.RightInset), + (float)to.Right, + (float?)from?.Right, + delay ?? DefaultDelay, + duration ?? DefaultDuration, + easingType, + easingMode)); + + this.compositionAnimationFactories.Add(new CompositionClipScalarAnimation( + nameof(InsetClip.BottomInset), + (float)to.Bottom, + (float?)from?.Bottom, + delay ?? DefaultDelay, + duration ?? DefaultDuration, + easingType, + easingMode)); + + return this; + } + + /// + /// Adds a new size animation for a single axis to the current schedule. + /// + /// The target size axis to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Size( + Axis axis, + double to, + double? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(Properties.Composition.Size(axis), (float)to, (float?)from, delay, duration, easingType, easingMode); + } + else + { + AddXamlAnimationFactory(Properties.Xaml.Size(axis), to, from, delay, duration, easingType, easingMode); + } + + return this; + } + + /// + /// Adds a new size animation for the X and Y axes to the current schedule. + /// + /// The final point for the animation. + /// The optional starting point for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function type for the animation. + /// The optional easing function mode for the animation. + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder Size( + Vector2 to, + Vector2? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode, + FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + AddCompositionAnimationFactory(Properties.Composition.Size(), to, from, delay, duration, easingType, easingMode); + } + else + { + AddXamlAnimationFactory(nameof(FrameworkElement.Width), to.X, from?.X, delay, duration, easingType, easingMode); + AddXamlAnimationFactory(nameof(FrameworkElement.Height), to.Y, from?.Y, delay, duration, easingType, easingMode); + } + + return this; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.External.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.External.cs new file mode 100644 index 00000000000..68dd890babd --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.External.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + public sealed partial class AnimationBuilder + { + /// + /// Adds a new external animation to the current schedule, which will be executed on the same + /// target object the current instance will be invoked upon. + /// + /// The external instance to add to the schedule. + /// The current instance. + public AnimationBuilder ExternalAnimation(CompositionAnimation animation) + { + this.compositionAnimationFactories.Add(new ExternalCompositionAnimation(null, animation)); + + return this; + } + + /// + /// Adds a new external animation to the current schedule, which will be executed on a given + /// when the current instance is invoked. + /// + /// The target to invoke the animation upon. + /// The external instance to add to the schedule. + /// The current instance. + public AnimationBuilder ExternalAnimation(CompositionObject target, CompositionAnimation animation) + { + this.compositionAnimationFactories.Add(new ExternalCompositionAnimation(target, animation)); + + return this; + } + + /// + /// Adds a new external animation to the current schedule, which will be executed on the same + /// target object the current instance will be invoked upon. + /// + /// The external instance to add to the schedule. + /// The current instance. + public AnimationBuilder ExternalAnimation(Timeline animation) + { + this.xamlAnimationFactories.Add(new ExternalXamlAnimation(animation)); + + return this; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Factories.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Factories.cs new file mode 100644 index 00000000000..455603b2aea --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Factories.cs @@ -0,0 +1,338 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.Numerics; +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.Diagnostics; +using Windows.Foundation; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Animation; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + public sealed partial class AnimationBuilder + { + /// + /// An interface for factories of XAML animations. + /// + internal interface IXamlAnimationFactory + { + /// + /// Gets a instance representing the animation to start. + /// + /// The suggested target instance to animate. + /// A instance with the specified animation. + Timeline GetAnimation(DependencyObject targetHint); + } + + /// + /// An interface for factories of composition animations. + /// + internal interface ICompositionAnimationFactory + { + /// + /// Gets a instance representing the animation to start. + /// + /// The suggested target instance to animate. + /// An optional instance to animate instead of the suggested one. + /// A instance with the specified animation. + /// + /// The separate parameter is needed because unlike with XAML animations, composition animations + /// can't store the target instance internally, and need to be started on the target object directly. This means that custom + /// animation factories that want to target an external object need to return that object separately to inform the callers. + /// + CompositionAnimation GetAnimation(CompositionObject targetHint, out CompositionObject? target); + } + + /// + /// A model representing a generic animation for a target object. + /// + private sealed record AnimationFactory( + string Property, + T To, + T? From, + TimeSpan Delay, + TimeSpan Duration, + EasingType EasingType, + EasingMode EasingMode) + : ICompositionAnimationFactory, IXamlAnimationFactory + where T : unmanaged + { + /// + public CompositionAnimation GetAnimation(CompositionObject targetHint, out CompositionObject? target) + { + CompositionEasingFunction? easingFunction = targetHint.Compositor.TryCreateEasingFunction(EasingType, EasingMode); + + target = null; + + if (typeof(T) == typeof(bool)) + { + return targetHint.Compositor.CreateBooleanKeyFrameAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration); + } + + if (typeof(T) == typeof(float)) + { + return targetHint.Compositor.CreateScalarKeyFrameAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction); + } + + if (typeof(T) == typeof(double)) + { + return targetHint.Compositor.CreateScalarKeyFrameAnimation( + Property, + (float)GetToAs(), + (float?)GetFromAs(), + Delay, + Duration, + easingFunction); + } + + if (typeof(T) == typeof(Vector2)) + { + return targetHint.Compositor.CreateVector2KeyFrameAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction); + } + + if (typeof(T) == typeof(Vector3)) + { + return targetHint.Compositor.CreateVector3KeyFrameAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction); + } + + if (typeof(T) == typeof(Vector4)) + { + return targetHint.Compositor.CreateVector4KeyFrameAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction); + } + + if (typeof(T) == typeof(Color)) + { + return targetHint.Compositor.CreateColorKeyFrameAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction); + } + + if (typeof(T) == typeof(Quaternion)) + { + return targetHint.Compositor.CreateQuaternionKeyFrameAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction); + } + + return ThrowHelper.ThrowInvalidOperationException("Invalid animation type"); + } + + /// + public Timeline GetAnimation(DependencyObject targetHint) + { + EasingFunctionBase? easingFunction = EasingType.ToEasingFunction(EasingMode); + + if (typeof(T) == typeof(float)) + { + return targetHint.CreateDoubleAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction, + enableDependecyAnimations: true); + } + + if (typeof(T) == typeof(double)) + { + return targetHint.CreateDoubleAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction, + enableDependecyAnimations: true); + } + + if (typeof(T) == typeof(Point)) + { + return targetHint.CreatePointAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction, + enableDependecyAnimations: true); + } + + if (typeof(T) == typeof(Color)) + { + return targetHint.CreateColorAnimation( + Property, + GetToAs(), + GetFromAs(), + Delay, + Duration, + easingFunction); + } + + return ThrowHelper.ThrowInvalidOperationException("Invalid animation type"); + } + + /// + /// Gets the current target value as . + /// + /// The target value type to use. + /// The target type cast to . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private TValue GetToAs() + where TValue : unmanaged + { + T to = To; + + return Unsafe.As(ref to); + } + + /// + /// Gets the current starting value as . + /// + /// The starting value type to use. + /// The starting type cast to nullable . + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private TValue? GetFromAs() + where TValue : unmanaged + { + if (From is null) + { + return null; + } + + T from = From.GetValueOrDefault(); + + return Unsafe.As(ref from); + } + } + + /// + /// A model representing a specified composition scalar animation factory targeting a clip. + /// + private sealed record CompositionClipScalarAnimation( + string Property, + float To, + float? From, + TimeSpan Delay, + TimeSpan Duration, + EasingType EasingType, + EasingMode EasingMode) + : ICompositionAnimationFactory + { + /// + public CompositionAnimation GetAnimation(CompositionObject targetHint, out CompositionObject? target) + { + Visual visual = (Visual)targetHint; + InsetClip clip = visual.Clip as InsetClip ?? (InsetClip)(visual.Clip = visual.Compositor.CreateInsetClip()); + CompositionEasingFunction? easingFunction = clip.Compositor.TryCreateEasingFunction(EasingType, EasingMode); + ScalarKeyFrameAnimation animation = clip.Compositor.CreateScalarKeyFrameAnimation(Property, To, From, Delay, Duration, easingFunction); + + target = clip; + + return animation; + } + } + + /// + /// A model representing a specified XAML animation factory targeting a transform. + /// + private sealed record XamlTransformDoubleAnimationFactory( + string Property, + double To, + double? From, + TimeSpan Delay, + TimeSpan Duration, + EasingType EasingType, + EasingMode EasingMode) + : IXamlAnimationFactory + { + /// + public Timeline GetAnimation(DependencyObject targetHint) + { + UIElement element = (UIElement)targetHint; + + if (element.RenderTransform is not CompositeTransform transform) + { + element.RenderTransform = transform = new CompositeTransform(); + } + + return transform.CreateDoubleAnimation(Property, To, From, Duration, Delay, EasingType.ToEasingFunction(EasingMode)); + } + } + + /// + /// A model representing an external composition animation with an optional target . + /// + private sealed record ExternalCompositionAnimation(CompositionObject? Target, CompositionAnimation Animation) : ICompositionAnimationFactory + { + /// + public CompositionAnimation GetAnimation(CompositionObject targetHint, out CompositionObject? target) + { + target = Target; + + return Animation; + } + } + + /// + /// A model representing an external composition animation with an optional target . + /// + private sealed record ExternalXamlAnimation(Timeline Animation) : IXamlAnimationFactory + { + /// + public Timeline GetAnimation(DependencyObject targetHint) + { + return Animation; + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.KeyFrames.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.KeyFrames.cs new file mode 100644 index 00000000000..e349a62c50c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.KeyFrames.cs @@ -0,0 +1,408 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Numerics; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + public sealed partial class AnimationBuilder + { + /// + /// Adds a new anchor point animation for a single axis to the current schedule. + /// + /// The target anchor point axis to animate. + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder AnchorPoint(Axis axis) + { + return new PropertyAnimationBuilder(this, Properties.Composition.AnchorPoint(axis), FrameworkLayer.Composition); + } + + /// + /// Adds a new anchor point animation for the X and Y axes to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder AnchorPoint() + { + return new PropertyAnimationBuilder(this, nameof(Visual.AnchorPoint), FrameworkLayer.Composition); + } + + /// + /// Adds a new opacity animation to the current schedule. + /// + /// The target framework layer to animate. + /// An instance to configure the animation. + public IPropertyAnimationBuilder Opacity(FrameworkLayer layer = FrameworkLayer.Composition) + { + return new PropertyAnimationBuilder(this, nameof(Visual.Opacity), layer); + } + + /// + /// Adds a new translation animation for a single axis to the current schedule. + /// + /// The target translation axis to animate. + /// The target framework layer to animate. + /// An instance to configure the animation. + public IPropertyAnimationBuilder Translation(Axis axis, FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + return new PropertyAnimationBuilder(this, Properties.Composition.Translation(axis), layer); + } + + return new XamlTransformPropertyAnimationBuilder(this, Properties.Xaml.Translation(axis)); + } + + /// + /// Adds a new composition translation animation for all axes to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder Translation() + { + return new PropertyAnimationBuilder(this, Properties.Composition.Translation(), FrameworkLayer.Composition); + } + + /// + /// Adds a new composition offset animation for a single axis to the current schedule. + /// + /// The target translation axis to animate. + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder Offset(Axis axis) + { + return new PropertyAnimationBuilder(this, Properties.Composition.Offset(axis), FrameworkLayer.Composition); + } + + /// + /// Adds a new composition offset translation animation for all axes to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder Offset() + { + return new PropertyAnimationBuilder(this, nameof(Visual.Offset), FrameworkLayer.Composition); + } + + /// + /// Adds a new scale animation on a specified axis to the current schedule. + /// + /// The target scale axis to animate. + /// The target framework layer to animate. + /// An instance to configure the animation. + public IPropertyAnimationBuilder Scale(Axis axis, FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + return new PropertyAnimationBuilder(this, Properties.Composition.Scale(axis), layer); + } + + return new XamlTransformPropertyAnimationBuilder(this, Properties.Xaml.Scale(axis)); + } + + /// + /// Adds a new scale animation for all axes to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder Scale() + { + return new PropertyAnimationBuilder(this, nameof(Visual.Scale), FrameworkLayer.Composition); + } + + /// + /// Adds a new center point animation on a specified axis to the current schedule. + /// + /// The target scale axis to animate. + /// The target framework layer to animate. + /// An instance to configure the animation. + public IPropertyAnimationBuilder CenterPoint(Axis axis, FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + return new PropertyAnimationBuilder(this, Properties.Composition.CenterPoint(axis), layer); + } + + return new XamlTransformPropertyAnimationBuilder(this, Properties.Xaml.CenterPoint(axis)); + } + + /// + /// Adds a new center point animation for all axes to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder CenterPoint() + { + return new PropertyAnimationBuilder(this, nameof(Visual.CenterPoint), FrameworkLayer.Composition); + } + + /// + /// Adds a new rotation animation to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder Rotation() + { + return new PropertyAnimationBuilder(this, nameof(Visual.RotationAngle), FrameworkLayer.Composition); + } + + /// + /// Adds a new rotation animation in degrees to the current schedule. + /// + /// The target framework layer to animate. + /// An instance to configure the animation. + public IPropertyAnimationBuilder RotationInDegrees(FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + return new PropertyAnimationBuilder(this, nameof(Visual.RotationAngleInDegrees), layer); + } + + return new XamlTransformPropertyAnimationBuilder(this, nameof(CompositeTransform.Rotation)); + } + + /// + /// Adds a new rotation axis animation to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder RotationAxis() + { + return new PropertyAnimationBuilder(this, nameof(Visual.RotationAxis), FrameworkLayer.Composition); + } + + /// + /// Adds a new orientation animation to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder Orientation() + { + return new PropertyAnimationBuilder(this, nameof(Visual.Orientation), FrameworkLayer.Composition); + } + + /// + /// Adds a new clip animation to the current schedule. + /// + /// The clip size to animate. + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder Clip(Side side) + { + return new CompositionClipAnimationBuilder(this, Properties.Composition.Clip(side)); + } + + /// + /// Adds a new size animation for a single axis to the current schedule. + /// + /// The target size axis to animate. + /// The target framework layer to animate. + /// An instance to configure the animation. + public IPropertyAnimationBuilder Size(Axis axis, FrameworkLayer layer = FrameworkLayer.Composition) + { + if (layer == FrameworkLayer.Composition) + { + return new PropertyAnimationBuilder(this, Properties.Composition.Size(axis), layer); + } + + return new PropertyAnimationBuilder(this, Properties.Xaml.Size(axis), layer); + } + + /// + /// Adds a new composition size translation animation for all axes to the current schedule. + /// + /// An instance to configure the animation. + /// This animation is only available on the composition layer. + public IPropertyAnimationBuilder Size() + { + return new PropertyAnimationBuilder(this, nameof(Visual.Size), FrameworkLayer.Composition); + } + + /// + /// Adds a custom animation based on normalized keyframes to the current schedule. + /// + /// The type of values to animate. + /// The target property to animate. + /// The callback to use to construct the custom animation. + /// The optional initial delay for the animation. + /// The animation duration. + /// The repeat option for the animation (defaults to one iteration). + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder NormalizedKeyFrames( + string property, + Action> build, + TimeSpan? delay = null, + TimeSpan? duration = null, + RepeatOption? repeatOption = null, + FrameworkLayer layer = FrameworkLayer.Composition) + where T : unmanaged + { + if (layer == FrameworkLayer.Composition) + { + NormalizedKeyFrameAnimationBuilder.Composition builder = new( + property, + delay, + duration ?? DefaultDuration, + repeatOption ?? RepeatOption.Once); + + build(builder); + + this.compositionAnimationFactories.Add(builder); + } + else + { + NormalizedKeyFrameAnimationBuilder.Xaml builder = new( + property, + delay, + duration ?? DefaultDuration, + repeatOption ?? RepeatOption.Once); + + build(builder); + + this.xamlAnimationFactories.Add(builder); + } + + return this; + } + + /// + /// Adds a custom animation based on normalized keyframes to the current schedule. + /// + /// The type of values to animate. + /// The type of state to pass to the builder. + /// The target property to animate. + /// The state to pass to the builder. + /// The callback to use to construct the custom animation. + /// The optional initial delay for the animation. + /// The animation duration. + /// The repeat option for the animation (defaults to one iteration). + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder NormalizedKeyFrames( + string property, + TState state, + Action, TState> build, + TimeSpan? delay = null, + TimeSpan? duration = null, + RepeatOption? repeatOption = null, + FrameworkLayer layer = FrameworkLayer.Composition) + where T : unmanaged + { + if (layer == FrameworkLayer.Composition) + { + NormalizedKeyFrameAnimationBuilder.Composition builder = new( + property, + delay, + duration ?? DefaultDuration, + repeatOption ?? RepeatOption.Once); + + build(builder, state); + + this.compositionAnimationFactories.Add(builder); + } + else + { + NormalizedKeyFrameAnimationBuilder.Xaml builder = new( + property, + delay, + duration ?? DefaultDuration, + repeatOption ?? RepeatOption.Once); + + build(builder, state); + + this.xamlAnimationFactories.Add(builder); + } + + return this; + } + + /// + /// Adds a custom animation based on timed keyframes to the current schedule. + /// + /// The type of values to animate. + /// The target property to animate. + /// The callback to use to construct the custom animation. + /// The optional initial delay for the animation. + /// The repeat option for the animation (defaults to one iteration). + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder TimedKeyFrames( + string property, + Action> build, + TimeSpan? delay = null, + RepeatOption? repeat = null, + FrameworkLayer layer = FrameworkLayer.Composition) + where T : unmanaged + { + if (layer == FrameworkLayer.Composition) + { + TimedKeyFrameAnimationBuilder.Composition builder = new(property, delay, repeat ?? RepeatOption.Once); + + build(builder); + + this.compositionAnimationFactories.Add(builder); + } + else + { + TimedKeyFrameAnimationBuilder.Xaml builder = new(property, delay, repeat ?? RepeatOption.Once); + + build(builder); + + this.xamlAnimationFactories.Add(builder); + } + + return this; + } + + /// + /// Adds a custom animation based on timed keyframes to the current schedule. + /// + /// The type of values to animate. + /// The type of state to pass to the builder. + /// The target property to animate. + /// The state to pass to the builder. + /// The callback to use to construct the custom animation. + /// The optional initial delay for the animation. + /// The repeat option for the animation (defaults to one iteration). + /// The target framework layer to animate. + /// The current instance. + public AnimationBuilder TimedKeyFrames( + string property, + TState state, + Action, TState> build, + TimeSpan? delay = null, + RepeatOption? repeatOption = null, + FrameworkLayer layer = FrameworkLayer.Composition) + where T : unmanaged + { + if (layer == FrameworkLayer.Composition) + { + TimedKeyFrameAnimationBuilder.Composition builder = new(property, delay, repeatOption ?? RepeatOption.Once); + + build(builder, state); + + this.compositionAnimationFactories.Add(builder); + } + else + { + TimedKeyFrameAnimationBuilder.Xaml builder = new(property, delay, repeatOption ?? RepeatOption.Once); + + build(builder, state); + + this.xamlAnimationFactories.Add(builder); + } + + return this; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.PropertyBuilders.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.PropertyBuilders.cs new file mode 100644 index 00000000000..9775b507c0e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.PropertyBuilders.cs @@ -0,0 +1,270 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + public sealed partial class AnimationBuilder + { + /// + /// A custom for a shared animation. + /// + /// The type of property to animate. + private sealed record PropertyAnimationBuilder( + AnimationBuilder Builder, + string Property, + FrameworkLayer Layer) + : IPropertyAnimationBuilder + where T : unmanaged + { + /// + public AnimationBuilder NormalizedKeyFrames( + Action> build, + TimeSpan? delay, + TimeSpan? duration, + RepeatOption? repeatOption) + { + return Builder.NormalizedKeyFrames(Property, build, delay, duration, repeatOption, Layer); + } + + /// + public AnimationBuilder NormalizedKeyFrames( + TState state, + Action, TState> build, + TimeSpan? delay, + TimeSpan? duration, + RepeatOption? repeatOption) + { + return Builder.NormalizedKeyFrames(Property, state, build, delay, duration, repeatOption, Layer); + } + + /// + public AnimationBuilder TimedKeyFrames( + Action> build, + TimeSpan? delay, + RepeatOption? repeatOption) + { + return Builder.TimedKeyFrames(Property, build, delay, repeatOption, Layer); + } + + /// + public AnimationBuilder TimedKeyFrames( + TState state, + Action, TState> build, + TimeSpan? delay, + RepeatOption? repeatOption) + { + return Builder.TimedKeyFrames(Property, state, build, delay, repeatOption, Layer); + } + } + + /// + /// A custom for a composition clip animation. + /// + private sealed record CompositionClipAnimationBuilder( + AnimationBuilder Builder, + string Property) + : IPropertyAnimationBuilder + { + /// + public AnimationBuilder NormalizedKeyFrames( + Action> build, + TimeSpan? delay, + TimeSpan? duration, + RepeatOption? repeatOption) + { + NormalizedKeyFrameAnimationBuilder.Composition builder = new( + Property, + delay, + duration ?? DefaultDuration, + repeatOption ?? RepeatOption.Once); + + build(builder); + + Builder.compositionAnimationFactories.Add(new Factory(builder)); + + return Builder; + } + + /// + public AnimationBuilder NormalizedKeyFrames( + TState state, + Action, TState> build, + TimeSpan? delay, + TimeSpan? duration, + RepeatOption? repeatOption) + { + NormalizedKeyFrameAnimationBuilder.Composition builder = new( + Property, + delay, + duration ?? DefaultDuration, + repeatOption ?? RepeatOption.Once); + + build(builder, state); + + Builder.compositionAnimationFactories.Add(new Factory(builder)); + + return Builder; + } + + /// + public AnimationBuilder TimedKeyFrames( + Action> build, + TimeSpan? delay, + RepeatOption? repeatOption) + { + TimedKeyFrameAnimationBuilder.Composition builder = new(Property, delay, repeatOption ?? RepeatOption.Once); + + build(builder); + + Builder.compositionAnimationFactories.Add(new Factory(builder)); + + return Builder; + } + + /// + public AnimationBuilder TimedKeyFrames( + TState state, + Action, TState> build, + TimeSpan? delay, + RepeatOption? repeatOption) + { + TimedKeyFrameAnimationBuilder.Composition builder = new(Property, delay, repeatOption ?? RepeatOption.Once); + + build(builder, state); + + Builder.compositionAnimationFactories.Add(new Factory(builder)); + + return Builder; + } + + /// + /// A private factory implementing . + /// + private sealed record Factory(ICompositionAnimationFactory Builder) : ICompositionAnimationFactory + { + /// + public CompositionAnimation GetAnimation(CompositionObject targetHint, out CompositionObject? target) + { + Visual visual = (Visual)targetHint; + InsetClip clip = visual.Clip as InsetClip ?? (InsetClip)(visual.Clip = visual.Compositor.CreateInsetClip()); + CompositionAnimation animation = Builder.GetAnimation(clip, out _); + + target = clip; + + return animation; + } + } + } + + /// + /// A custom for a XAML transform animation. + /// + private sealed record XamlTransformPropertyAnimationBuilder( + AnimationBuilder Builder, + string Property) + : IPropertyAnimationBuilder + { + /// + public AnimationBuilder NormalizedKeyFrames( + Action> build, + TimeSpan? delay, + TimeSpan? duration, + RepeatOption? repeatOption) + { + NormalizedKeyFrameAnimationBuilder.Xaml builder = new( + Property, + delay, + duration ?? DefaultDuration, + repeatOption ?? RepeatOption.Once); + + build(builder); + + Builder.xamlAnimationFactories.Add(new Factory(builder)); + + return Builder; + } + + /// + public AnimationBuilder NormalizedKeyFrames( + TState state, + Action, TState> build, + TimeSpan? delay, + TimeSpan? duration, + RepeatOption? repeatOption) + { + NormalizedKeyFrameAnimationBuilder.Xaml builder = new( + Property, + delay, + duration ?? DefaultDuration, + repeatOption ?? RepeatOption.Once); + + build(builder, state); + + Builder.xamlAnimationFactories.Add(new Factory(builder)); + + return Builder; + } + + /// + public AnimationBuilder TimedKeyFrames( + Action> build, + TimeSpan? delay, + RepeatOption? repeatOption) + { + TimedKeyFrameAnimationBuilder.Xaml builder = new(Property, delay, repeatOption ?? RepeatOption.Once); + + build(builder); + + Builder.xamlAnimationFactories.Add(new Factory(builder)); + + return Builder; + } + + /// + public AnimationBuilder TimedKeyFrames( + TState state, + Action, TState> build, + TimeSpan? delay, + RepeatOption? repeatOption) + { + TimedKeyFrameAnimationBuilder.Xaml builder = new(Property, delay, repeatOption ?? RepeatOption.Once); + + build(builder, state); + + Builder.xamlAnimationFactories.Add(new Factory(builder)); + + return Builder; + } + + /// + /// A private factory implementing . + /// + private sealed record Factory(IXamlAnimationFactory Builder) : IXamlAnimationFactory + { + /// + public Timeline GetAnimation(DependencyObject targetHint) + { + UIElement element = (UIElement)targetHint; + + if (element.RenderTransform is not CompositeTransform transform) + { + element.RenderTransform = transform = new CompositeTransform(); + } + + return Builder.GetAnimation(transform); + } + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Setup.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Setup.cs new file mode 100644 index 00000000000..f095bab5736 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.Setup.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A that allows to build custom animations targeting both the XAML and composition layers. + /// + public sealed partial class AnimationBuilder + { + /// + /// The list of instances representing factories for composition animations to run. + /// + private readonly List compositionAnimationFactories = new(); + + /// + /// The list of instances representing factories for XAML animations to run. + /// + private readonly List xamlAnimationFactories = new(); + + /// + /// Adds a new composition animation to the current schedule. + /// + /// The type of values to animate. + /// The target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The animation duration. + /// The easing function for the animation. + /// The easing mode for the animation. + /// The current instance. + private AnimationBuilder AddCompositionAnimationFactory( + string property, + T to, + T? from, + TimeSpan? delay, + TimeSpan? duration, + EasingType easingType, + EasingMode easingMode) + where T : unmanaged + { + AnimationFactory animation = new( + property, + to, + from, + delay ?? DefaultDelay, + duration ?? DefaultDuration, + easingType, + easingMode); + + this.compositionAnimationFactories.Add(animation); + + return this; + } + + /// + /// Adds a new XAML animation to the current schedule. + /// + /// The type of values to animate. + /// The target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The animation duration. + /// The easing function for the animation. + /// The easing mode for the animation. + /// The current instance. + private AnimationBuilder AddXamlAnimationFactory( + string property, + T to, + T? from, + TimeSpan? delay, + TimeSpan? duration, + EasingType easingType, + EasingMode easingMode) + where T : unmanaged + { + AnimationFactory animation = new( + property, + to, + from, + delay ?? DefaultDelay, + duration ?? DefaultDuration, + easingType, + easingMode); + + this.xamlAnimationFactories.Add(animation); + + return this; + } + + /// + /// Adds a new XAML transform animation to the current schedule. + /// + /// The target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The animation duration. + /// The easing function for the animation. + /// The easing mode for the animation. + /// The current instance. + private AnimationBuilder AddXamlTransformDoubleAnimationFactory( + string property, + double to, + double? from, + TimeSpan? delay, + TimeSpan? duration, + EasingType easingType, + EasingMode easingMode) + { + XamlTransformDoubleAnimationFactory animation = new( + property, + to, + from, + delay ?? DefaultDelay, + duration ?? DefaultDuration, + easingType, + easingMode); + + this.xamlAnimationFactories.Add(animation); + + return this; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.cs new file mode 100644 index 00000000000..c139e01a288 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/AnimationBuilder.cs @@ -0,0 +1,313 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.Threading; +using System.Threading.Tasks; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A that allows to build custom animations targeting both the XAML and composition layers. + /// + public sealed partial class AnimationBuilder + { + /// + /// Initializes a new instance of the class. + /// + /// This is private as the public entry point is the method. + private AnimationBuilder() + { + } + + /// + /// + /// Creates a new instance to setup an animation schedule. + /// This can be used as the entry point to construct a custom animation sequence. + /// + /// For instance: + /// + /// AnimationBuilder.Create()
+ /// .Opacity(from: 0, to: 1)
+ /// .Translation(Axis.X, from: -40, to: 0)
+ /// .Start(MyButton); + ///
+ /// + /// Configured instances are also reusable, meaning that the same + /// one can be used to start an animation sequence on multiple elements as well. + /// + /// For instance: + /// + /// var animation = AnimationBuilder.Create().Opacity(0, 1).Size(1.2, 1);
+ ///
+ /// animation.Start(MyButton);
+ /// animation.Start(MyGrid); + ///
+ /// Alternatively, the type can be used to configure animations directly from XAML. + /// The same APIs will still be used behind the scenes to handle animations. + ///
+ /// An empty instance to use to construct an animation sequence. + [Pure] + public static AnimationBuilder Create() => new(); + + /// + /// Starts the animations present in the current instance. + /// + /// The target to animate. + public void Start(UIElement element) + { + if (this.compositionAnimationFactories.Count > 0) + { + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + + Visual visual = ElementCompositionPreview.GetElementVisual(element); + + foreach (var factory in this.compositionAnimationFactories) + { + var animation = factory.GetAnimation(visual, out var target); + + if (target is null) + { + visual.StartAnimation(animation.Target, animation); + } + else + { + target.StartAnimation(animation.Target, animation); + } + } + } + + if (this.xamlAnimationFactories.Count > 0) + { + Storyboard storyboard = new(); + + foreach (var factory in this.xamlAnimationFactories) + { + storyboard.Children.Add(factory.GetAnimation(element)); + } + + storyboard.Begin(); + } + } + + /// + /// Starts the animations present in the current instance, and + /// registers a given cancellation token to stop running animations before they complete. + /// + /// The target to animate. + /// The cancellation token to stop animations while they're running. + public void Start(UIElement element, CancellationToken token) + { + List<(CompositionObject Target, string Path)>? compositionAnimations = null; + + if (this.compositionAnimationFactories.Count > 0) + { + compositionAnimations = new List<(CompositionObject Target, string Path)>(this.compositionAnimationFactories.Count); + + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + + Visual visual = ElementCompositionPreview.GetElementVisual(element); + + foreach (var factory in this.compositionAnimationFactories) + { + var animation = factory.GetAnimation(visual, out var target); + + if (target is null) + { + visual.StartAnimation(animation.Target, animation); + } + else + { + target.StartAnimation(animation.Target, animation); + } + + compositionAnimations.Add((target ?? visual, animation.Target)); + } + } + + Storyboard? storyboard = null; + + if (this.xamlAnimationFactories.Count > 0) + { + storyboard = new Storyboard(); + + foreach (var factory in this.xamlAnimationFactories) + { + storyboard.Children.Add(factory.GetAnimation(element)); + } + + storyboard.Begin(); + } + + static void Stop(object state) + { + (List<(CompositionObject Target, string Path)>? animations, Storyboard? storyboard) = ((List<(CompositionObject, string)>?, Storyboard?))state; + + if (animations is not null) + { + foreach (var (target, path) in animations) + { + target.StopAnimation(path); + } + } + + storyboard?.Stop(); + } + + token.Register(static obj => Stop(obj), (compositionAnimations, storyboard)); + } + + /// + /// Starts the animations present in the current instance. + /// + /// The target to animate. + /// A that completes when all animations have completed. + public Task StartAsync(UIElement element) + { + Task + compositionTask = Task.CompletedTask, + xamlTask = Task.CompletedTask; + + if (this.compositionAnimationFactories.Count > 0) + { + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + + Visual visual = ElementCompositionPreview.GetElementVisual(element); + CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); + TaskCompletionSource taskCompletionSource = new(); + + batch.Completed += (_, _) => taskCompletionSource.SetResult(null); + + foreach (var factory in this.compositionAnimationFactories) + { + var animation = factory.GetAnimation(visual, out var target); + + if (target is null) + { + visual.StartAnimation(animation.Target, animation); + } + else + { + target.StartAnimation(animation.Target, animation); + } + } + + batch.End(); + + compositionTask = taskCompletionSource.Task; + } + + if (this.xamlAnimationFactories.Count > 0) + { + Storyboard storyboard = new(); + TaskCompletionSource taskCompletionSource = new(); + + foreach (var factory in this.xamlAnimationFactories) + { + storyboard.Children.Add(factory.GetAnimation(element)); + } + + storyboard.Completed += (_, _) => taskCompletionSource.SetResult(null); + storyboard.Begin(); + + xamlTask = taskCompletionSource.Task; + } + + return Task.WhenAll(compositionTask, xamlTask); + } + + /// + /// Starts the animations present in the current instance, and + /// registers a given cancellation token to stop running animations before they complete. + /// + /// The target to animate. + /// The cancellation token to stop animations while they're running. + /// A that completes when all animations have completed. + public Task StartAsync(UIElement element, CancellationToken token) + { + Task + compositionTask = Task.CompletedTask, + xamlTask = Task.CompletedTask; + List<(CompositionObject Target, string Path)>? compositionAnimations = null; + + if (this.compositionAnimationFactories.Count > 0) + { + compositionAnimations = new List<(CompositionObject Target, string Path)>(this.compositionAnimationFactories.Count); + + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + + Visual visual = ElementCompositionPreview.GetElementVisual(element); + CompositionScopedBatch batch = visual.Compositor.CreateScopedBatch(CompositionBatchTypes.Animation); + TaskCompletionSource taskCompletionSource = new(); + + batch.Completed += (_, _) => taskCompletionSource.SetResult(null); + + foreach (var factory in this.compositionAnimationFactories) + { + var animation = factory.GetAnimation(visual, out var target); + + if (target is null) + { + visual.StartAnimation(animation.Target, animation); + } + else + { + target.StartAnimation(animation.Target, animation); + } + + compositionAnimations.Add((target ?? visual, animation.Target)); + } + + batch.End(); + + compositionTask = taskCompletionSource.Task; + } + + Storyboard? storyboard = null; + + if (this.xamlAnimationFactories.Count > 0) + { + storyboard = new Storyboard(); + + TaskCompletionSource taskCompletionSource = new(); + + foreach (var factory in this.xamlAnimationFactories) + { + storyboard.Children.Add(factory.GetAnimation(element)); + } + + storyboard.Completed += (_, _) => taskCompletionSource.SetResult(null); + storyboard.Begin(); + + xamlTask = taskCompletionSource.Task; + } + + static void Stop(object state) + { + (List<(CompositionObject Target, string Path)>? animations, Storyboard? storyboard) = ((List<(CompositionObject, string)>?, Storyboard?))state; + + if (animations is not null) + { + foreach (var (target, path) in animations) + { + target.StopAnimation(path); + } + } + + storyboard?.Stop(); + } + + token.Register(static obj => Stop(obj), (compositionAnimations, storyboard)); + + return Task.WhenAll(compositionTask, xamlTask); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Helpers/ListBuilder{T}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Helpers/ListBuilder{T}.cs new file mode 100644 index 00000000000..df9bc575253 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Helpers/ListBuilder{T}.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; + +namespace Microsoft.Toolkit.Uwp.UI.Animations.Builders.Helpers +{ + /// + /// A small generic builder type that allows to create instances. + /// + /// The type of items to create a sequence of. + internal struct ListBuilder + { + /// + /// The array in use. + /// + private T[] array; + + /// + /// The current index. + /// + private int index; + + /// + /// Gets an emoty instance. + /// + public static ListBuilder Empty + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + ListBuilder builder; + builder.array = new T[1]; + builder.index = 0; + + return builder; + } + } + + /// + /// Appens an item to the current builder. + /// + /// The item to append. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Append(T item) + { + if (this.index >= this.array.Length) + { + Array.Resize(ref this.array, this.array.Length * 2); + } + + this.array[this.index++] = item; + } + + /// + /// Gets a instance with the current items. + /// + /// A instance with the current items. + [Pure] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan AsSpan() + { + return this.array.AsSpan(0, this.index); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/IKeyFrameInfo.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/IKeyFrameInfo.cs new file mode 100644 index 00000000000..d229311ec8f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/IKeyFrameInfo.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An interface representing a generic model containing info for an abstract keyframe. + /// + internal interface IKeyFrameInfo + { + /// + /// Gets the easing type to use to reach the new keyframe. + /// + EasingType EasingType { get; } + + /// + /// Gets the easing mode to use to reach the new keyframe. + /// + EasingMode EasingMode { get; } + + /// + /// Gets the value for the new keyframe to add. + /// + /// The type of values being set by the animation being constructed. + /// The value for the current keyframe. + [Pure] + T GetValueAs(); + + /// + /// Tries to insert an expression keyframe into the target animation, if possible. + /// + /// The target instance. + /// The total duration for the full animation. + /// Whether or not the curreent instance contained an expression. + bool TryInsertExpressionKeyFrame(KeyFrameAnimation animation, TimeSpan duration); + + /// + /// Gets the normalized progress for the current keyframe. + /// + /// The total duration for the full animation. + /// The normalized progress for the current keyframe. + [Pure] + float GetNormalizedProgress(TimeSpan duration); + + /// + /// Gets the timed progress for the current keyframe. + /// + /// The total duration for the full animation. + /// The timed progress for the current keyframe. + [Pure] + TimeSpan GetTimedProgress(TimeSpan duration); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/INormalizedKeyFrameAnimationBuilder{T}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/INormalizedKeyFrameAnimationBuilder{T}.cs new file mode 100644 index 00000000000..6ae97ee7680 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/INormalizedKeyFrameAnimationBuilder{T}.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An interface for an animation builder using normalized keyframes. + /// + /// The type of values being set by the animation being constructed. + public interface INormalizedKeyFrameAnimationBuilder + { + /// + /// Adds a new normalized keyframe to the builder in use. + /// + /// The normalized progress for the keyframe (must be in the [0, 1] range). + /// The value for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + /// The same instance that the method was invoked upon. + INormalizedKeyFrameAnimationBuilder KeyFrame( + double progress, + T value, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode); + + /// + /// Adds a new normalized expression keyframe to the builder in use. + /// This method can only be used when the animation being built targets the composition layer. + /// + /// The normalized progress for the keyframe (must be in the [0, 1] range). + /// The expression for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + /// The same instance that the method was invoked upon. + /// Thrown when the animation being built targets the XAML layer. + INormalizedKeyFrameAnimationBuilder ExpressionKeyFrame( + double progress, + string expression, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/IPropertyAnimationBuilder{T}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/IPropertyAnimationBuilder{T}.cs new file mode 100644 index 00000000000..40d4989b5ba --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/IPropertyAnimationBuilder{T}.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An animation for an animation builder using keyframes, targeting a specific property. + /// + /// The type of values being set by the animation being constructed. + public interface IPropertyAnimationBuilder + { + /// + /// Adds a custom animation based on normalized keyframes ot the current schedule. + /// + /// The callback to use to construct the custom animation. + /// The optional initial delay for the animation. + /// The animation duration. + /// The repeat option for the animation (defaults to one iteration). + /// The current instance. + AnimationBuilder NormalizedKeyFrames( + Action> build, + TimeSpan? delay = null, + TimeSpan? duration = null, + RepeatOption? repeat = null); + + /// + /// Adds a custom animation based on normalized keyframes ot the current schedule. + /// + /// The type of state to pass to the builder. + /// The state to pass to the builder. + /// The callback to use to construct the custom animation. + /// The optional initial delay for the animation. + /// The animation duration. + /// The repeat option for the animation (defaults to one iteration). + /// The current instance. + AnimationBuilder NormalizedKeyFrames( + TState state, + Action, TState> build, + TimeSpan? delay = null, + TimeSpan? duration = null, + RepeatOption? repeat = null); + + /// + /// Adds a custom animation based on timed keyframes to the current schedule. + /// + /// The callback to use to construct the custom animation. + /// The optional initial delay for the animation. + /// The repeat option for the animation (defaults to one iteration). + /// The current instance. + AnimationBuilder TimedKeyFrames( + Action> build, + TimeSpan? delay = null, + RepeatOption? repeat = null); + + /// + /// Adds a custom animation based on timed keyframes to the current schedule. + /// + /// The type of state to pass to the builder. + /// The state to pass to the builder. + /// The callback to use to construct the custom animation. + /// The optional initial delay for the animation. + /// The repeat option for the animation (defaults to one iteration). + /// The current instance. + AnimationBuilder TimedKeyFrames( + TState state, + Action, TState> build, + TimeSpan? delay = null, + RepeatOption? repeat = null); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/ITimedKeyFrameAnimationBuilder{T}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/ITimedKeyFrameAnimationBuilder{T}.cs new file mode 100644 index 00000000000..652b6fe5853 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/Interfaces/ITimedKeyFrameAnimationBuilder{T}.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An interface for an animation builder using timed keyframes. + /// + /// The type of values being set by the animation being constructed. + public interface ITimedKeyFrameAnimationBuilder + { + /// + /// Adds a new timed keyframe to the builder in use. + /// + /// The timed progress for the keyframe, relative to the start of the animation. + /// The value for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + /// The same instance that the method was invoked upon. + ITimedKeyFrameAnimationBuilder KeyFrame( + TimeSpan progress, + T value, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode); + + /// + /// Adds a new timed expressionkeyframe to the builder in use. + /// This method can only be used when the animation being built targets the composition layer. + /// + /// The timed progress for the keyframe, relative to the start of the animation. + /// The expression for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + /// The same instance that the method was invoked upon. + /// Thrown when the animation being built targets the XAML layer. + ITimedKeyFrameAnimationBuilder ExpressionKeyFrame( + TimeSpan progress, + string expression, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.Composition.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.Composition.cs new file mode 100644 index 00000000000..e5dc6fb44e8 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.Composition.cs @@ -0,0 +1,291 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Numerics; +using Microsoft.Toolkit.Diagnostics; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Animation; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + internal abstract partial class NormalizedKeyFrameAnimationBuilder + { + /// + /// Gets a instance representing the animation to start. + /// + /// The type of keyframes being used to define the animation. + /// The target instance to animate. + /// The target property to animate. + /// The optional initial delay for the animation. + /// The animation duration. + /// The value for the animation + /// The list of keyframes to use to build the animation. + /// A instance with the specified animation. + public static CompositionAnimation GetAnimation( + CompositionObject target, + string property, + TimeSpan? delay, + TimeSpan duration, + RepeatOption repeat, + ReadOnlySpan keyFrames) + where TKeyFrame : struct, IKeyFrameInfo + { + KeyFrameAnimation animation; + + if (typeof(T) == typeof(bool)) + { + BooleanKeyFrameAnimation boolAnimation = target.Compositor.CreateBooleanKeyFrameAnimation(); + + foreach (ref readonly var keyFrame in keyFrames) + { + if (keyFrame.TryInsertExpressionKeyFrame(boolAnimation, duration)) + { + continue; + } + + boolAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs()); + } + + animation = boolAnimation; + } + else if (typeof(T) == typeof(float)) + { + ScalarKeyFrameAnimation scalarAnimation = target.Compositor.CreateScalarKeyFrameAnimation(); + + foreach (ref readonly var keyFrame in keyFrames) + { + if (keyFrame.TryInsertExpressionKeyFrame(scalarAnimation, duration)) + { + continue; + } + + CompositionEasingFunction? easingFunction = target.Compositor.TryCreateEasingFunction(keyFrame.EasingType, keyFrame.EasingMode); + + if (easingFunction is null) + { + scalarAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs()); + } + else + { + scalarAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs(), easingFunction); + } + } + + animation = scalarAnimation; + } + else if (typeof(T) == typeof(double)) + { + ScalarKeyFrameAnimation scalarAnimation = target.Compositor.CreateScalarKeyFrameAnimation(); + + foreach (ref readonly var keyFrame in keyFrames) + { + if (keyFrame.TryInsertExpressionKeyFrame(scalarAnimation, duration)) + { + continue; + } + + CompositionEasingFunction? easingFunction = target.Compositor.TryCreateEasingFunction(keyFrame.EasingType, keyFrame.EasingMode); + + if (easingFunction is null) + { + scalarAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), (float)keyFrame.GetValueAs()); + } + else + { + scalarAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), (float)keyFrame.GetValueAs(), easingFunction); + } + } + + animation = scalarAnimation; + } + else if (typeof(T) == typeof(Vector2)) + { + Vector2KeyFrameAnimation vector2Animation = target.Compositor.CreateVector2KeyFrameAnimation(); + + foreach (ref readonly var keyFrame in keyFrames) + { + if (keyFrame.TryInsertExpressionKeyFrame(vector2Animation, duration)) + { + continue; + } + + CompositionEasingFunction? easingFunction = target.Compositor.TryCreateEasingFunction(keyFrame.EasingType, keyFrame.EasingMode); + + if (easingFunction is null) + { + vector2Animation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs()); + } + else + { + vector2Animation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs(), easingFunction); + } + } + + animation = vector2Animation; + } + else if (typeof(T) == typeof(Vector3)) + { + Vector3KeyFrameAnimation vector3Animation = target.Compositor.CreateVector3KeyFrameAnimation(); + + foreach (ref readonly var keyFrame in keyFrames) + { + if (keyFrame.TryInsertExpressionKeyFrame(vector3Animation, duration)) + { + continue; + } + + CompositionEasingFunction? easingFunction = target.Compositor.TryCreateEasingFunction(keyFrame.EasingType, keyFrame.EasingMode); + + if (easingFunction is null) + { + vector3Animation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs()); + } + else + { + vector3Animation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs(), easingFunction); + } + } + + animation = vector3Animation; + } + else if (typeof(T) == typeof(Vector4)) + { + Vector4KeyFrameAnimation vector4Animation = target.Compositor.CreateVector4KeyFrameAnimation(); + + foreach (ref readonly var keyFrame in keyFrames) + { + if (keyFrame.TryInsertExpressionKeyFrame(vector4Animation, duration)) + { + continue; + } + + CompositionEasingFunction? easingFunction = target.Compositor.TryCreateEasingFunction(keyFrame.EasingType, keyFrame.EasingMode); + + if (easingFunction is null) + { + vector4Animation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs()); + } + else + { + vector4Animation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs(), easingFunction); + } + } + + animation = vector4Animation; + } + else if (typeof(T) == typeof(Color)) + { + ColorKeyFrameAnimation colorAnimation = target.Compositor.CreateColorKeyFrameAnimation(); + + foreach (ref readonly var keyFrame in keyFrames) + { + if (keyFrame.TryInsertExpressionKeyFrame(colorAnimation, duration)) + { + continue; + } + + CompositionEasingFunction? easingFunction = target.Compositor.TryCreateEasingFunction(keyFrame.EasingType, keyFrame.EasingMode); + + if (easingFunction is null) + { + colorAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs()); + } + else + { + colorAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs(), easingFunction); + } + } + + animation = colorAnimation; + } + else if (typeof(T) == typeof(Quaternion)) + { + QuaternionKeyFrameAnimation quaternionAnimation = target.Compositor.CreateQuaternionKeyFrameAnimation(); + + foreach (ref readonly var keyFrame in keyFrames) + { + if (keyFrame.TryInsertExpressionKeyFrame(quaternionAnimation, duration)) + { + continue; + } + + CompositionEasingFunction? easingFunction = target.Compositor.TryCreateEasingFunction(keyFrame.EasingType, keyFrame.EasingMode); + + if (easingFunction is null) + { + quaternionAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs()); + } + else + { + quaternionAnimation.InsertKeyFrame(keyFrame.GetNormalizedProgress(duration), keyFrame.GetValueAs(), easingFunction); + } + } + + animation = quaternionAnimation; + } + else + { + return ThrowHelper.ThrowInvalidOperationException("Invalid animation type"); + } + + animation.Duration = duration; + + if (delay.HasValue) + { + animation.DelayTime = delay!.Value; + } + + animation.Target = property; + (animation.IterationBehavior, animation.IterationCount) = repeat.ToBehaviorAndCount(); + + return animation; + } + + /// + /// A custom class targeting the composition layer. + /// + public sealed class Composition : NormalizedKeyFrameAnimationBuilder, AnimationBuilder.ICompositionAnimationFactory + { + /// + /// Initializes a new instance of the class. + /// + /// + public Composition(string property, TimeSpan? delay, TimeSpan duration, RepeatOption repeat) + : base(property, delay, duration, repeat) + { + } + + /// + public override INormalizedKeyFrameAnimationBuilder ExpressionKeyFrame( + double progress, + string expression, + EasingType easingType, + EasingMode easingMode) + { + this.keyFrames.Append(new(progress, expression, easingType, easingMode)); + + return this; + } + + /// + public CompositionAnimation GetAnimation(CompositionObject targetHint, out CompositionObject? target) + { + target = null; + + return GetAnimation( + targetHint, + this.property, + this.delay, + this.duration, + this.repeat, + this.keyFrames.AsSpan()); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.Xaml.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.Xaml.cs new file mode 100644 index 00000000000..23665b9544b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.Xaml.cs @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + internal abstract partial class NormalizedKeyFrameAnimationBuilder + where T : unmanaged + { + /// + /// A custom class targeting the XAML layer. + /// + public sealed class Xaml : NormalizedKeyFrameAnimationBuilder, AnimationBuilder.IXamlAnimationFactory + { + /// + /// Initializes a new instance of the class. + /// + /// + public Xaml(string property, TimeSpan? delay, TimeSpan duration, RepeatOption repeat) + : base(property, delay, duration, repeat) + { + } + + /// + public override INormalizedKeyFrameAnimationBuilder ExpressionKeyFrame( + double progress, + string expression, + EasingType easingType, + EasingMode easingMode) + { + throw new InvalidOperationException("Expression keyframes can only be used on the composition layer"); + } + + /// + public Timeline GetAnimation(DependencyObject targetHint) + { + return TimedKeyFrameAnimationBuilder.GetAnimation( + targetHint, + this.property, + this.delay, + this.duration, + this.repeat, + this.keyFrames.AsSpan()); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.cs new file mode 100644 index 00000000000..ef707c415d6 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/NormalizedKeyFrameAnimationBuilder{T}.cs @@ -0,0 +1,193 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.Uwp.UI.Animations.Builders.Helpers; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Animation; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A generic keyframe animation builder. + /// + /// The type of values being set by the animation being constructed. + internal abstract partial class NormalizedKeyFrameAnimationBuilder : INormalizedKeyFrameAnimationBuilder + where T : unmanaged + { + /// + /// The target property to animate. + /// + private readonly string property; + + /// + /// The target delay for the animation, if any. + /// + private readonly TimeSpan? delay; + + /// + /// The target duration for the animation. + /// + private readonly TimeSpan duration; + + /// + /// The repeat options for the animation. + /// + private readonly RepeatOption repeat; + + /// + /// The list builder of keyframes to use. + /// + private ListBuilder keyFrames = ListBuilder.Empty; + + /// + /// Initializes a new instance of the class. + /// + /// The target property to animate. + /// The target delay for the animation. + /// The target duration for the animation. + /// The repeat options for the animation. + protected NormalizedKeyFrameAnimationBuilder(string property, TimeSpan? delay, TimeSpan duration, RepeatOption repeat) + { + this.property = property; + this.delay = delay; + this.duration = duration; + this.repeat = repeat; + } + + /// + public INormalizedKeyFrameAnimationBuilder KeyFrame( + double progress, + T value, + EasingType easingType, + EasingMode easingMode) + { + this.keyFrames.Append(new(progress, value, easingType, easingMode)); + + return this; + } + + /// + public abstract INormalizedKeyFrameAnimationBuilder ExpressionKeyFrame( + double progress, + string expression, + EasingType easingType, + EasingMode easingMode); + + /// + /// The abstracted info for a normalized animation keyframe. + /// + protected readonly struct KeyFrameInfo : IKeyFrameInfo + { + /// + /// The normalized progress for the keyframe. + /// + private readonly double progress; + + /// + /// The value for the current keyframe. + /// + private readonly T value; + + /// + /// The expression for the current keyframe, if present. + /// + private readonly string? expression; + + /// + /// Initializes a new instance of the struct. + /// + /// The normalized progress for the keyframe. + /// The value for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + public KeyFrameInfo( + double progress, + T value, + EasingType easingType, + EasingMode easingMode) + { + this.progress = progress; + this.value = value; + this.expression = null; + + EasingType = easingType; + EasingMode = easingMode; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The normalized progress for the keyframe. + /// The expression for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + public KeyFrameInfo( + double progress, + string expression, + EasingType easingType, + EasingMode easingMode) + { + this.progress = progress; + this.value = default; + this.expression = expression; + + EasingType = easingType; + EasingMode = easingMode; + } + + /// + public EasingType EasingType { get; } + + /// + public EasingMode EasingMode { get; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TValue GetValueAs() + { + return Unsafe.As(ref Unsafe.AsRef(in this.value)); + } + + /// + public bool TryInsertExpressionKeyFrame(KeyFrameAnimation animation, TimeSpan duration) + { + if (this.expression is null) + { + return false; + } + + CompositionEasingFunction? easingFunction = animation.Compositor.TryCreateEasingFunction(EasingType, EasingMode); + + if (easingFunction is null) + { + animation.InsertExpressionKeyFrame((float)this.progress, this.expression); + } + else + { + animation.InsertExpressionKeyFrame((float)this.progress, this.expression, easingFunction); + } + + return true; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float GetNormalizedProgress(TimeSpan duration) + { + return (float)this.progress; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TimeSpan GetTimedProgress(TimeSpan duration) + { + return TimeSpan.FromMilliseconds(duration.TotalMilliseconds * this.progress); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.Composition.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.Composition.cs new file mode 100644 index 00000000000..066e7ad6c1a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.Composition.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Animation; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + internal abstract partial class TimedKeyFrameAnimationBuilder + where T : unmanaged + { + /// + /// A custom class targeting the composition layer. + /// + public sealed class Composition : TimedKeyFrameAnimationBuilder, AnimationBuilder.ICompositionAnimationFactory + { + /// + /// Initializes a new instance of the class. + /// + /// + public Composition(string property, TimeSpan? delay, RepeatOption repeat) + : base(property, delay, repeat) + { + } + + /// + public override ITimedKeyFrameAnimationBuilder ExpressionKeyFrame( + TimeSpan progress, + string expression, + EasingType easingType, + EasingMode easingMode) + { + this.keyFrames.Append(new(progress, expression, easingType, easingMode)); + + return this; + } + + /// + public CompositionAnimation GetAnimation(CompositionObject targetHint, out CompositionObject? target) + { + target = null; + + // We can retrieve the total duration from the last timed keyframe, and then set + // this as the target duration and use it to normalize the keyframe progresses. + ReadOnlySpan keyFrames = this.keyFrames.AsSpan(); + TimeSpan duration = keyFrames[keyFrames.Length - 1].GetTimedProgress(default); + + return NormalizedKeyFrameAnimationBuilder.GetAnimation( + targetHint, + this.property, + this.delay, + duration, + this.repeat, + keyFrames); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.Xaml.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.Xaml.cs new file mode 100644 index 00000000000..ee6042a7a07 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.Xaml.cs @@ -0,0 +1,170 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Toolkit.Diagnostics; +using Windows.Foundation; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + internal abstract partial class TimedKeyFrameAnimationBuilder + where T : unmanaged + { + /// + /// Gets a instance representing the animation to start. + /// + /// The type of keyframes being used to define the animation. + /// The target instance to animate. + /// The target property to animate. + /// The optional initial delay for the animation. + /// The animation duration. + /// The value for the animation + /// The list of keyframes to use to build the animation. + /// A instance with the specified animation. + public static Timeline GetAnimation( + DependencyObject target, + string property, + TimeSpan? delay, + TimeSpan duration, + RepeatOption repeat, + ReadOnlySpan keyFrames) + where TKeyFrame : struct, IKeyFrameInfo + { + Timeline animation; + + if (typeof(T) == typeof(float)) + { + DoubleAnimationUsingKeyFrames doubleAnimation = new() { EnableDependentAnimation = true }; + + foreach (var keyFrame in keyFrames) + { + doubleAnimation.KeyFrames.Add(new EasingDoubleKeyFrame() + { + KeyTime = keyFrame.GetTimedProgress(duration), + Value = keyFrame.GetValueAs(), + EasingFunction = keyFrame.EasingType.ToEasingFunction(keyFrame.EasingMode) + }); + } + + animation = doubleAnimation; + } + else if (typeof(T) == typeof(double)) + { + DoubleAnimationUsingKeyFrames doubleAnimation = new() { EnableDependentAnimation = true }; + + foreach (var keyFrame in keyFrames) + { + doubleAnimation.KeyFrames.Add(new EasingDoubleKeyFrame() + { + KeyTime = keyFrame.GetTimedProgress(duration), + Value = keyFrame.GetValueAs(), + EasingFunction = keyFrame.EasingType.ToEasingFunction(keyFrame.EasingMode) + }); + } + + animation = doubleAnimation; + } + else if (typeof(T) == typeof(Point)) + { + PointAnimationUsingKeyFrames pointAnimation = new() { EnableDependentAnimation = true }; + + foreach (var keyFrame in keyFrames) + { + pointAnimation.KeyFrames.Add(new EasingPointKeyFrame() + { + KeyTime = keyFrame.GetTimedProgress(duration), + Value = keyFrame.GetValueAs(), + EasingFunction = keyFrame.EasingType.ToEasingFunction(keyFrame.EasingMode) + }); + } + + animation = pointAnimation; + } + else if (typeof(T) == typeof(Color)) + { + ColorAnimationUsingKeyFrames colorAnimation = new() { EnableDependentAnimation = true }; + + foreach (var keyFrame in keyFrames) + { + colorAnimation.KeyFrames.Add(new EasingColorKeyFrame() + { + KeyTime = keyFrame.GetTimedProgress(duration), + Value = keyFrame.GetValueAs(), + EasingFunction = keyFrame.EasingType.ToEasingFunction(keyFrame.EasingMode) + }); + } + + animation = colorAnimation; + } + else if (typeof(T) == typeof(object)) + { + ObjectAnimationUsingKeyFrames objectAnimation = new() { EnableDependentAnimation = true }; + + foreach (var keyFrame in keyFrames) + { + objectAnimation.KeyFrames.Add(new DiscreteObjectKeyFrame() + { + KeyTime = keyFrame.GetTimedProgress(duration), + Value = keyFrame.GetValueAs() + }); + } + + animation = objectAnimation; + } + else + { + return ThrowHelper.ThrowInvalidOperationException("Invalid animation type"); + } + + animation.BeginTime = delay; + animation.RepeatBehavior = repeat.ToRepeatBehavior(); + + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, property); + + return animation; + } + + /// + /// A custom class targeting the XAML layer. + /// + public sealed class Xaml : TimedKeyFrameAnimationBuilder, AnimationBuilder.IXamlAnimationFactory + { + /// + /// Initializes a new instance of the class. + /// + /// + public Xaml(string property, TimeSpan? delay, RepeatOption repeat) + : base(property, delay, repeat) + { + } + + /// + public override ITimedKeyFrameAnimationBuilder ExpressionKeyFrame( + TimeSpan progress, + string expression, + EasingType easingType, + EasingMode easingMode) + { + throw new InvalidOperationException("Expression keyframes can only be used on the composition layer"); + } + + /// + public Timeline GetAnimation(DependencyObject targetHint) + { + return GetAnimation( + targetHint, + this.property, + this.delay, + default, + this.repeat, + this.keyFrames.AsSpan()); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.cs new file mode 100644 index 00000000000..6c0368d6f55 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Builders/TimedKeyFrameAnimationBuilder{T}.cs @@ -0,0 +1,186 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Runtime.CompilerServices; +using Microsoft.Toolkit.Uwp.UI.Animations.Builders.Helpers; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Animation; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A generic keyframe animation builder. + /// + /// The type of values being set by the animation being constructed. + internal abstract partial class TimedKeyFrameAnimationBuilder : ITimedKeyFrameAnimationBuilder + where T : unmanaged + { + /// + /// The target property to animate. + /// + private readonly string property; + + /// + /// The target delay for the animation, if any. + /// + private readonly TimeSpan? delay; + + /// + /// The repeat options for the animation. + /// + private readonly RepeatOption repeat; + + /// + /// The list builder of keyframes to use. + /// + private ListBuilder keyFrames = ListBuilder.Empty; + + /// + /// Initializes a new instance of the class. + /// + /// The target property to animate. + /// The target delay for the animation. + /// The repeat options for the animation. + protected TimedKeyFrameAnimationBuilder(string property, TimeSpan? delay, RepeatOption repeat) + { + this.property = property; + this.delay = delay; + this.repeat = repeat; + } + + /// + public ITimedKeyFrameAnimationBuilder KeyFrame( + TimeSpan progress, + T value, + EasingType easingType, + EasingMode easingMode) + { + this.keyFrames.Append(new(progress, value, easingType, easingMode)); + + return this; + } + + /// + public abstract ITimedKeyFrameAnimationBuilder ExpressionKeyFrame( + TimeSpan progress, + string expression, + EasingType easingType, + EasingMode easingMode); + + /// + /// The abstracted info for a timed animation keyframe. + /// + protected readonly struct KeyFrameInfo : IKeyFrameInfo + { + /// + /// The progress for the keyframe. + /// + private readonly TimeSpan progress; + + /// + /// The value for the current keyframe. + /// + private readonly T value; + + /// + /// The expression for the current keyframe, if present. + /// + private readonly string? expression; + + /// + /// Initializes a new instance of the struct. + /// + /// The progress for the keyframe. + /// The value for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + public KeyFrameInfo( + TimeSpan progress, + T value, + EasingType easingType, + EasingMode easingMode) + { + this.progress = progress; + this.value = value; + this.expression = null; + + EasingType = easingType; + EasingMode = easingMode; + } + + /// + /// Initializes a new instance of the struct. + /// + /// The progress for the keyframe. + /// The expression for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + public KeyFrameInfo( + TimeSpan progress, + string expression, + EasingType easingType, + EasingMode easingMode) + { + this.progress = progress; + this.value = default; + this.expression = expression; + + EasingType = easingType; + EasingMode = easingMode; + } + + /// + public EasingType EasingType { get; } + + /// + public EasingMode EasingMode { get; } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TValue GetValueAs() + { + return Unsafe.As(ref Unsafe.AsRef(in this.value)); + } + + /// + public bool TryInsertExpressionKeyFrame(KeyFrameAnimation animation, TimeSpan duration) + { + if (this.expression is null) + { + return false; + } + + CompositionEasingFunction? easingFunction = animation.Compositor.TryCreateEasingFunction(EasingType, EasingMode); + + if (easingFunction is null) + { + animation.InsertExpressionKeyFrame(GetNormalizedProgress(duration), this.expression); + } + else + { + animation.InsertExpressionKeyFrame(GetNormalizedProgress(duration), this.expression, easingFunction); + } + + return true; + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public float GetNormalizedProgress(TimeSpan duration) + { + return (float)Math.Clamp(this.progress.TotalMilliseconds * 100 / duration.TotalMilliseconds, 0, 1); + } + + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public TimeSpan GetTimedProgress(TimeSpan duration) + { + return this.progress; + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/AnimationCollection.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/AnimationCollection.cs deleted file mode 100644 index 11d387e30be..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/AnimationCollection.cs +++ /dev/null @@ -1,218 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Windows.UI.Composition; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Hosting; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// An ObservableCollection of - /// - public class AnimationCollection : IList - { - private readonly List _internalList = new List(); - - // needed in order to be able to update animations when a animations are added/removed or - // animation properties change (for example in binding) - private WeakReference _parent; - - internal UIElement Parent - { - get - { - _parent.TryGetTarget(out var element); - return element; - } - - set => _parent = new WeakReference(value); - } - - /// - /// Gets a value indicating whether the collection contains an animation that targets the Translation property - /// - public bool ContainsTranslationAnimation => this.Count(anim => !string.IsNullOrWhiteSpace(anim.Target) && anim.Target.StartsWith("Translation")) > 0; - - /// - public int Count => _internalList.Count; - - /// - public bool IsReadOnly => false; - - /// - public AnimationBase this[int index] { get => _internalList[index]; set => Insert(index, value); } - - /// - /// Raised when an animation has been added/removed or modified - /// - public event EventHandler AnimationCollectionChanged; - - /// - /// Starts the animations in the collection - /// - /// The to be animated - public void StartAnimation(UIElement element) - { - foreach (var animation in this) - { - animation.StartAnimation(element); - } - } - - /// - /// Creates a that can be used to animate a visual on the - /// Composition layer - /// - /// The element used to get the - /// - internal CompositionAnimationGroup GetCompositionAnimationGroup(UIElement element) - { - var visual = ElementCompositionPreview.GetElementVisual(element); - var compositor = visual.Compositor; - var animationGroup = compositor.CreateAnimationGroup(); - - foreach (var cAnim in this) - { - var compositionAnimation = cAnim.GetCompositionAnimation(compositor); - if (compositionAnimation != null) - { - animationGroup.Add(compositionAnimation); - } - } - - return animationGroup; - } - - /// - /// Creates a that can be used to apply implicit animation on a - /// visual on the Composition layer - /// - /// The element used to get the - /// - internal ImplicitAnimationCollection GetImplicitAnimationCollection(UIElement element) - { - var visual = ElementCompositionPreview.GetElementVisual(element); - var compositor = visual.Compositor; - var implicitAnimations = compositor.CreateImplicitAnimationCollection(); - - var animations = new Dictionary(); - - foreach (var cAnim in this) - { - CompositionAnimation animation; - if (!string.IsNullOrWhiteSpace(cAnim.Target) - && (animation = cAnim.GetCompositionAnimation(compositor)) != null) - { - var target = cAnim.ImplicitTarget ?? cAnim.Target; - if (!animations.ContainsKey(target)) - { - animations[target] = compositor.CreateAnimationGroup(); - } - - animations[target].Add(animation); - } - } - - foreach (var kv in animations) - { - implicitAnimations[kv.Key] = kv.Value; - } - - return implicitAnimations; - } - - private void AnimationChanged(object sender, EventArgs e) - { - AnimationCollectionChanged?.Invoke(this, EventArgs.Empty); - } - - /// - public int IndexOf(AnimationBase item) - { - return _internalList.IndexOf(item); - } - - /// - public void Insert(int index, AnimationBase item) - { - item.AnimationChanged += AnimationChanged; - _internalList.Insert(index, item); - AnimationCollectionChanged?.Invoke(this, EventArgs.Empty); - } - - /// - public void RemoveAt(int index) - { - if (index >= 0 && index < _internalList.Count) - { - var animation = _internalList[index]; - animation.AnimationChanged -= AnimationChanged; - } - - _internalList.RemoveAt(index); - AnimationCollectionChanged?.Invoke(this, EventArgs.Empty); - } - - /// - public void Add(AnimationBase item) - { - item.AnimationChanged += AnimationChanged; - _internalList.Add(item); - AnimationCollectionChanged?.Invoke(this, EventArgs.Empty); - } - - /// - public void Clear() - { - foreach (var animation in _internalList) - { - animation.AnimationChanged -= AnimationChanged; - } - - _internalList.Clear(); - AnimationCollectionChanged?.Invoke(this, EventArgs.Empty); - } - - /// - public bool Contains(AnimationBase item) - { - return _internalList.Contains(item); - } - - /// - public void CopyTo(AnimationBase[] array, int arrayIndex) - { - _internalList.CopyTo(array, arrayIndex); - } - - /// - public bool Remove(AnimationBase item) - { - var result = _internalList.Remove(item); - if (result) - { - item.AnimationChanged -= AnimationChanged; - AnimationCollectionChanged?.Invoke(this, EventArgs.Empty); - } - - return result; - } - - /// - public IEnumerator GetEnumerator() - { - return _internalList.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return _internalList.GetEnumerator(); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/AnimationBase.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/AnimationBase.cs deleted file mode 100644 index 7ed9584a0db..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/AnimationBase.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Windows.UI.Composition; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Hosting; -using Windows.UI.Xaml.Markup; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Abstract class providing common dependency properties for composition animations - /// - [ContentProperty(Name = nameof(KeyFrames))] - public abstract partial class AnimationBase : DependencyObject - { - /// - /// Identifies the property - /// - public static readonly DependencyProperty TargetProperty = - DependencyProperty.Register(nameof(Target), typeof(string), typeof(AnimationBase), new PropertyMetadata(null, OnAnimationPropertyChanged)); - - /// - /// Identifies the property - /// - public static readonly DependencyProperty DurationProperty = - DependencyProperty.Register(nameof(Duration), typeof(TimeSpan), typeof(AnimationBase), new PropertyMetadata(TimeSpan.FromMilliseconds(400), OnAnimationPropertyChanged)); - - /// - /// Identifies the property - /// - public static readonly DependencyProperty KeyFramesProperty = - DependencyProperty.Register(nameof(KeyFrames), typeof(KeyFrameCollection), typeof(AnimationBase), new PropertyMetadata(null, OnAnimationPropertyChanged)); - - /// - /// Identifies the property - /// - public static readonly DependencyProperty ImplicitTargetProperty = - DependencyProperty.Register(nameof(ImplicitTarget), typeof(string), typeof(AnimationBase), new PropertyMetadata(null, OnAnimationPropertyChanged)); - - /// - /// Identifies the property - /// - public static readonly DependencyProperty DelayProperty = - DependencyProperty.Register(nameof(Delay), typeof(TimeSpan), typeof(AnimationBase), new PropertyMetadata(TimeSpan.Zero, OnAnimationPropertyChanged)); - - /// - /// Identifies the property - /// - public static readonly DependencyProperty SetInitialValueBeforeDelayProperty = - DependencyProperty.Register(nameof(SetInitialValueBeforeDelay), typeof(bool), typeof(AnimationBase), new PropertyMetadata(false, OnAnimationPropertyChanged)); - - /// - /// Initializes a new instance of the class. - /// - public AnimationBase() - { - if (KeyFrames == null) - { - KeyFrames = new KeyFrameCollection(); - } - } - - /// - /// Raised when a property changes - /// - public event EventHandler AnimationChanged; - - /// - /// Gets or sets the duration of the animation - /// - public TimeSpan Duration - { - get { return (TimeSpan)GetValue(DurationProperty); } - set { SetValue(DurationProperty, value); } - } - - /// - /// Gets or sets the of the animations - /// - public KeyFrameCollection KeyFrames - { - get { return (KeyFrameCollection)GetValue(KeyFramesProperty); } - set { SetValue(KeyFramesProperty, value); } - } - - /// - /// Gets or sets the target property to be animated - /// - public string Target - { - get { return (string)GetValue(TargetProperty); } - set { SetValue(TargetProperty, value); } - } - - /// - /// Gets or sets the property that should start the implicit animation - /// - public string ImplicitTarget - { - get { return (string)GetValue(ImplicitTargetProperty); } - set { SetValue(ImplicitTargetProperty, value); } - } - - /// - /// Gets or sets the delay of the animation - /// - public TimeSpan Delay - { - get { return (TimeSpan)GetValue(DelayProperty); } - set { SetValue(DelayProperty, value); } - } - - /// - /// Gets or sets a value indicating whether the value at keyframe 0 should be set before the delay - /// - public bool SetInitialValueBeforeDelay - { - get { return (bool)GetValue(SetInitialValueBeforeDelayProperty); } - set { SetValue(SetInitialValueBeforeDelayProperty, value); } - } - - /// - /// Starts the animation on the specified element - /// - /// The to be animated - public void StartAnimation(UIElement element) - { - if (element == null) - { - return; - } - - var visual = ElementCompositionPreview.GetElementVisual(element); - var compositor = visual.Compositor; - - if (Target.Contains("Translation")) - { - ElementCompositionPreview.SetIsTranslationEnabled(element, true); - } - - var compositionAnimation = GetCompositionAnimation(compositor); - visual.StartAnimation(Target, compositionAnimation); - } - - /// - /// Gets a that can be used on the Composition layer - /// - /// The to use to create the animation - /// - public abstract CompositionAnimation GetCompositionAnimation(Compositor compositor); - - /// - /// Called when any property of the animation changes - /// - protected void OnAnimationChanged() - { - AnimationChanged?.Invoke(this, EventArgs.Empty); - } - - /// - /// Called when any property of the animation changes - /// - /// The animation where a property has changed - /// The details about the property change - private static void OnAnimationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - (d as AnimationBase).OnAnimationChanged(); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/OffsetAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/OffsetAnimation.cs deleted file mode 100644 index 84bcd413217..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/OffsetAnimation.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Vector3Animation that animates the property - /// - public class OffsetAnimation : Vector3Animation - { - /// - /// Initializes a new instance of the class. - /// - public OffsetAnimation() - { - Target = nameof(Visual.Offset); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/OpacityAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/OpacityAnimation.cs deleted file mode 100644 index 572fd9fdc48..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/OpacityAnimation.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// ScalarAnimation that animates the property - /// - public class OpacityAnimation : ScalarAnimation - { - /// - /// Initializes a new instance of the class. - /// - public OpacityAnimation() - { - Target = nameof(Visual.Opacity); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/RotationAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/RotationAnimation.cs deleted file mode 100644 index 8f28e9e0474..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/RotationAnimation.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// ScalarAnimation that animates the property - /// - public class RotationAnimation : ScalarAnimation - { - /// - /// Initializes a new instance of the class. - /// - public RotationAnimation() - { - Target = nameof(Visual.RotationAngle); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/RotationInDegreesAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/RotationInDegreesAnimation.cs deleted file mode 100644 index aa40df2f999..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/RotationInDegreesAnimation.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// ScalarAnimation that animates the property - /// - public class RotationInDegreesAnimation : ScalarAnimation - { - /// - /// Initializes a new instance of the class. - /// - public RotationInDegreesAnimation() - { - Target = nameof(Visual.RotationAngleInDegrees); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/ScalarAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/ScalarAnimation.cs deleted file mode 100644 index d92c75e5240..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/ScalarAnimation.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Animation that animates a value of type float - /// - public class ScalarAnimation : TypedAnimationBase - { - /// - protected override KeyFrameAnimation GetTypedAnimationFromCompositor(Compositor compositor) - { - return compositor.CreateScalarKeyFrameAnimation(); - } - - /// - protected override void InsertKeyFrameToTypedAnimation(KeyFrameAnimation animation, ScalarKeyFrame keyFrame) - { - (animation as ScalarKeyFrameAnimation).InsertKeyFrame((float)keyFrame.Key, (float)keyFrame.Value); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/ScaleAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/ScaleAnimation.cs deleted file mode 100644 index 29167bd1047..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/ScaleAnimation.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Vector3Animation that animates the property - /// - public class ScaleAnimation : Vector3Animation - { - /// - /// Initializes a new instance of the class. - /// - public ScaleAnimation() - { - Target = nameof(Visual.Scale); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/TranslationAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/TranslationAnimation.cs deleted file mode 100644 index 1ce4a10ee8a..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/TranslationAnimation.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Composition; -using Windows.UI.Xaml.Hosting; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Vector3Animation that animates the Translation property - /// - /// - public class TranslationAnimation : Vector3Animation - { - /// - /// Initializes a new instance of the class. - /// - public TranslationAnimation() - { - Target = "Translation"; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/TypedAnimationBase.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/TypedAnimationBase.cs deleted file mode 100644 index fed3156586c..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/TypedAnimationBase.cs +++ /dev/null @@ -1,164 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Windows.UI.Composition; -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// A generic class extending to provide common implementation for most animations - /// - /// Type of to use - /// Type of value being animated. - public abstract class TypedAnimationBase : AnimationBase - where TKeyFrame : TypedKeyFrame, new() - { - private TKeyFrame _fromKeyFrame; - private TKeyFrame _toKeyFrame; - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty FromProperty = - DependencyProperty.Register(nameof(From), typeof(U), typeof(TypedAnimationBase), new PropertyMetadata(GetDefaultValue(), OnAnimationPropertyChanged)); - - /// - /// Identifies the dependency property. - /// - public static readonly DependencyProperty ToProperty = - DependencyProperty.Register(nameof(To), typeof(U), typeof(TypedAnimationBase), new PropertyMetadata(GetDefaultValue(), OnAnimationPropertyChanged)); - - /// - /// Gets or sets the value at the beginning. - /// Setting this value adds a new where the Key = 0 - /// - public U From - { - get { return (U)GetValue(FromProperty); } - set { SetValue(FromProperty, value); } - } - - /// - /// Gets or sets the value at the end. - /// Setting this value generates a new where the Key = 1 - /// - public U To - { - get { return (U)GetValue(ToProperty); } - set { SetValue(ToProperty, value); } - } - - /// - public override CompositionAnimation GetCompositionAnimation(Compositor compositor) - { - if (DesignTimeHelpers.IsRunningInLegacyDesignerMode) - { - return null; - } - - if (string.IsNullOrWhiteSpace(Target)) - { - return null; - } - - PrepareKeyFrames(); - var animation = GetTypedAnimationFromCompositor(compositor); - animation.Target = Target; - animation.Duration = Duration; - animation.DelayTime = Delay; - animation.DelayBehavior = SetInitialValueBeforeDelay ? AnimationDelayBehavior.SetInitialValueBeforeDelay : AnimationDelayBehavior.SetInitialValueAfterDelay; - - if (KeyFrames.Count == 0) - { - animation.InsertExpressionKeyFrame(1.0f, "this.FinalValue"); - return animation; - } - - foreach (var keyFrame in KeyFrames) - { - if (keyFrame is TKeyFrame typedKeyFrame) - { - InsertKeyFrameToTypedAnimation(animation, typedKeyFrame); - } - else if (keyFrame is ExpressionKeyFrame expressionKeyFrame) - { - animation.InsertExpressionKeyFrame((float)keyFrame.Key, expressionKeyFrame.Value); - } - } - - return animation; - } - - /// - /// Creates a composition animation for the property to be animated - /// - /// used to create the animation - /// - protected abstract KeyFrameAnimation GetTypedAnimationFromCompositor(Compositor compositor); - - /// - /// Inserts the value and a specified key in the typed - /// - /// The animation where the key frame will be inserted - /// The key frame that will be inserted - protected abstract void InsertKeyFrameToTypedAnimation(KeyFrameAnimation animation, TKeyFrame keyFrame); - - private static void OnAnimationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - ((TypedAnimationBase)d).OnAnimationChanged(); - } - - // these two methods are required to support double (non nullable type) - private static object GetDefaultValue() - { - if (typeof(U) == typeof(double)) - { - return double.NaN; - } - - return default(U); - } - - private static bool IsValueNull(U value) - { - if (typeof(U) == typeof(double)) - { - return double.IsNaN((double)(object)value); - } - - return value == null; - } - - private void PrepareKeyFrames() - { - if (_fromKeyFrame != null) - { - KeyFrames.Remove(_fromKeyFrame); - } - - if (_toKeyFrame != null) - { - KeyFrames.Remove(_toKeyFrame); - } - - if (!IsValueNull(From)) - { - _fromKeyFrame = new TKeyFrame(); - _fromKeyFrame.Key = 0f; - _fromKeyFrame.Value = From; - KeyFrames.Add(_fromKeyFrame); - } - - if (!IsValueNull(To)) - { - _toKeyFrame = new TKeyFrame(); - _toKeyFrame.Key = 1f; - _toKeyFrame.Value = To; - KeyFrames.Add(_toKeyFrame); - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector2Animation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector2Animation.cs deleted file mode 100644 index 21c8d57ea2b..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector2Animation.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Numerics; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Animation that animates a value of type - /// - public class Vector2Animation : TypedAnimationBase - { - /// - protected override KeyFrameAnimation GetTypedAnimationFromCompositor(Compositor compositor) - { - return compositor.CreateVector2KeyFrameAnimation(); - } - - /// - protected override void InsertKeyFrameToTypedAnimation(KeyFrameAnimation animation, Vector2KeyFrame keyFrame) - { - (animation as Vector2KeyFrameAnimation).InsertKeyFrame((float)keyFrame.Key, keyFrame.Value.ToVector2()); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector3Animation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector3Animation.cs deleted file mode 100644 index 691aab21bad..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector3Animation.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Numerics; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Animation that animates a value of type - /// - public class Vector3Animation : TypedAnimationBase - { - /// - protected override KeyFrameAnimation GetTypedAnimationFromCompositor(Compositor compositor) - { - return compositor.CreateVector3KeyFrameAnimation(); - } - - /// - protected override void InsertKeyFrameToTypedAnimation(KeyFrameAnimation animation, Vector3KeyFrame keyFrame) - { - (animation as Vector3KeyFrameAnimation).InsertKeyFrame((float)keyFrame.Key, keyFrame.Value.ToVector3()); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector4Animation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector4Animation.cs deleted file mode 100644 index f902f20b0cb..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/Animations/Vector4Animation.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Numerics; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Animation that animates a value of type - /// - public class Vector4Animation : TypedAnimationBase - { - /// - protected override KeyFrameAnimation GetTypedAnimationFromCompositor(Compositor compositor) - { - return compositor.CreateVector4KeyFrameAnimation(); - } - - /// - protected override void InsertKeyFrameToTypedAnimation(KeyFrameAnimation animation, Vector4KeyFrame keyFrame) - { - (animation as Vector4KeyFrameAnimation).InsertKeyFrame((float)keyFrame.Key, keyFrame.Value.ToVector4()); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/ExpressionKeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/ExpressionKeyFrame.cs deleted file mode 100644 index b50c3f4a106..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/ExpressionKeyFrame.cs +++ /dev/null @@ -1,13 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// of type string - /// - public class ExpressionKeyFrame : TypedKeyFrame - { - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/KeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/KeyFrame.cs deleted file mode 100644 index f3fd4c650da..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/KeyFrame.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Provides common Dependency properties for KeyFrames - /// - public abstract partial class KeyFrame : DependencyObject - { - /// - /// Identifies the dependency property - /// - public static readonly DependencyProperty KeyProperty = - DependencyProperty.Register(nameof(Key), typeof(double), typeof(KeyFrame), new PropertyMetadata(0.0)); - - /// - /// Gets or sets the key of the key frame - /// Value should be between 0.0 and 1.0 - /// - public double Key - { - get { return (double)GetValue(KeyProperty); } - set { SetValue(KeyProperty, value); } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/KeyFrameCollection.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/KeyFrameCollection.cs deleted file mode 100644 index ebee73c4e47..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/KeyFrameCollection.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Collections.Generic; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// A collection of - /// - public class KeyFrameCollection : List - { - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/TypedKeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/TypedKeyFrame.cs deleted file mode 100644 index 1d2c7cd2722..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/TypedKeyFrame.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Provides type implementation of - /// - /// The type of property being animated - public abstract class TypedKeyFrame : KeyFrame - { - /// - /// Gets or sets the value at the specific key - /// - public T Value - { - get { return (T)GetValue(ValueProperty); } - set { SetValue(ValueProperty, value); } - } - - /// - /// Identifies the dependency property - /// - public static readonly DependencyProperty ValueProperty = - DependencyProperty.Register(nameof(Value), typeof(T), typeof(TypedKeyFrame), new PropertyMetadata(default(T))); - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector3KeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector3KeyFrame.cs deleted file mode 100644 index 98bca721815..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector3KeyFrame.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Numerics; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// of type - /// - public class Vector3KeyFrame : TypedKeyFrame - { - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector4KeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector4KeyFrame.cs deleted file mode 100644 index f7675638b53..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector4KeyFrame.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System.Numerics; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// of type - /// - public class Vector4KeyFrame : TypedKeyFrame - { - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/EasingType.cs b/Microsoft.Toolkit.Uwp.UI.Animations/EasingType.cs deleted file mode 100644 index b233d652652..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/EasingType.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// EasingType is used to describe how the animation interpolates between keyframes. - /// - public enum EasingType - { - /// - /// Creates an animation that accelerates with the default EasingType which is specified in AnimationExtensions.DefaultEasingType which is by default Cubic. - /// - Default, - - /// - /// Creates an animation that accelerates or decelerates linearly. - /// - Linear, - - /// - /// Creates an animation that accelerates or decelerates using the formula f(t) = t3. - /// - Cubic, - - /// - /// Retracts the motion of an animation slightly before it begins to animate in the path indicated. - /// - Back, - - /// - /// Creates a bouncing effect. - /// - Bounce, - - /// - /// Creates an animation that resembles a spring oscillating back and forth until it comes to rest. - /// - Elastic, - - /// - /// Creates an animation that accelerates or decelerates using a circular function. - /// - Circle, - - /// - /// Creates an animation that accelerates or decelerates using the formula f(t) = t2. - /// - Quadratic, - - /// - /// Creates an animation that accelerates or decelerates using the formula f(t) = t4. - /// - Quartic, - - /// - /// Create an animation that accelerates or decelerates using the formula f(t) = t5. - /// - Quintic, - - /// - /// Creates an animation that accelerates or decelerates using a sine formula. - /// - Sine - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/EffectAnimationDefinition.cs b/Microsoft.Toolkit.Uwp.UI.Animations/EffectAnimationDefinition.cs deleted file mode 100644 index 5c00ac95b2a..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/EffectAnimationDefinition.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Defines an which is used by - /// to link effect animations to Visuals - /// - internal class EffectAnimationDefinition - { - /// - /// Gets or sets that will be animated - /// - public CompositionObject EffectBrush { get; set; } - - /// - /// Gets or sets the - /// - public CompositionAnimation Animation { get; set; } - - /// - /// Gets or sets the property name that will be animated on the - /// - public string PropertyName { get; set; } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/EffectDirectPropertyChangeDefinition.cs b/Microsoft.Toolkit.Uwp.UI.Animations/EffectDirectPropertyChangeDefinition.cs deleted file mode 100644 index 04ccef0e94f..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/EffectDirectPropertyChangeDefinition.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Defines an which is used by - /// to link effect property Changes to Visuals - /// - internal class EffectDirectPropertyChangeDefinition - { - /// - /// Gets or sets that will be animated - /// - public CompositionObject EffectBrush { get; set; } - - /// - /// Gets or sets the value for the property - /// - public float Value { get; set; } - - /// - /// Gets or sets the property name that will be animated on the - /// - public string PropertyName { get; set; } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Enums/Axis.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/Axis.cs new file mode 100644 index 00000000000..2a9410741ae --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/Axis.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// Indicates an axis in the 3D space. + /// + public enum Axis + { + /// + /// The X axis (horizontal). + /// + X, + + /// + /// The Y axis (vertical). + /// + Y, + + /// + /// The Z axis (depth). + /// + /// + /// This axis might only be available in certain scenarios, such as when working with composition APIs. + /// + Z + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Enums/EasingType.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/EasingType.cs new file mode 100644 index 00000000000..ace6b52e23b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/EasingType.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// Indicates how the animation interpolates between keyframes. + /// + public enum EasingType + { + /// + /// The default easing type, which is specified in . + /// Animations using this easing type follow the guidelines mentioned in the "Timing and easing" section of the docs. + /// For more info, see: . + /// + Default, + + /// + /// A linear acceleration and deceleration. + /// + Linear, + + /// + /// An acceleration or deceleration using the formula f(t) = t3. + /// + Cubic, + + /// + /// An animation that rectracts its motion slightly before it begins to animate in the path indicated. + /// + Back, + + /// + /// A bouncing animation. + /// + Bounce, + + /// + /// An animation that resembles a spring oscillating back and forth until it comes to rest. + /// + Elastic, + + /// + /// An animation that accelerates or decelerates using a circular function. + /// + Circle, + + /// + /// An animation that accelerates or decelerates using the formula f(t) = t^2. + /// + Quadratic, + + /// + /// An animation that accelerates or decelerates using the formula f(t) = t^4. + /// + Quartic, + + /// + /// An animation that accelerates or decelerates using the formula f(t) = t^5. + /// + Quintic, + + /// + /// An animation that accelerates or decelerates using a sine formula. + /// + Sine + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Enums/FrameworkLayer.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/FrameworkLayer.cs new file mode 100644 index 00000000000..e43462fdd5d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/FrameworkLayer.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An that indicates the framework layer to target in a specific animation. + /// + public enum FrameworkLayer + { + /// + /// Indicates the APIs. + /// + Composition, + + /// + /// Indicates the APIs. + /// + Xaml + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Enums/Side.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/Side.cs new file mode 100644 index 00000000000..856909a8e93 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/Side.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// Indicates a side to animate in the bounds of a given element. + /// + public enum Side + { + /// + /// Maps the top side of the target bounds. + /// + Top, + + /// + /// Maps the bottom side of the target bounds. + /// + Bottom, + + /// + /// Maps the left side of the target bounds. + /// + Left, + + /// + /// Maps the right side of the target bounds. + /// + Right + } +} diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/Enums/VisualProperty.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/VisualProperty.cs similarity index 93% rename from Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/Enums/VisualProperty.cs rename to Microsoft.Toolkit.Uwp.UI.Animations/Enums/VisualProperty.cs index 2ec4967ce02..539cf2a313c 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/Enums/VisualProperty.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Enums/VisualProperty.cs @@ -4,7 +4,7 @@ using Windows.UI.Composition; -namespace Microsoft.Toolkit.Uwp.UI.Extensions +namespace Microsoft.Toolkit.Uwp.UI.Animations { /// /// Indicates a property of a object. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Explicit.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Explicit.cs new file mode 100644 index 00000000000..022d8490178 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Explicit.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// Attached properties to support explicitly triggered animations for instances. + /// + public static class Explicit + { + /// + /// The attached "Animations" property. + /// + public static readonly DependencyProperty AnimationsProperty = DependencyProperty.RegisterAttached( + "Animations", + typeof(AnimationDictionary), + typeof(Explicit), + new PropertyMetadata(null, OnAnimationsPropertyChanged)); + + /// + /// Gets the value of the property. + /// + /// The to get the value for. + /// The retrieved item. + public static AnimationDictionary GetAnimations(UIElement element) + { + if (element.GetValue(AnimationsProperty) is AnimationDictionary collection) + { + return collection; + } + + collection = new AnimationDictionary(); + + element.SetValue(AnimationsProperty, collection); + + return collection; + } + + /// + /// Sets the value of the property. + /// + /// The to set the value for. + /// The value to set. + public static void SetAnimations(UIElement element, AnimationDictionary value) + { + element.SetValue(AnimationsProperty, value); + } + + /// + /// Callback to keep the attached parent in sync for animations linked to the property. + /// + /// The target object the property was changed for. + /// The instance for the current event. + private static void OnAnimationsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not UIElement element) + { + return; + } + + if (e.OldValue is AnimationDictionary oldDictionary) + { + oldDictionary.Parent = null; + } + + if (e.NewValue is AnimationDictionary newDictionary) + { + newDictionary.Parent = element; + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Blur.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Blur.cs deleted file mode 100644 index 879e7a2819d..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Blur.cs +++ /dev/null @@ -1,76 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Toolkit.Uwp.UI.Animations.Effects; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media.Animation; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// These extension methods perform animation on UIElements - /// - public static partial class AnimationExtensions - { - /// - /// Gets the blur effect. - /// - /// - /// The blur effect. - /// - public static Blur BlurEffect { get; } = new Blur(); - - /// - /// Animates the Gaussian blur of the UIElement. - /// - /// The associated object. - /// The blur amount. - /// The duration in milliseconds. - /// The delay. (ignored if duration == 0) - /// The easing function - /// The easing mode - /// - /// An Animation Set. - /// - public static AnimationSet Blur( - this FrameworkElement associatedObject, - double value = 0d, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (associatedObject == null) - { - return null; - } - - var animationSet = new AnimationSet(associatedObject); - return animationSet.Blur(value, duration, delay, easingType, easingMode); - } - - /// - /// Animates the Gaussian blur of the UIElement. - /// - /// The animation set. - /// The blur amount. - /// The duration in milliseconds. - /// The delay. (ignored if duration == 0) - /// The easing function - /// The easing mode - /// - /// An Animation Set. - /// - public static AnimationSet Blur( - this AnimationSet animationSet, - double value = 0d, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - return BlurEffect.EffectAnimation(animationSet, value, duration, delay, easingType, easingMode); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Fade.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Fade.cs deleted file mode 100644 index 24e7a65e631..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Fade.cs +++ /dev/null @@ -1,110 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media.Animation; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// These extension methods perform animation on UIElements - /// - public static partial class AnimationExtensions - { - /// - /// Animates the opacity of the UIElement. - /// - /// The UI Element to change the opacity of. - /// The fade value, between 0 and 1. - /// The duration in milliseconds. - /// The delay. (ignored if duration == 0) - /// Used to describe how the animation interpolates between keyframes. - /// The easing mode to use to interpolate between keyframes. - /// - /// An AnimationSet. - /// - public static AnimationSet Fade( - this UIElement associatedObject, - float value = 0f, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (associatedObject == null) - { - return null; - } - - var animationSet = new AnimationSet(associatedObject); - return animationSet.Fade(value, duration, delay, easingType, easingMode); - } - - /// - /// Animates the opacity of the UIElement. - /// - /// The animation set. - /// The fade value, between 0 and 1. - /// The duration in milliseconds. - /// The delay. (ignored if duration == 0) - /// Used to describe how the animation interpolates between keyframes. - /// The EasingMode to use to interpolate between keyframes. - /// - /// An AnimationSet. - /// - public static AnimationSet Fade( - this AnimationSet animationSet, - float value = 0f, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (animationSet == null) - { - return null; - } - - if (!AnimationSet.UseComposition) - { - var animation = new DoubleAnimation - { - To = value, - Duration = TimeSpan.FromMilliseconds(duration), - BeginTime = TimeSpan.FromMilliseconds(delay), - EasingFunction = GetEasingFunction(easingType, easingMode) - }; - - animationSet.AddStoryboardAnimation("Opacity", animation); - } - else - { - if (duration <= 0) - { - animationSet.AddCompositionDirectPropertyChange("Opacity", value); - return animationSet; - } - - var visual = animationSet.Visual; - - var compositor = visual?.Compositor; - - if (compositor == null) - { - return null; - } - - var animation = compositor.CreateScalarKeyFrameAnimation(); - animation.Duration = TimeSpan.FromMilliseconds(duration); - animation.DelayTime = TimeSpan.FromMilliseconds(delay); - animation.InsertKeyFrame(1f, value, GetCompositionEasingFunction(easingType, compositor, easingMode)); - - animationSet.AddCompositionAnimation("Opacity", animation); - } - - return animationSet; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Light.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Light.cs deleted file mode 100644 index c897d4cb206..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Light.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Collections.Generic; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Effects; -using Microsoft.Toolkit.Uwp.Extensions; -using Windows.UI; -using Windows.UI.Composition; -using Windows.UI.Composition.Effects; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Hosting; -using Windows.UI.Xaml.Media.Animation; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Provides an extension which allows lighting. - /// - public static partial class AnimationExtensions - { - /// - /// Stores all the point lights along with the visuals that they are applied to. - /// This is to stop multiplication of point lights on a single visual. - /// - private static Dictionary pointLights = new Dictionary(); - - /// - /// Animates a point light and it's distance. - /// - /// The associated object. - /// The value. - /// The duration. - /// The delay. - /// The color of the spotlight. - /// The easing function - /// The easing mode - /// An animation set. - [Obsolete("The Light effect will be removed in a future major release. Please use XamlLight instead")] - public static AnimationSet Light( - this FrameworkElement associatedObject, - double distance = 0d, - double duration = 500d, - double delay = 0d, - Color? color = null, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (associatedObject == null) - { - return null; - } - - var animationSet = new AnimationSet(associatedObject); - return animationSet.Light(distance, duration, delay, color, easingType, easingMode); - } - - /// - /// Animates a point light and it's distance. - /// - /// The animation set. - /// The distance of the light. - /// The duration in milliseconds. - /// The delay. (ignored if duration == 0) - /// The color of the spotlight. - /// The easing function - /// The easing mode - /// - /// An Animation Set. - /// - [Obsolete("The Light effect will be removed in a future major release. Please use XamlLight instead")] - public static AnimationSet Light( - this AnimationSet animationSet, - double distance = 0d, - double duration = 500d, - double delay = 0d, - Color? color = null, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (animationSet == null) - { - return null; - } - - var visual = animationSet.Visual; - var associatedObject = animationSet.Element as FrameworkElement; - - if (associatedObject == null) - { - return animationSet; - } - - var compositor = visual?.Compositor; - if (compositor == null) - { - return null; - } - - var task = new AnimationTask(); - task.AnimationSet = animationSet; - - task.Task = visual.DispatcherQueue.EnqueueAsync( - () => - { - const string sceneName = "PointLightScene"; - PointLight pointLight; - CompositionDrawingSurface normalMap = null; - - if (!pointLights.ContainsKey(visual)) - { - SurfaceLoader.Initialize(compositor); - normalMap = SurfaceLoader.LoadText(string.Empty, new Windows.Foundation.Size(512, 512), new Graphics.Canvas.Text.CanvasTextFormat(), Colors.Transparent, Colors.Transparent); - } - - if (pointLights.ContainsKey(visual)) - { - pointLight = pointLights[visual]; - } - else - { - pointLight = compositor.CreatePointLight(); - - var normalBrush = compositor.CreateSurfaceBrush(normalMap); - normalBrush.Stretch = CompositionStretch.Fill; - - // check to see if the visual already has a point light applied. - var spriteVisual = ElementCompositionPreview.GetElementChildVisual(associatedObject) as SpriteVisual; - var normalsBrush = spriteVisual?.Brush as CompositionEffectBrush; - - if (normalsBrush == null || normalsBrush.Comment != sceneName) - { - var lightEffect = new CompositeEffect() - { - Mode = CanvasComposite.Add, - Sources = - { - new CompositionEffectSourceParameter("ImageSource"), - new SceneLightingEffect() - { - Name = sceneName, - AmbientAmount = 0, - DiffuseAmount = 0.5f, - SpecularAmount = 0, - NormalMapSource = new CompositionEffectSourceParameter("NormalMap"), - } - } - }; - - var effectFactory = compositor.CreateEffectFactory(lightEffect); - var brush = effectFactory.CreateBrush(); - brush.SetSourceParameter("NormalMap", normalBrush); - - var sprite = compositor.CreateSpriteVisual(); - sprite.Size = visual.Size; - sprite.Brush = brush; - sprite.Comment = sceneName; - - ElementCompositionPreview.SetElementChildVisual(task.AnimationSet.Element, sprite); - - pointLight.CoordinateSpace = visual; - pointLight.Targets.Add(visual); - } - } - - pointLight.Color = color ?? Colors.White; - var delayTime = task.Delay != null ? task.Delay.Value : TimeSpan.FromMilliseconds(delay); - var durationTime = task.Duration != null ? task.Duration.Value : TimeSpan.FromMilliseconds(duration); - - if (durationTime.TotalMilliseconds <= 0) - { - task.AnimationSet.AddEffectDirectPropertyChange(pointLight, (float)distance, nameof(pointLight.Offset)); - } - else - { - var diffuseAnimation = compositor.CreateVector3KeyFrameAnimation(); - diffuseAnimation.InsertKeyFrame(1f, new System.Numerics.Vector3(visual.Size.X / 2, visual.Size.Y / 2, (float)distance), GetCompositionEasingFunction(easingType, compositor, easingMode)); - diffuseAnimation.Duration = durationTime; - diffuseAnimation.DelayTime = delayTime; - - task.AnimationSet.AddCompositionEffectAnimation(pointLight, diffuseAnimation, nameof(pointLight.Offset)); - } - - pointLights[visual] = pointLight; - }); - - animationSet.AddAnimationThroughTask(task); - return animationSet; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Offset.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Offset.cs deleted file mode 100644 index a967fefbd7e..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Offset.cs +++ /dev/null @@ -1,122 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Numerics; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media.Animation; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// These extension methods perform animation on UIElements - /// - public static partial class AnimationExtensions - { - /// - /// Animates the offset of the UIElement. - /// - /// The specified UI Element. - /// The offset on the x axis. - /// The offset on the y axis. - /// The duration in milliseconds. - /// The delay in milliseconds. (ignored if duration == 0) - /// Used to describe how the animation interpolates between keyframes. - /// The EasingMode to use to interpolate between keyframes. - /// - /// An AnimationSet. - /// - public static AnimationSet Offset( - this UIElement associatedObject, - float offsetX = 0f, - float offsetY = 0f, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (associatedObject == null) - { - return null; - } - - var animationSet = new AnimationSet(associatedObject); - return animationSet.Offset(offsetX, offsetY, duration, delay, easingType, easingMode); - } - - /// - /// Animates the offset of the UIElement. - /// - /// The animation set. - /// The offset on the x axis. - /// The offset on the y axis. - /// The duration in milliseconds. - /// The delay in milliseconds. (ignored if duration == 0) - /// Used to describe how the animation interpolates between keyframes. - /// The EasingMode to use to interpolate between keyframes. - /// - /// An AnimationSet. - /// - public static AnimationSet Offset( - this AnimationSet animationSet, - float offsetX = 0f, - float offsetY = 0f, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (animationSet == null) - { - return null; - } - - if (!AnimationSet.UseComposition) - { - var element = animationSet.Element; - var transform = GetAttachedCompositeTransform(element); - - var animationX = new DoubleAnimation(); - var animationY = new DoubleAnimation(); - - animationX.To = offsetX; - animationY.To = offsetY; - - animationX.Duration = animationY.Duration = TimeSpan.FromMilliseconds(duration); - animationX.BeginTime = animationY.BeginTime = TimeSpan.FromMilliseconds(delay); - animationX.EasingFunction = animationY.EasingFunction = GetEasingFunction(easingType, easingMode); - - animationSet.AddStoryboardAnimation(GetAnimationPath(transform, element, "TranslateX"), animationX); - animationSet.AddStoryboardAnimation(GetAnimationPath(transform, element, "TranslateY"), animationY); - } - else - { - var visual = animationSet.Visual; - var offsetVector = new Vector3(offsetX, offsetY, 0); - - if (duration <= 0) - { - animationSet.AddCompositionDirectPropertyChange("Offset", offsetVector); - return animationSet; - } - - var compositor = visual?.Compositor; - - if (compositor == null) - { - return null; - } - - var animation = compositor.CreateVector3KeyFrameAnimation(); - animation.Duration = TimeSpan.FromMilliseconds(duration); - animation.DelayTime = TimeSpan.FromMilliseconds(delay); - animation.InsertKeyFrame(1f, offsetVector, GetCompositionEasingFunction(easingType, compositor, easingMode)); - - animationSet.AddCompositionAnimation("Offset", animation); - } - - return animationSet; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Rotate.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Rotate.cs deleted file mode 100644 index d288b07a453..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Rotate.cs +++ /dev/null @@ -1,126 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Numerics; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media.Animation; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// These extension methods perform animation on UIElements - /// - public static partial class AnimationExtensions - { - /// - /// Animates the rotation in degrees of the UIElement. - /// - /// The UI Element to rotate. - /// The value in degrees to rotate. - /// The center x in pixels. - /// The center y in pixels. - /// The duration in milliseconds. - /// The delay in milliseconds. (ignored if duration == 0) - /// Used to describe how the animation interpolates between keyframes. - /// EasingMode used to interpolate between keyframes. - /// - /// An AnimationSet. - /// - public static AnimationSet Rotate( - this UIElement associatedObject, - float value = 0f, - float centerX = 0f, - float centerY = 0f, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (associatedObject == null) - { - return null; - } - - var animationSet = new AnimationSet(associatedObject); - return animationSet.Rotate(value, centerX, centerY, duration, delay, easingType); - } - - /// - /// Animates the rotation in degrees of the UIElement. - /// - /// The animation set. - /// The value in degrees to rotate. - /// The center x in pixels. - /// The center y in pixels. - /// The duration in milliseconds. - /// The delay in milliseconds. (ignored if duration == 0) - /// Used to describe how the animation interpolates between keyframes. - /// The EasingMode to use to interpolate between keyframes. - /// - /// An AnimationSet. - /// - public static AnimationSet Rotate( - this AnimationSet animationSet, - float value = 0f, - float centerX = 0f, - float centerY = 0f, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (animationSet == null) - { - return null; - } - - if (!AnimationSet.UseComposition) - { - var element = animationSet.Element; - var transform = GetAttachedCompositeTransform(element); - - transform.CenterX = centerX; - transform.CenterY = centerY; - - var animation = new DoubleAnimation - { - To = value, - Duration = TimeSpan.FromMilliseconds(duration), - BeginTime = TimeSpan.FromMilliseconds(delay), - EasingFunction = GetEasingFunction(easingType, easingMode) - }; - - animationSet.AddStoryboardAnimation(GetAnimationPath(transform, element, "Rotation"), animation); - } - else - { - var visual = animationSet.Visual; - visual.CenterPoint = new Vector3(centerX, centerY, 0); - - if (duration <= 0) - { - animationSet.AddCompositionDirectPropertyChange("RotationAngleInDegrees", value); - return animationSet; - } - - var compositor = visual.Compositor; - - if (compositor == null) - { - return null; - } - - var animation = compositor.CreateScalarKeyFrameAnimation(); - animation.Duration = TimeSpan.FromMilliseconds(duration); - animation.DelayTime = TimeSpan.FromMilliseconds(delay); - animation.InsertKeyFrame(1f, value, GetCompositionEasingFunction(easingType, compositor, easingMode)); - - animationSet.AddCompositionAnimation("RotationAngleInDegrees", animation); - } - - return animationSet; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Saturation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Saturation.cs deleted file mode 100644 index 9a7169b3515..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Saturation.cs +++ /dev/null @@ -1,72 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Microsoft.Toolkit.Uwp.UI.Animations.Effects; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media.Animation; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// A partial for the AnimationExtension which includes saturation. - /// - public static partial class AnimationExtensions - { - /// - /// Gets the saturation effect. - /// - /// - /// The saturation effect. - /// - public static Saturation SaturationEffect { get; } = new Saturation(); - - /// - /// Saturates the FrameworkElement. - /// - /// The associated object. - /// The value, between 0 and 1. 0 is desaturated, 1 is saturated. - /// The duration in milliseconds. - /// The delay in milliseconds. - /// The - /// The - /// An animation set with saturation effects incorporated. - public static AnimationSet Saturation( - this FrameworkElement associatedObject, - double value = 0d, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (associatedObject == null) - { - return null; - } - - var animationSet = new AnimationSet(associatedObject); - return animationSet.Saturation(value, duration, delay, easingType, easingMode); - } - - /// - /// Saturates the visual within the animation set. - /// - /// The animation set. - /// The value. 0 is desaturated, 1 is saturated. - /// The duration in milliseconds. - /// The delay in milliseconds. - /// The - /// The - /// An animation set with saturation effects incorporated. - public static AnimationSet Saturation( - this AnimationSet animationSet, - double value = 0d, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - return SaturationEffect.EffectAnimation(animationSet, value, duration, delay, easingType, easingMode); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Scale.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Scale.cs deleted file mode 100644 index 5ee0d22feb2..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.Scale.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Numerics; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Media.Animation; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// These extension methods perform animation on UIElements - /// - public static partial class AnimationExtensions - { - /// - /// Animates the scale of the specified UIElement. - /// - /// The associated UIElement. - /// The scale on the x axis. - /// The scale on the y axis. - /// The center x in pixels. - /// The center y in pixels. - /// The duration in millisecond. - /// The delay in milliseconds. (ignored if duration == 0) - /// Used to describe how the animation interpolates between keyframes. - /// The EasingMode to use to interpolate between keyframes. - /// - /// An AnimationSet. - /// - public static AnimationSet Scale( - this UIElement associatedObject, - float scaleX = 1f, - float scaleY = 1f, - float centerX = 0f, - float centerY = 0f, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (associatedObject == null) - { - return null; - } - - var animationSet = new AnimationSet(associatedObject); - return animationSet.Scale(scaleX, scaleY, centerX, centerY, duration, delay, easingType, easingMode); - } - - /// - /// Animates the scale of the specified UIElement. - /// - /// The animationSet object. - /// The scale on the x axis. - /// The scale on the y axis. - /// The center x in pixels. - /// The center y in pixels. - /// The duration in milliseconds. - /// The delay in milliseconds. (ignored if duration == 0) - /// Used to describe how the animation interpolates between keyframes. - /// The EasingMode to use to interpolate between keyframes. - /// - /// An AnimationSet. - /// - public static AnimationSet Scale( - this AnimationSet animationSet, - float scaleX = 1f, - float scaleY = 1f, - float centerX = 0f, - float centerY = 0f, - double duration = 500d, - double delay = 0d, - EasingType easingType = EasingType.Default, - EasingMode easingMode = EasingMode.EaseOut) - { - if (animationSet == null) - { - return null; - } - - if (!AnimationSet.UseComposition) - { - var element = animationSet.Element; - var transform = GetAttachedCompositeTransform(element); - - transform.CenterX = centerX; - transform.CenterY = centerY; - - var animationX = new DoubleAnimation(); - var animationY = new DoubleAnimation(); - - animationX.To = scaleX; - animationY.To = scaleY; - - animationX.Duration = animationY.Duration = TimeSpan.FromMilliseconds(duration); - animationX.BeginTime = animationY.BeginTime = TimeSpan.FromMilliseconds(delay); - animationX.EasingFunction = animationY.EasingFunction = GetEasingFunction(easingType, easingMode); - - animationSet.AddStoryboardAnimation(GetAnimationPath(transform, element, "ScaleX"), animationX); - animationSet.AddStoryboardAnimation(GetAnimationPath(transform, element, "ScaleY"), animationY); - } - else - { - var visual = animationSet.Visual; - visual.CenterPoint = new Vector3(centerX, centerY, 0); - var scaleVector = new Vector3(scaleX, scaleY, 1.0f); - - if (duration <= 0) - { - animationSet.AddCompositionDirectPropertyChange("Scale", scaleVector); - return animationSet; - } - - var compositor = visual.Compositor; - - if (compositor == null) - { - return null; - } - - var animation = compositor.CreateVector3KeyFrameAnimation(); - animation.Duration = TimeSpan.FromMilliseconds(duration); - animation.DelayTime = TimeSpan.FromMilliseconds(delay); - animation.InsertKeyFrame(1f, scaleVector, GetCompositionEasingFunction(easingType, compositor, easingMode)); - - animationSet.AddCompositionAnimation("Scale", animation); - } - - return animationSet; - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.cs index 7692b9a3d0e..e40c9033170 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationExtensions.cs @@ -1,11 +1,12 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; +using System.Diagnostics.Contracts; using System.Numerics; -using System.Threading.Tasks; +using Microsoft.Toolkit.Diagnostics; using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; @@ -14,269 +15,290 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations { /// - /// These extension methods perform animation on UIElements + /// Common properties related to extensions. /// - public static partial class AnimationExtensions + public static class AnimationExtensions { -#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly -#pragma warning disable SA1009 // Closing parenthesis must be spaced correctly - /// - /// A cached dictionary mapping easings to bézier control points - /// - private static readonly Dictionary<(string, EasingMode), (Vector2, Vector2)> _compositionEasingFunctions = new Dictionary<(string, EasingMode), (Vector2, Vector2)>(); -#pragma warning restore SA1009 // Closing parenthesis must be spaced correctly -#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly - /// - /// Gets or sets the default EasingType used for storyboard animations + /// Gets the default delay of animations. /// - public static EasingType DefaultEasingType { get; set; } = EasingType.Cubic; + public static TimeSpan DefaultDelay => default; /// - /// Begins a Storyboard animation and returns a task that completes when the - /// animation is complete + /// Gets the default duration of animations. /// - /// The storyboard to be started - /// Task that completes when the animation is complete - public static Task BeginAsync(this Storyboard storyboard) - { - var taskSource = new TaskCompletionSource(); - EventHandler completed = null; - completed += (s, e) => - { - storyboard.Completed -= completed; - taskSource.SetResult(null); - }; - - storyboard.Completed += completed; - storyboard.Begin(); - - return taskSource.Task; - } + public static TimeSpan DefaultDuration => TimeSpan.FromMilliseconds(400); /// - /// Gets the EasingFunction from an EasingType and optional EasingMode to be used with Storyboard animations + /// The default value used for animations. /// - /// The EasingType used to determine the EasingFunction - /// The EasingMode used to determine the EasingFunction. Defaults to - /// Return the appropriate EasingFuntion or null if the EasingType is Linear - public static EasingFunctionBase GetEasingFunction(EasingType easingType, EasingMode easingMode = EasingMode.EaseOut) - { - if (easingType == EasingType.Default) - { - easingType = DefaultEasingType; - } - - switch (easingType) - { - case EasingType.Linear: - return null; - case EasingType.Cubic: - return new CubicEase { EasingMode = easingMode }; - case EasingType.Back: - return new BackEase { EasingMode = easingMode }; - case EasingType.Bounce: - return new BounceEase { EasingMode = easingMode }; - case EasingType.Elastic: - return new ElasticEase { EasingMode = easingMode }; - case EasingType.Circle: - return new CircleEase { EasingMode = easingMode }; - case EasingType.Quadratic: - return new QuadraticEase { EasingMode = easingMode }; - case EasingType.Quartic: - return new QuarticEase { EasingMode = easingMode }; - case EasingType.Quintic: - return new QuinticEase { EasingMode = easingMode }; - case EasingType.Sine: - return new SineEase { EasingMode = easingMode }; - default: - throw new NotSupportedException($"{easingType.ToString()} EasingType is not currently supported"); - } - } + public const EasingType DefaultEasingType = EasingType.Default; /// - /// Generates an to be used with Composition animations + /// The default value used for animations. /// - /// The used to generate the CompositionEasingFunction - /// The - /// The used to generate the CompositionEasingFunction. Defaults to - /// - public static CompositionEasingFunction GetCompositionEasingFunction(EasingType easingType, Compositor compositor, EasingMode easingMode = EasingMode.EaseOut) - { - if (DesignTimeHelpers.IsRunningInLegacyDesignerMode) - { - return compositor.CreateLinearEasingFunction(); - } - - return GenerateCompositionEasingFunctionFromEasingType(easingType, compositor, easingMode); - } + public const EasingMode DefaultEasingMode = EasingMode.EaseInOut; /// - /// Creates the cache of easing functions if the cache does not already exist. + /// The reusable mapping of control points for easing curves for combinations of and values. /// - private static void EnsureEasingsCached() - { - if (_compositionEasingFunctions.Count > 0) - { - return; - } - - // We don't cache actual composition easing functions here as they can be disposed - // and we don't want to deal with caching a disposed easing function - void Add(EasingType type, EasingMode mode, Vector2 p1, Vector2 p2) - { - // In order to generate a usable hash code for our ValueTuple without collisions - // we can't use enum values for both type & mode, so we have to string one of them. -#pragma warning disable SA1008 // Opening parenthesis must be spaced correctly - _compositionEasingFunctions[(type.ToString(), mode)] = (p1, p2); -#pragma warning restore SA1008 // Opening parenthesis must be spaced correctly - } - - Add(EasingType.Cubic, EasingMode.EaseOut, new Vector2(0.215f, 0.61f), new Vector2(0.355f, 1f)); - Add(EasingType.Cubic, EasingMode.EaseIn, new Vector2(0.55f, 0.055f), new Vector2(0.675f, 0.19f)); - Add(EasingType.Cubic, EasingMode.EaseInOut, new Vector2(0.645f, 0.045f), new Vector2(0.355f, 1f)); - - Add(EasingType.Back, EasingMode.EaseOut, new Vector2(0.175f, 0.885f), new Vector2(0.32f, 1.275f)); - Add(EasingType.Back, EasingMode.EaseIn, new Vector2(0.6f, -0.28f), new Vector2(0.735f, 0.045f)); - Add(EasingType.Back, EasingMode.EaseInOut, new Vector2(0.68f, -0.55f), new Vector2(0.265f, 1.55f)); - - Add(EasingType.Bounce, EasingMode.EaseOut, new Vector2(0.58f, 1.93f), new Vector2(.08f, .36f)); - Add(EasingType.Bounce, EasingMode.EaseIn, new Vector2(0.93f, 0.7f), new Vector2(0.4f, -0.93f)); - Add(EasingType.Bounce, EasingMode.EaseInOut, new Vector2(0.65f, -0.85f), new Vector2(0.35f, 1.85f)); - - Add(EasingType.Elastic, EasingMode.EaseOut, new Vector2(0.37f, 2.68f), new Vector2(0f, 0.22f)); - Add(EasingType.Elastic, EasingMode.EaseIn, new Vector2(1, .78f), new Vector2(.63f, -1.68f)); - Add(EasingType.Elastic, EasingMode.EaseInOut, new Vector2(0.9f, -1.2f), new Vector2(0.1f, 2.2f)); - - Add(EasingType.Circle, EasingMode.EaseOut, new Vector2(0.075f, 0.82f), new Vector2(0.165f, 1f)); - Add(EasingType.Circle, EasingMode.EaseIn, new Vector2(0.6f, 0.04f), new Vector2(0.98f, 0.335f)); - Add(EasingType.Circle, EasingMode.EaseInOut, new Vector2(0.785f, 0.135f), new Vector2(0.15f, 0.86f)); - - Add(EasingType.Quadratic, EasingMode.EaseOut, new Vector2(0.25f, 0.46f), new Vector2(0.45f, 0.94f)); - Add(EasingType.Quadratic, EasingMode.EaseIn, new Vector2(0.55f, 0.085f), new Vector2(0.68f, 0.53f)); - Add(EasingType.Quadratic, EasingMode.EaseInOut, new Vector2(0.445f, 0.03f), new Vector2(0.515f, 0.955f)); - - Add(EasingType.Quartic, EasingMode.EaseOut, new Vector2(0.165f, 0.84f), new Vector2(0.44f, 1f)); - Add(EasingType.Quartic, EasingMode.EaseIn, new Vector2(0.895f, 0.03f), new Vector2(0.685f, 0.22f)); - Add(EasingType.Quartic, EasingMode.EaseInOut, new Vector2(0.77f, 0.0f), new Vector2(0.175f, 1.0f)); - - Add(EasingType.Quintic, EasingMode.EaseOut, new Vector2(0.23f, 1f), new Vector2(0.32f, 1f)); - Add(EasingType.Quintic, EasingMode.EaseIn, new Vector2(0.755f, 0.05f), new Vector2(0.855f, 0.06f)); - Add(EasingType.Quintic, EasingMode.EaseInOut, new Vector2(0.86f, 0.0f), new Vector2(0.07f, 1.0f)); - - Add(EasingType.Sine, EasingMode.EaseOut, new Vector2(0.39f, 0.575f), new Vector2(0.565f, 1f)); - Add(EasingType.Sine, EasingMode.EaseIn, new Vector2(0.47f, 0.0f), new Vector2(0.745f, 0.715f)); - Add(EasingType.Sine, EasingMode.EaseInOut, new Vector2(0.445f, 0.05f), new Vector2(0.55f, 0.95f)); - } - - private static CompositionEasingFunction GenerateCompositionEasingFunctionFromEasingType(EasingType easingType, Compositor compositor, EasingMode easingMode = EasingMode.EaseOut) + internal static readonly Dictionary<(EasingType Type, EasingMode Mode), (Vector2 A, Vector2 B)> EasingMaps = new() { - if (easingType == EasingType.Default) - { - easingType = DefaultEasingType; - } - - if (easingType == EasingType.Linear) - { - return compositor.CreateLinearEasingFunction(); - } + // The default/inout combination is missing, as in this case we just skip creating + // an easing function entirely, and rely on the composition APIs using the implicit + // easing automatically. This is a bit more efficient, and results in the same + // visual behavior anyway, as that's the standard combination for animations. + [(EasingType.Default, EasingMode.EaseOut)] = (new(0.1f, 0.9f), new(0.2f, 1.0f)), + [(EasingType.Default, EasingMode.EaseIn)] = (new(0.7f, 0.0f), new(1.0f, 0.5f)), + + [(EasingType.Cubic, EasingMode.EaseOut)] = (new(0.215f, 0.61f), new(0.355f, 1f)), + [(EasingType.Cubic, EasingMode.EaseIn)] = (new(0.55f, 0.055f), new(0.675f, 0.19f)), + [(EasingType.Cubic, EasingMode.EaseInOut)] = (new(0.645f, 0.045f), new(0.355f, 1f)), + + [(EasingType.Back, EasingMode.EaseOut)] = (new(0.175f, 0.885f), new(0.32f, 1.275f)), + [(EasingType.Back, EasingMode.EaseIn)] = (new(0.6f, -0.28f), new(0.735f, 0.045f)), + [(EasingType.Back, EasingMode.EaseInOut)] = (new(0.68f, -0.55f), new(0.265f, 1.55f)), + + [(EasingType.Bounce, EasingMode.EaseOut)] = (new(0.58f, 1.93f), new(.08f, .36f)), + [(EasingType.Bounce, EasingMode.EaseIn)] = (new(0.93f, 0.7f), new(0.4f, -0.93f)), + [(EasingType.Bounce, EasingMode.EaseInOut)] = (new(0.65f, -0.85f), new(0.35f, 1.85f)), + + [(EasingType.Elastic, EasingMode.EaseOut)] = (new(0.37f, 2.68f), new(0f, 0.22f)), + [(EasingType.Elastic, EasingMode.EaseIn)] = (new(1, .78f), new(.63f, -1.68f)), + [(EasingType.Elastic, EasingMode.EaseInOut)] = (new(0.9f, -1.2f), new(0.1f, 2.2f)), + + [(EasingType.Circle, EasingMode.EaseOut)] = (new(0.075f, 0.82f), new(0.165f, 1f)), + [(EasingType.Circle, EasingMode.EaseIn)] = (new(0.6f, 0.04f), new(0.98f, 0.335f)), + [(EasingType.Circle, EasingMode.EaseInOut)] = (new(0.785f, 0.135f), new(0.15f, 0.86f)), + + [(EasingType.Quadratic, EasingMode.EaseOut)] = (new(0.25f, 0.46f), new(0.45f, 0.94f)), + [(EasingType.Quadratic, EasingMode.EaseIn)] = (new(0.55f, 0.085f), new(0.68f, 0.53f)), + [(EasingType.Quadratic, EasingMode.EaseInOut)] = (new(0.445f, 0.03f), new(0.515f, 0.955f)), + + [(EasingType.Quartic, EasingMode.EaseOut)] = (new(0.165f, 0.84f), new(0.44f, 1f)), + [(EasingType.Quartic, EasingMode.EaseIn)] = (new(0.895f, 0.03f), new(0.685f, 0.22f)), + [(EasingType.Quartic, EasingMode.EaseInOut)] = (new(0.77f, 0.0f), new(0.175f, 1.0f)), + + [(EasingType.Quintic, EasingMode.EaseOut)] = (new(0.23f, 1f), new(0.32f, 1f)), + [(EasingType.Quintic, EasingMode.EaseIn)] = (new(0.755f, 0.05f), new(0.855f, 0.06f)), + [(EasingType.Quintic, EasingMode.EaseInOut)] = (new(0.86f, 0.0f), new(0.07f, 1.0f)), + + [(EasingType.Sine, EasingMode.EaseOut)] = (new(0.39f, 0.575f), new(0.565f, 1f)), + [(EasingType.Sine, EasingMode.EaseIn)] = (new(0.47f, 0.0f), new(0.745f, 0.715f)), + [(EasingType.Sine, EasingMode.EaseInOut)] = (new(0.445f, 0.05f), new(0.55f, 0.95f)) + }; - // Pay-per-play caching of easing functions - EnsureEasingsCached(); - - if (_compositionEasingFunctions.TryGetValue((easingType.ToString(), easingMode), out (Vector2, Vector2) points)) - { - return compositor.CreateCubicBezierEasingFunction(points.Item1, points.Item2); - } - - throw new NotSupportedException($"{easingType.ToString()} EasingType and {easingMode.ToString()} EasingMode combination is not currently supported"); - } - - private static string GetAnimationPath(CompositeTransform transform, UIElement element, string property) + /// + /// A static container for animatable properties. Composite properties in + /// the nested classes are manually stored as constants to avoid having + /// to perform string interpolation at runtime and allocating memory. + /// + internal static class Properties { - if (element.RenderTransform == transform) - { - return $"(UIElement.RenderTransform).(CompositeTransform.{property})"; - } - - var group = element.RenderTransform as TransformGroup; - - if (group == null) - { - return string.Empty; - } - - for (var index = 0; index < group.Children.Count; index++) + /// + /// Animatable properties for the composition layer. + /// + public static class Composition { - if (group.Children[index] == transform) + /// + /// Gets the path for a axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string AnchorPoint(Axis axis) => axis switch { - return $"(UIElement.RenderTransform).(TransformGroup.Children)[{index}].(CompositeTransform.{property})"; - } - } - - return string.Empty; - } - - private static CompositeTransform GetAttachedCompositeTransform(UIElement element) - { - // We need to use an index to keep track of our CompositeTransform as animation engine - // recreates new transform objects when animating properties - - // Already attached? - var compositeTransformIndex = AnimationTools.GetAnimationCompositeTransformIndex(element); - - if (compositeTransformIndex > -2) - { - if (compositeTransformIndex == -1 && element.RenderTransform is CompositeTransform) + Axis.X => "AnchorPoint.X", + Axis.Y => "AnchorPoint.Y", + Axis.Z => "AnchorPoint.Z", + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; + + /// + /// Gets the path for the translation property. + /// + /// The animation property. + [Pure] + public static string Translation() => "Translation"; + + /// + /// Gets the path for a translation axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string Translation(Axis axis) => axis switch { - return (CompositeTransform)element.RenderTransform; - } - - var group = element.RenderTransform as TransformGroup; - - if (group?.Children.Count > compositeTransformIndex && group.Children[compositeTransformIndex] is CompositeTransform) + Axis.X => "Translation.X", + Axis.Y => "Translation.Y", + Axis.Z => "Translation.Z", + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; + + /// + /// Gets the "Translation.XY" constant. + /// + /// The animation property. + [Pure] + public static string TranslationXY() => "Translation.XY"; + + /// + /// Gets the path for a axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string Offset(Axis axis) => axis switch { - return (CompositeTransform)group.Children[compositeTransformIndex]; - } + Axis.X => "Offset.X", + Axis.Y => "Offset.Y", + Axis.Z => "Offset.Z", + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; + + /// + /// Gets the "Offset.XY" constant. + /// + /// The animation property. + [Pure] + public static string OffsetXY() => "Offset.XY"; + + /// + /// Gets the path for a axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string Scale(Axis axis) => axis switch + { + Axis.X => "Scale.X", + Axis.Y => "Scale.Y", + Axis.Z => "Scale.Z", + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; + + /// + /// Gets the "Scale.XY" constant. + /// + /// The animation property. + [Pure] + public static string ScaleXY() => "Scale.XY"; + + /// + /// Gets the path for a axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string CenterPoint(Axis axis) => axis switch + { + Axis.X => "CenterPoint.X", + Axis.Y => "CenterPoint.Y", + Axis.Z => "CenterPoint.Z", + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; + + /// + /// Gets the "CenterPoint.XY" constant. + /// + /// The animation property. + [Pure] + public static string CenterPointXY() => "CenterPoint.XY"; + + /// + /// Gets the path for an side. + /// + /// The target side. + /// The animation property. + [Pure] + public static string Clip(Side side) => side switch + { + Side.Top => nameof(InsetClip.TopInset), + Side.Bottom => nameof(InsetClip.BottomInset), + Side.Right => nameof(InsetClip.RightInset), + Side.Left => nameof(InsetClip.LeftInset), + _ => ThrowHelper.ThrowArgumentException("Invalid clip side") + }; + + /// + /// Gets the "Size" constant. + /// + /// The animation property. + [Pure] + public static string Size() => "Size"; + + /// + /// Gets the path for a axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string Size(Axis axis) => axis switch + { + Axis.X => "Size.X", + Axis.Y => "Size.Y", + Axis.Z => "Size.Z", + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; } - // Let's create a new CompositeTransform - var result = new CompositeTransform(); - - var currentTransform = element.RenderTransform; - - if (currentTransform != null) + /// + /// Animatable properties for the XAML layer. + /// + public static class Xaml { - // We found a RenderTransform - - // Is it a TransformGroup? - var currentTransformGroup = currentTransform as TransformGroup; - - if (currentTransformGroup != null) + /// + /// Gets the path for a translation axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string Translation(Axis axis) => axis switch { - currentTransformGroup.Children.Add(result); - - AnimationTools.SetAnimationCompositeTransformIndex(element, currentTransformGroup.Children.Count - 1); - } - else + Axis.X => nameof(CompositeTransform.TranslateX), + Axis.Y => nameof(CompositeTransform.TranslateY), + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; + + /// + /// Gets the path for a scale axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string Scale(Axis axis) => axis switch { - // Let's create our own TransformGroup - var group = new TransformGroup(); - group.Children.Add(currentTransform); - group.Children.Add(result); - element.RenderTransform = group; - - AnimationTools.SetAnimationCompositeTransformIndex(element, 1); - } - } - else - { - element.RenderTransform = result; - - AnimationTools.SetAnimationCompositeTransformIndex(element, -1); + Axis.X => nameof(CompositeTransform.ScaleX), + Axis.Y => nameof(CompositeTransform.ScaleY), + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; + + /// + /// Gets the path for a center point axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string CenterPoint(Axis axis) => axis switch + { + Axis.X => nameof(CompositeTransform.CenterX), + Axis.Y => nameof(CompositeTransform.CenterY), + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; + + /// + /// Gets the path for a size axis. + /// + /// The target axis. + /// The animation property. + [Pure] + public static string Size(Axis axis) => axis switch + { + Axis.X => nameof(FrameworkElement.Width), + Axis.Y => nameof(FrameworkElement.Height), + _ => ThrowHelper.ThrowArgumentException("Invalid axis") + }; } - - return result; } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationTools.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationTools.cs deleted file mode 100644 index 63417c900ff..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/AnimationTools.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using Windows.UI.Xaml; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Internal tool to link composite transforms to elements - /// - internal partial class AnimationTools : DependencyObject - { - /// - /// Attached property used to link composite transform with UIElement - /// - public static readonly DependencyProperty AnimationCompositeTransformIndexProperty = DependencyProperty.RegisterAttached( - "AnimationCompositeTransformIndex", - typeof(int), - typeof(AnimationTools), - new PropertyMetadata(-2)); - - /// - /// Attach a composite transform index to an UIElement. - /// - /// UIElement to use - /// Composite transform index - public static void SetAnimationCompositeTransformIndex(UIElement element, int value) - { - element.SetValue(AnimationCompositeTransformIndexProperty, value); - } - - /// - /// Get the composite transform index attached to an UIElement. - /// - /// UIElement to use - /// Composite transform index. - public static int GetAnimationCompositeTransformIndex(UIElement element) - { - return (int)element.GetValue(AnimationCompositeTransformIndexProperty); - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/CompositionObjectExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/CompositionObjectExtensions.cs new file mode 100644 index 00000000000..bba2529c210 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/CompositionObjectExtensions.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Diagnostics.Contracts; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An extension for the type. + /// + public static class CompositionObjectExtensions + { + /// + /// Starts a given on a target . + /// + /// The target instance to animate. + /// The instance to run. + /// This method requires to have its property set. + [Pure] + public static void StartAnimation(this CompositionObject compositionObject, CompositionAnimation animation) + { + compositionObject.StartAnimation(animation.Target, animation); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/CompositorExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/CompositorExtensions.cs new file mode 100644 index 00000000000..11e19944804 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/CompositorExtensions.cs @@ -0,0 +1,483 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Diagnostics.Contracts; +using System.Numerics; +using Windows.UI; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An extension for the type. + /// + public static class CompositorExtensions + { + /// + /// Creates the appropriate from the given easing type and mode. + /// + /// The source used to create the easing function. + /// The target easing function to use. + /// The target easing mode to use. + /// + /// A instance with the specified easing, or + /// when the input parameters refer to the built-in easing, which means no instance is needed. + /// + [Pure] + public static CompositionEasingFunction? TryCreateEasingFunction(this Compositor compositor, EasingType easingType = DefaultEasingType, EasingMode easingMode = DefaultEasingMode) + { + if (easingType == DefaultEasingType && easingMode == DefaultEasingMode) + { + return null; + } + + if (easingType == EasingType.Linear) + { + return compositor.CreateLinearEasingFunction(); + } + + var (a, b) = EasingMaps[(easingType, easingMode)]; + + return compositor.CreateCubicBezierEasingFunction(a, b); + } + + /// + /// Creates a from the input control points. + /// + /// The source used to create the easing function. + /// The X coordinate of the first control point. + /// The Y coordinate of the first control point. + /// The X coordinate of the second control point. + /// The Y coordinate of the second control point. + /// A instance with the given control points. + [Pure] + public static CubicBezierEasingFunction CreateCubicBezierEasingFunction(this Compositor compositor, float x1, float y1, float x2, float y2) + { + return compositor.CreateCubicBezierEasingFunction(new(x1, y1), new(x2, y2)); + } + + /// + /// Creates a instance with the given parameters to on a target element. + /// + /// The current instance used to create the animation. + /// The optional target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The delay behavior to use for the animation. + /// The direction to use when playing the animation. + /// The iteration behavior to use for the animation. + /// The iteration count to use for the animation. + /// A instance with the specified parameters. + [Pure] + public static BooleanKeyFrameAnimation CreateBooleanKeyFrameAnimation( + this Compositor compositor, + string? target, + bool to, + bool? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + AnimationDelayBehavior delayBehavior = AnimationDelayBehavior.SetInitialValueBeforeDelay, + AnimationDirection direction = AnimationDirection.Normal, + AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count, + int iterationCount = 1) + { + BooleanKeyFrameAnimation animation = compositor.CreateBooleanKeyFrameAnimation(); + + animation.Duration = duration ?? DefaultDuration; + + if (delay.HasValue) + { + animation.DelayTime = delay.Value; + animation.DelayBehavior = delayBehavior; + } + + animation.InsertKeyFrame(1, to); + + if (from.HasValue) + { + animation.InsertKeyFrame(0, from.Value); + } + + animation.Target = target; + animation.Direction = direction; + animation.IterationBehavior = iterationBehavior; + animation.IterationCount = iterationCount; + + return animation; + } + + /// + /// Creates a instance with the given parameters to on a target element. + /// + /// The current instance used to create the animation. + /// The optional target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function for the animation. + /// The delay behavior to use for the animation. + /// The direction to use when playing the animation. + /// The iteration behavior to use for the animation. + /// The iteration count to use for the animation. + /// A instance with the specified parameters. + [Pure] + public static ScalarKeyFrameAnimation CreateScalarKeyFrameAnimation( + this Compositor compositor, + string? target, + float to, + float? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + CompositionEasingFunction? easing = null, + AnimationDelayBehavior delayBehavior = AnimationDelayBehavior.SetInitialValueBeforeDelay, + AnimationDirection direction = AnimationDirection.Normal, + AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count, + int iterationCount = 1) + { + ScalarKeyFrameAnimation animation = compositor.CreateScalarKeyFrameAnimation(); + + animation.Duration = duration ?? DefaultDuration; + + if (delay.HasValue) + { + animation.DelayTime = delay.Value; + animation.DelayBehavior = delayBehavior; + } + + if (easing is null) + { + animation.InsertKeyFrame(1, to); + } + else + { + animation.InsertKeyFrame(1, to, easing); + } + + if (from.HasValue) + { + animation.InsertKeyFrame(0, from.Value); + } + + animation.Target = target; + animation.Direction = direction; + animation.IterationBehavior = iterationBehavior; + animation.IterationCount = iterationCount; + + return animation; + } + + /// + /// Creates a instance with the given parameters to on a target element. + /// + /// The current instance used to create the animation. + /// The optional target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function for the animation. + /// The delay behavior to use for the animation. + /// The direction to use when playing the animation. + /// The iteration behavior to use for the animation. + /// The iteration count to use for the animation. + /// A instance with the specified parameters. + [Pure] + public static Vector2KeyFrameAnimation CreateVector2KeyFrameAnimation( + this Compositor compositor, + string? target, + Vector2 to, + Vector2? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + CompositionEasingFunction? easing = null, + AnimationDelayBehavior delayBehavior = AnimationDelayBehavior.SetInitialValueBeforeDelay, + AnimationDirection direction = AnimationDirection.Normal, + AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count, + int iterationCount = 1) + { + Vector2KeyFrameAnimation animation = compositor.CreateVector2KeyFrameAnimation(); + + animation.Duration = duration ?? DefaultDuration; + + if (delay.HasValue) + { + animation.DelayTime = delay.Value; + animation.DelayBehavior = delayBehavior; + } + + if (easing is null) + { + animation.InsertKeyFrame(1, to); + } + else + { + animation.InsertKeyFrame(1, to, easing); + } + + if (from.HasValue) + { + animation.InsertKeyFrame(0, from.Value); + } + + animation.Target = target; + animation.Direction = direction; + animation.IterationBehavior = iterationBehavior; + animation.IterationCount = iterationCount; + + return animation; + } + + /// + /// Creates a instance with the given parameters to on a target element. + /// + /// The current instance used to create the animation. + /// The optional target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function for the animation. + /// The delay behavior to use for the animation. + /// The direction to use when playing the animation. + /// The iteration behavior to use for the animation. + /// The iteration count to use for the animation. + /// A instance with the specified parameters. + [Pure] + public static Vector3KeyFrameAnimation CreateVector3KeyFrameAnimation( + this Compositor compositor, + string? target, + Vector3 to, + Vector3? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + CompositionEasingFunction? easing = null, + AnimationDelayBehavior delayBehavior = AnimationDelayBehavior.SetInitialValueBeforeDelay, + AnimationDirection direction = AnimationDirection.Normal, + AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count, + int iterationCount = 1) + { + Vector3KeyFrameAnimation animation = compositor.CreateVector3KeyFrameAnimation(); + + animation.Duration = duration ?? DefaultDuration; + + if (delay.HasValue) + { + animation.DelayTime = delay.Value; + animation.DelayBehavior = delayBehavior; + } + + if (easing is null) + { + animation.InsertKeyFrame(1, to); + } + else + { + animation.InsertKeyFrame(1, to, easing); + } + + if (from.HasValue) + { + animation.InsertKeyFrame(0, from.Value); + } + + animation.Target = target; + animation.Direction = direction; + animation.IterationBehavior = iterationBehavior; + animation.IterationCount = iterationCount; + + return animation; + } + + /// + /// Creates a instance with the given parameters to on a target element. + /// + /// The current instance used to create the animation. + /// The optional target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function for the animation. + /// The delay behavior to use for the animation. + /// The direction to use when playing the animation. + /// The iteration behavior to use for the animation. + /// The iteration count to use for the animation. + /// A instance with the specified parameters. + [Pure] + public static Vector4KeyFrameAnimation CreateVector4KeyFrameAnimation( + this Compositor compositor, + string? target, + Vector4 to, + Vector4? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + CompositionEasingFunction? easing = null, + AnimationDelayBehavior delayBehavior = AnimationDelayBehavior.SetInitialValueBeforeDelay, + AnimationDirection direction = AnimationDirection.Normal, + AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count, + int iterationCount = 1) + { + Vector4KeyFrameAnimation animation = compositor.CreateVector4KeyFrameAnimation(); + + animation.Duration = duration ?? DefaultDuration; + + if (delay.HasValue) + { + animation.DelayTime = delay.Value; + animation.DelayBehavior = delayBehavior; + } + + if (easing is null) + { + animation.InsertKeyFrame(1, to); + } + else + { + animation.InsertKeyFrame(1, to, easing); + } + + if (from.HasValue) + { + animation.InsertKeyFrame(0, from.Value); + } + + animation.Target = target; + animation.Direction = direction; + animation.IterationBehavior = iterationBehavior; + animation.IterationCount = iterationCount; + + return animation; + } + + /// + /// Creates a instance with the given parameters to on a target element. + /// + /// The current instance used to create the animation. + /// The optional target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function for the animation. + /// The delay behavior to use for the animation. + /// The direction to use when playing the animation. + /// The iteration behavior to use for the animation. + /// The iteration count to use for the animation. + /// A instance with the specified parameters. + [Pure] + public static ColorKeyFrameAnimation CreateColorKeyFrameAnimation( + this Compositor compositor, + string? target, + Color to, + Color? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + CompositionEasingFunction? easing = null, + AnimationDelayBehavior delayBehavior = AnimationDelayBehavior.SetInitialValueBeforeDelay, + AnimationDirection direction = AnimationDirection.Normal, + AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count, + int iterationCount = 1) + { + ColorKeyFrameAnimation animation = compositor.CreateColorKeyFrameAnimation(); + + animation.Duration = duration ?? DefaultDuration; + + if (delay.HasValue) + { + animation.DelayTime = delay.Value; + animation.DelayBehavior = delayBehavior; + } + + if (easing is null) + { + animation.InsertKeyFrame(1, to); + } + else + { + animation.InsertKeyFrame(1, to, easing); + } + + if (from.HasValue) + { + animation.InsertKeyFrame(0, from.Value); + } + + animation.Target = target; + animation.Direction = direction; + animation.IterationBehavior = iterationBehavior; + animation.IterationCount = iterationCount; + + return animation; + } + + /// + /// Creates a instance with the given parameters to on a target element. + /// + /// The current instance used to create the animation. + /// The optional target property to animate. + /// The final value for the animation. + /// The optional starting value for the animation. + /// The optional initial delay for the animation. + /// The optional animation duration. + /// The optional easing function for the animation. + /// The delay behavior to use for the animation. + /// The direction to use when playing the animation. + /// The iteration behavior to use for the animation. + /// The iteration count to use for the animation. + /// A instance with the specified parameters. + [Pure] + public static QuaternionKeyFrameAnimation CreateQuaternionKeyFrameAnimation( + this Compositor compositor, + string? target, + Quaternion to, + Quaternion? from = null, + TimeSpan? delay = null, + TimeSpan? duration = null, + CompositionEasingFunction? easing = null, + AnimationDelayBehavior delayBehavior = AnimationDelayBehavior.SetInitialValueBeforeDelay, + AnimationDirection direction = AnimationDirection.Normal, + AnimationIterationBehavior iterationBehavior = AnimationIterationBehavior.Count, + int iterationCount = 1) + { + QuaternionKeyFrameAnimation animation = compositor.CreateQuaternionKeyFrameAnimation(); + + animation.Duration = duration ?? DefaultDuration; + + if (delay.HasValue) + { + animation.DelayTime = delay.Value; + animation.DelayBehavior = delayBehavior; + } + + if (easing is null) + { + animation.InsertKeyFrame(1, to); + } + else + { + animation.InsertKeyFrame(1, to, easing); + } + + if (from.HasValue) + { + animation.InsertKeyFrame(0, from.Value); + } + + animation.Target = target; + animation.Direction = direction; + animation.IterationBehavior = iterationBehavior; + animation.IterationCount = iterationCount; + + return animation; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/DependencyObjectExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/DependencyObjectExtensions.cs new file mode 100644 index 00000000000..ecbd6be37d8 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/DependencyObjectExtensions.cs @@ -0,0 +1,163 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Diagnostics.Contracts; +using Windows.Foundation; +using Windows.UI; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; +using XamlColorAnimation = Windows.UI.Xaml.Media.Animation.ColorAnimation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An extension for the type. + /// + public static class DependencyObjectExtensions + { + /// + /// Prepares a with the given info. + /// + /// The target to animate. + /// The property to animate inside the target . + /// The final property value. + /// The optional initial property value. + /// The optional delay for the animation. + /// The duration of the . + /// The easing function to use inside the . + /// The repeat behavior for the animation (defaults to one iteration). + /// The behavior to use when the animation reaches the end of its schedule. + /// Indicates whether the animation plays in reverse after each forward iteration. + /// Indicates whether or not to apply this animation to elements that need the visual tree to be rearranged. + /// A instance with the specified parameters. + [Pure] + public static DoubleAnimation CreateDoubleAnimation( + this DependencyObject target, + string property, + double to, + double? from, + TimeSpan? delay, + TimeSpan duration, + EasingFunctionBase? easing = null, + RepeatBehavior? repeatBehavior = null, + FillBehavior fillBehavior = FillBehavior.HoldEnd, + bool autoReverse = false, + bool enableDependecyAnimations = false) + { + DoubleAnimation animation = new() + { + To = to, + From = from, + BeginTime = delay, + Duration = duration, + EasingFunction = easing, + RepeatBehavior = repeatBehavior ?? new RepeatBehavior(1), + FillBehavior = fillBehavior, + AutoReverse = autoReverse, + EnableDependentAnimation = enableDependecyAnimations + }; + + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, property); + + return animation; + } + + /// + /// Prepares a with the given info. + /// + /// The target to animate. + /// The property to animate inside the target . + /// The final property value. + /// The optional initial property value. + /// The optional delay for the animation. + /// The duration of the . + /// The easing function to use inside the . + /// The repeat behavior for the animation (defaults to one iteration). + /// The behavior to use when the animation reaches the end of its schedule. + /// Indicates whether the animation plays in reverse after each forward iteration. + /// Indicates whether or not to apply this animation to elements that need the visual tree to be rearranged. + /// A instance with the specified parameters. + [Pure] + public static PointAnimation CreatePointAnimation( + this DependencyObject target, + string property, + Point to, + Point? from, + TimeSpan? delay, + TimeSpan duration, + EasingFunctionBase? easing = null, + RepeatBehavior? repeatBehavior = null, + FillBehavior fillBehavior = FillBehavior.HoldEnd, + bool autoReverse = false, + bool enableDependecyAnimations = false) + { + PointAnimation animation = new() + { + To = to, + From = from, + BeginTime = delay, + Duration = duration, + EasingFunction = easing, + RepeatBehavior = repeatBehavior ?? new RepeatBehavior(1), + FillBehavior = fillBehavior, + AutoReverse = autoReverse, + EnableDependentAnimation = enableDependecyAnimations + }; + + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, property); + + return animation; + } + + /// + /// Prepares a with the given info. + /// + /// The target to animate. + /// The property to animate inside the target . + /// The final property value. + /// The optional initial property value. + /// The optional delay for the animation. + /// The duration of the . + /// The easing function to use inside the . + /// The repeat behavior for the animation (defaults to one iteration). + /// The behavior to use when the animation reaches the end of its schedule. + /// Indicates whether the animation plays in reverse after each forward iteration. + /// A instance with the specified parameters. + [Pure] + public static XamlColorAnimation CreateColorAnimation( + this DependencyObject target, + string property, + Color to, + Color? from, + TimeSpan? delay, + TimeSpan duration, + EasingFunctionBase? easing = null, + RepeatBehavior? repeatBehavior = null, + FillBehavior fillBehavior = FillBehavior.HoldEnd, + bool autoReverse = false) + { + XamlColorAnimation animation = new() + { + To = to, + From = from, + BeginTime = delay, + Duration = duration, + EasingFunction = easing, + RepeatBehavior = repeatBehavior ?? new RepeatBehavior(1), + FillBehavior = fillBehavior, + AutoReverse = autoReverse + }; + + Storyboard.SetTarget(animation, target); + Storyboard.SetTargetProperty(animation, property); + + return animation; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/EasingTypeExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/EasingTypeExtensions.cs new file mode 100644 index 00000000000..039f807f4f5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/EasingTypeExtensions.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Diagnostics.Contracts; +using Microsoft.Toolkit.Diagnostics; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An extension for the type. + /// + public static class EasingTypeExtensions + { + /// + /// Gets an instance corresponding to a given value. + /// + /// The desired easing function type. + /// The desired easing mode. + /// An instance corresponding to the input parameters. + [Pure] + public static EasingFunctionBase? ToEasingFunction(this EasingType easingType, EasingMode easingMode = DefaultEasingMode) + { + return easingType switch + { + EasingType.Linear => null, + + EasingType.Default when easingMode == EasingMode.EaseIn + => new ExponentialEase { Exponent = 4.5, EasingMode = EasingMode.EaseIn }, + EasingType.Default when easingMode == EasingMode.EaseOut + => new ExponentialEase { Exponent = 7, EasingMode = EasingMode.EaseOut }, + EasingType.Default when easingMode == EasingMode.EaseInOut + => new CircleEase { EasingMode = EasingMode.EaseInOut }, + + EasingType.Cubic => new CubicEase { EasingMode = easingMode }, + EasingType.Back => new BackEase { EasingMode = easingMode }, + EasingType.Bounce => new BounceEase { EasingMode = easingMode }, + EasingType.Elastic => new ElasticEase { EasingMode = easingMode }, + EasingType.Circle => new CircleEase { EasingMode = easingMode }, + EasingType.Quadratic => new QuadraticEase { EasingMode = easingMode }, + EasingType.Quartic => new QuarticEase { EasingMode = easingMode }, + EasingType.Quintic => new QuinticEase { EasingMode = easingMode }, + EasingType.Sine => new SineEase { EasingMode = easingMode }, + + _ => ThrowHelper.ThrowArgumentException("Invalid easing type") + }; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/ITimedKeyFrameAnimationBuilderExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/ITimedKeyFrameAnimationBuilderExtensions.cs new file mode 100644 index 00000000000..3884a6ab4dc --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/ITimedKeyFrameAnimationBuilderExtensions.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An extension for the type. + /// + public static class ITimedKeyFrameAnimationBuilderExtensions + { + /// + /// Adds a new timed keyframe to the builder in use. + /// + /// The type of values being set by the animation being constructed. + /// The target instance in use. + /// The timed progress for the keyframe (in milliseconds), relative to the start of the animation. + /// The value for the new keyframe to add. + /// The easing type to use to reach the new keyframe. + /// The easing mode to use to reach the new keyframe. + /// The same instance that the method was invoked upon. + public static ITimedKeyFrameAnimationBuilder KeyFrame( + this ITimedKeyFrameAnimationBuilder builder, + int progress, + T value, + EasingType easingType = DefaultEasingType, + EasingMode easingMode = DefaultEasingMode) + where T : unmanaged + { + return builder.KeyFrame(TimeSpan.FromMilliseconds(progress), value, easingType, easingMode); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.ExpressionAnimations.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/ScrollViewerExtensions.cs similarity index 94% rename from Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.ExpressionAnimations.cs rename to Microsoft.Toolkit.Uwp.UI.Animations/Extensions/ScrollViewerExtensions.cs index a7624a7f0cf..a11b6b27b11 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/ScrollViewerExtensions.ExpressionAnimations.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/ScrollViewerExtensions.cs @@ -2,14 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; using System.Numerics; +using Microsoft.Toolkit.Diagnostics; using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Hosting; -namespace Microsoft.Toolkit.Uwp.UI.Extensions +namespace Microsoft.Toolkit.Uwp.UI.Animations { /// /// Provides attached dependency properties and methods for the control. @@ -50,7 +50,6 @@ public static ExpressionAnimation StartExpressionAnimation( VisualProperty property = VisualProperty.Translation) { CompositionPropertySet scrollSet = ElementCompositionPreview.GetScrollViewerManipulationPropertySet(scroller); - ExpressionAnimation animation = scrollSet.Compositor.CreateExpressionAnimation($"{nameof(scroller)}.{nameof(UIElement.Translation)}.{sourceAxis}"); animation.SetReferenceParameter(nameof(scroller), scrollSet); @@ -66,7 +65,9 @@ public static ExpressionAnimation StartExpressionAnimation( case VisualProperty.Offset: visual.StartAnimation($"{nameof(Visual.Offset)}.{targetAxis}", animation); break; - default: throw new ArgumentException($"Invalid target property: {property}", nameof(property)); + default: + ThrowHelper.ThrowArgumentException("Invalid target property"); + break; } return animation; diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/StoryboardAnimations.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/StoryboardAnimations.cs new file mode 100644 index 00000000000..0a67e640ead --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Extensions/StoryboardAnimations.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System.Threading.Tasks; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An extension for the type. + /// + public static class StoryboardAnimations + { + /// + /// Starts an animation and returns a that reports when it completes. + /// + /// The target storyboard to start. + /// A that completes when completes. + public static Task BeginAsync(this Storyboard storyboard) + { + TaskCompletionSource taskCompletionSource = new TaskCompletionSource(); + + storyboard.Completed += (_, _) => taskCompletionSource.SetResult(null); + + storyboard.Begin(); + + return taskCompletionSource.Task; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Implicit.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Implicit.cs index a8a28187eba..93c799013e8 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Implicit.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Implicit.cs @@ -2,220 +2,227 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using Windows.UI.Composition; +using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Hosting; +#nullable enable + namespace Microsoft.Toolkit.Uwp.UI.Animations { /// - /// Attached Properties to enable Implicit Animations through XAML + /// Attached properties to support implicitly triggered animations for instances. /// - public class Implicit + public static class Implicit { /// - /// Identifies the Implicit.ShowAnimations XAML attached property + /// The attached "ShowAnimations" property. /// - public static readonly DependencyProperty ShowAnimationsProperty = - DependencyProperty.RegisterAttached("ShowAnimations", typeof(AnimationCollection), typeof(Implicit), new PropertyMetadata(null, ShowAnimationsChanged)); + public static readonly DependencyProperty ShowAnimationsProperty = DependencyProperty.RegisterAttached( + "ShowAnimations", + typeof(ImplicitAnimationSet), + typeof(Implicit), + new PropertyMetadata(null, OnShowAnimationsPropertyChanged)); /// - /// Identifies the Implicit.HideAnimations XAML attached property + /// The attached "HideAnimations" property. /// - public static readonly DependencyProperty HideAnimationsProperty = - DependencyProperty.RegisterAttached("HideAnimations", typeof(AnimationCollection), typeof(Implicit), new PropertyMetadata(null, HideAnimationsChanged)); + public static readonly DependencyProperty HideAnimationsProperty = DependencyProperty.RegisterAttached( + "HideAnimations", + typeof(ImplicitAnimationSet), + typeof(Implicit), + new PropertyMetadata(null, OnHideAnimationsPropertyChanged)); /// - /// Identifies the Implicit.Animations XAML attached property + /// The attached "Animations" property. /// - public static readonly DependencyProperty AnimationsProperty = - DependencyProperty.RegisterAttached("Animations", typeof(AnimationCollection), typeof(Implicit), new PropertyMetadata(null, AnimationsChanged)); + public static readonly DependencyProperty AnimationsProperty = DependencyProperty.RegisterAttached( + "Animations", + typeof(ImplicitAnimationSet), + typeof(Implicit), + new PropertyMetadata(null, OnAnimationsPropertyChanged)); /// - /// Gets the value of the Implicit.ShowAnimations XAML attached property. + /// Gets the value of the property. /// - /// The to get the value from - /// - public static AnimationCollection GetShowAnimations(DependencyObject obj) + /// The to get the value for. + /// The retrieved value. + public static ImplicitAnimationSet GetShowAnimations(UIElement element) { - var collection = (AnimationCollection)obj.GetValue(ShowAnimationsProperty); + var collection = (ImplicitAnimationSet)element.GetValue(ShowAnimationsProperty); - if (collection == null) + if (collection is null) { - collection = new AnimationCollection(); - obj.SetValue(ShowAnimationsProperty, collection); + element.SetValue(ShowAnimationsProperty, collection = new()); } return collection; } /// - /// Sets the value of the Implicit.ShowAnimations XAML attached property. + /// Sets the value of the property. /// - /// The to set the value - /// The to set - public static void SetShowAnimations(DependencyObject obj, AnimationCollection value) + /// The to set the value for. + /// The value to set. + public static void SetShowAnimations(UIElement element, ImplicitAnimationSet value) { - obj.SetValue(ShowAnimationsProperty, value); + element.SetValue(ShowAnimationsProperty, value); } /// - /// Gets the value of the Implicit.HideAnimations XAML attached property. + /// Gets the value of the property. /// - /// The to get the value from - /// - public static AnimationCollection GetHideAnimations(DependencyObject obj) + /// The to get the value for. + /// The retrieved value. + public static ImplicitAnimationSet GetHideAnimations(UIElement element) { - var collection = (AnimationCollection)obj.GetValue(HideAnimationsProperty); + var collection = (ImplicitAnimationSet)element.GetValue(HideAnimationsProperty); - if (collection == null) + if (collection is null) { - collection = new AnimationCollection(); - obj.SetValue(HideAnimationsProperty, collection); + element.SetValue(HideAnimationsProperty, collection = new()); } return collection; } /// - /// Sets the value of the Implicit.HideAnimations XAML attached property. + /// Sets the value of the property. /// - /// The to set the value - /// The to set - public static void SetHideAnimations(DependencyObject obj, AnimationCollection value) + /// The to set the value for. + /// The value to set. + public static void SetHideAnimations(UIElement element, ImplicitAnimationSet value) { - obj.SetValue(HideAnimationsProperty, value); + element.SetValue(HideAnimationsProperty, value); } /// - /// Gets the value of the Implicit.Animations XAML attached property. + /// Gets the value of the property. /// - /// The to get the value from - /// - public static AnimationCollection GetAnimations(DependencyObject obj) + /// The to get the value for. + /// The retrieved value. + public static ImplicitAnimationSet GetAnimations(UIElement element) { - var collection = (AnimationCollection)obj.GetValue(AnimationsProperty); + var collection = (ImplicitAnimationSet)element.GetValue(AnimationsProperty); - if (collection == null) + if (collection is null) { - collection = new AnimationCollection(); - obj.SetValue(AnimationsProperty, collection); + element.SetValue(AnimationsProperty, collection = new()); } return collection; } /// - /// Sets the value of the Implicit.Animations XAML attached property. + /// Sets the value of the property. /// - /// The to set the value - /// The to set - public static void SetAnimations(DependencyObject obj, AnimationCollection value) + /// The to set the value for. + /// The value to set. + public static void SetAnimations(UIElement element, ImplicitAnimationSet value) { - obj.SetValue(AnimationsProperty, value); + element.SetValue(AnimationsProperty, value); } - private static void ShowAnimationsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + /// + /// Callback to keep the attached parent in sync for animations linked to the property. + /// + /// The target object the property was changed for. + /// The instance for the current event. + private static void OnShowAnimationsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (e.OldValue is AnimationCollection oldCollection) + static void OnAnimationsChanged(IObservableVector sender, IVectorChangedEventArgs args) { - oldCollection.AnimationCollectionChanged -= ShowCollectionChanged; - } + var collection = (ImplicitAnimationSet)sender; - if (e.NewValue is AnimationCollection animationCollection && d is UIElement element) - { - animationCollection.Parent = element; - animationCollection.AnimationCollectionChanged -= ShowCollectionChanged; - animationCollection.AnimationCollectionChanged += ShowCollectionChanged; - ElementCompositionPreview.SetImplicitShowAnimation(element, GetCompositionAnimationGroup(animationCollection, element)); + if (collection.ParentReference!.TryGetTarget(out UIElement element)) + { + ElementCompositionPreview.SetImplicitShowAnimation(element, collection.GetCompositionAnimationGroup()); + } } - } - private static void HideAnimationsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (e.OldValue is AnimationCollection oldCollection) + if (e.OldValue is ImplicitAnimationSet oldCollection) { - oldCollection.AnimationCollectionChanged -= HideCollectionChanged; + oldCollection.VectorChanged -= OnAnimationsChanged; } - if (e.NewValue is AnimationCollection animationCollection && d is UIElement element) + if (d is UIElement element && + e.NewValue is ImplicitAnimationSet collection) { - animationCollection.Parent = element; - animationCollection.AnimationCollectionChanged -= HideCollectionChanged; - animationCollection.AnimationCollectionChanged += HideCollectionChanged; - ElementCompositionPreview.SetImplicitHideAnimation(element, GetCompositionAnimationGroup(animationCollection, element)); + collection.ParentReference = new(element); + collection.VectorChanged -= OnAnimationsChanged; + collection.VectorChanged += OnAnimationsChanged; + + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + ElementCompositionPreview.SetImplicitShowAnimation(element, collection.GetCompositionAnimationGroup()); } } - private static void AnimationsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + /// + /// Callback to keep the attached parent in sync for animations linked to the property. + /// + /// The target object the property was changed for. + /// The instance for the current event. + private static void OnHideAnimationsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (e.OldValue is AnimationCollection oldCollection) + static void OnAnimationsChanged(IObservableVector sender, IVectorChangedEventArgs args) { - oldCollection.AnimationCollectionChanged -= AnimationsCollectionChanged; - } + var collection = (ImplicitAnimationSet)sender; - if (e.NewValue is AnimationCollection animationCollection && d is UIElement element) - { - animationCollection.Parent = element; - animationCollection.AnimationCollectionChanged -= AnimationsCollectionChanged; - animationCollection.AnimationCollectionChanged += AnimationsCollectionChanged; - ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = GetImplicitAnimationCollection(animationCollection, element); + if (collection.ParentReference!.TryGetTarget(out UIElement element)) + { + ElementCompositionPreview.SetImplicitHideAnimation(element, collection.GetCompositionAnimationGroup()); + } } - } - private static void ShowCollectionChanged(object sender, EventArgs e) - { - var collection = (AnimationCollection)sender; - if (collection.Parent == null) + if (e.OldValue is ImplicitAnimationSet oldCollection) { - return; + oldCollection.VectorChanged -= OnAnimationsChanged; } - ElementCompositionPreview.SetImplicitShowAnimation(collection.Parent, GetCompositionAnimationGroup(collection, collection.Parent)); - } - - private static void HideCollectionChanged(object sender, EventArgs e) - { - var collection = (AnimationCollection)sender; - if (collection.Parent == null) + if (d is UIElement element && + e.NewValue is ImplicitAnimationSet collection) { - return; - } + collection.ParentReference = new(element); + collection.VectorChanged -= OnAnimationsChanged; + collection.VectorChanged += OnAnimationsChanged; - ElementCompositionPreview.SetImplicitHideAnimation(collection.Parent, GetCompositionAnimationGroup(collection, collection.Parent)); + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + ElementCompositionPreview.SetImplicitHideAnimation(element, collection.GetCompositionAnimationGroup()); + } } - private static void AnimationsCollectionChanged(object sender, EventArgs e) + /// + /// Callback to keep the attached parent in sync for animations linked to the property. + /// + /// The target object the property was changed for. + /// The instance for the current event. + private static void OnAnimationsPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - var collection = (AnimationCollection)sender; - if (collection.Parent == null) + static void OnAnimationsChanged(IObservableVector sender, IVectorChangedEventArgs args) { - return; - } + var collection = (ImplicitAnimationSet)sender; - ElementCompositionPreview.GetElementVisual(collection.Parent).ImplicitAnimations = - GetImplicitAnimationCollection(collection, collection.Parent); - } + if (collection.ParentReference!.TryGetTarget(out UIElement element)) + { + ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = collection.GetImplicitAnimationCollection(); + } + } - private static CompositionAnimationGroup GetCompositionAnimationGroup(AnimationCollection collection, UIElement element) - { - if (collection.ContainsTranslationAnimation) + if (e.OldValue is ImplicitAnimationSet oldCollection) { - ElementCompositionPreview.SetIsTranslationEnabled(element, true); + oldCollection.VectorChanged -= OnAnimationsChanged; } - return collection.GetCompositionAnimationGroup(element); - } - - private static ImplicitAnimationCollection GetImplicitAnimationCollection(AnimationCollection collection, UIElement element) - { - if (collection.ContainsTranslationAnimation) + if (d is UIElement element && + e.NewValue is ImplicitAnimationSet collection) { + collection.ParentReference = new(element); + collection.VectorChanged -= OnAnimationsChanged; + collection.VectorChanged += OnAnimationsChanged; + ElementCompositionPreview.SetIsTranslationEnabled(element, true); + ElementCompositionPreview.GetElementVisual(element).ImplicitAnimations = collection.GetImplicitAnimationCollection(); } - - return collection.GetImplicitAnimationCollection(element); } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/ItemsReorderAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/ItemsReorderAnimation.cs new file mode 100644 index 00000000000..efb1ff1880a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/ItemsReorderAnimation.cs @@ -0,0 +1,165 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.ApplicationModel; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Hosting; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// Provides the ability to assign a reorder animation to a . + /// + public static class ItemsReorderAnimation + { + /// + /// Identifies the attached "Duration" . + /// + public static readonly DependencyProperty DurationProperty = DependencyProperty.RegisterAttached( + "Duration", + typeof(TimeSpan), + typeof(ItemsReorderAnimation), + new PropertyMetadata(TimeSpan.Zero, OnDurationChanged)); + + /// + /// Identifies the attached "ReorderAnimation" . + /// + private static readonly DependencyProperty ReorderAnimationProperty = DependencyProperty.RegisterAttached( + "ReorderAnimation", + typeof(ImplicitAnimationCollection), + typeof(ItemsReorderAnimation), + new PropertyMetadata(null)); + + /// + /// Gets the value of the property. + /// + /// The to get the value for. + /// The retrieved value. + public static TimeSpan GetDuration(ListViewBase listView) + { + return (TimeSpan)listView.GetValue(DurationProperty); + } + + /// + /// Sets a value for the duration, in milliseconds, the animation should take. + /// + /// the object to set the value on. + /// The duration. + public static void SetDuration(ListViewBase listView, TimeSpan value) + { + listView.SetValue(DurationProperty, value); + } + + /// + /// Callback to apply the reorder animation when changes. + /// + /// The target object the property was changed for. + /// The instance for the current event. + private static void OnDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (DesignMode.DesignModeEnabled) + { + return; + } + + if (d is ListViewBase listView && e.NewValue is TimeSpan duration) + { + AssignReorderAnimation(listView, duration); + + listView.ContainerContentChanging -= OnContainerContentChanging; + listView.ContainerContentChanging += OnContainerContentChanging; + + listView.ChoosingItemContainer -= OnChoosingItemContainer; + listView.ChoosingItemContainer += OnChoosingItemContainer; + } + } + + /// + /// Updates the reorder animation for a target instance. + /// + /// The target instance. + /// The duration of the animation. + private static void AssignReorderAnimation(ListViewBase listView, TimeSpan duration) + { + Compositor compositor = ElementCompositionPreview.GetElementVisual(listView).Compositor; + ImplicitAnimationCollection animationCollection = (ImplicitAnimationCollection)listView.GetValue(ReorderAnimationProperty); + + if (animationCollection is null) + { + animationCollection = compositor.CreateImplicitAnimationCollection(); + + listView.SetValue(ReorderAnimationProperty, animationCollection); + } + + if (duration == TimeSpan.Zero) + { + animationCollection.Remove(nameof(Visual.Offset)); + } + else + { + Vector3KeyFrameAnimation offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); + + offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue"); + offsetAnimation.Duration = duration; + offsetAnimation.Target = nameof(Visual.Offset); + + CompositionAnimationGroup animationGroup = compositor.CreateAnimationGroup(); + + animationGroup.Add(offsetAnimation); + + animationCollection[nameof(Visual.Offset)] = animationGroup; + } + } + + /// + /// Updates the reorder animation to each container, whenever one changes. + /// + /// The sender instance. + /// The instance for the current container change. + private static void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + { + if (args.InRecycleQueue) + { + PokeUIElementZIndex(args.ItemContainer); + } + else + { + Visual visual = ElementCompositionPreview.GetElementVisual(args.ItemContainer); + ImplicitAnimationCollection? animationCollection = sender.GetValue(ReorderAnimationProperty) as ImplicitAnimationCollection; + + visual.ImplicitAnimations = animationCollection; + } + } + + /// + /// Pokes the Z index of each container when one is chosen, to ensure animations are displayed correctly. + /// + /// The sender instance. + /// The instance for the current container event. + private static void OnChoosingItemContainer(ListViewBase sender, ChoosingItemContainerEventArgs args) + { + if (args.ItemContainer is not null) + { + PokeUIElementZIndex(args.ItemContainer); + } + } + + /// + /// Pokes the Z index of a target . + /// + /// The target to poke the Z index for. + private static void PokeUIElementZIndex(UIElement element) + { + int oldZIndex = Canvas.GetZIndex(element); + + Canvas.SetZIndex(element, oldZIndex + 1); + Canvas.SetZIndex(element, oldZIndex); + } + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj b/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj index c127c5a7431..77c82601938 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Microsoft.Toolkit.Uwp.UI.Animations.csproj @@ -7,22 +7,35 @@ This library provides helpers and extensions on top of Windows Composition and XAML storyboards. It is part of the Windows Community Toolkit. Namespace: - - Behaviors: Blur, CompositionBehaviorBase, CompositionBehaviorBase, Fade, FadeHeaderBehavior, Light, Offset, QuickReturnHeaderBehavior, Rotate, Saturation, Scale, StickyHeaderBehavior. - CompositionAnimations: - Animations: AnimationBase, OffsetAnimation, OpacityAnimation, RotationAnimation, RotationInDegreesAnimation, ScalarAnimation, ScaleAnimation, TranslationAnimation, TypedAnimationBase, Vector2Animation, Vector3Animation, Vector4Animation - CompositionAnimations: ExpressionKeyFrame, KeyFrame, KeyFrameCollection, ScalarKeyFrame, TypedKeyFrame, Vector2KeyFrame, Vector3KeyFrame, Vector4KeyFrame - ConnectedAnimations: Connected, ConnectedAnimationHelper, ConnectedAnimationListProperty, ConnectedAnimationProperties - - Effects: AnimationEffect, Blur, Saturation - Expressions: ExpressionNodes, ExpressionValues, ReferenceNodes, CompositionExtensions, ExpressionFunctions, OperationType - AnimationExtensions: Blur, Fade, Light, Offset, Rotate, Saturation, Scale UWP Toolkit Windows Animations Composition Connected Implicit XAML + 9.0 + + + + + + + + + + + + + + + + - - diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Options/RepeatOption.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Options/RepeatOption.cs new file mode 100644 index 00000000000..62515f7a833 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Options/RepeatOption.cs @@ -0,0 +1,113 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using Microsoft.Toolkit.Diagnostics; +using Windows.Foundation.Metadata; +using Windows.UI.Composition; +using Windows.UI.Xaml.Media.Animation; + +#pragma warning disable CS0419 + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A type describing the repeat behavior for a custom animation. + /// + [CreateFromString(MethodName = "Microsoft.Toolkit.Uwp.UI.Animations.RepeatOption.Parse")] + public readonly struct RepeatOption + { + /// + /// The number of iterations for the animation. + /// + private readonly int value; + + /// + /// Initializes a new instance of the struct. + /// + /// The number of iterations for the animation. + private RepeatOption(int value) + { + this.value = value; + } + + /// + /// Gets a value representing a single iteration. + /// + public static RepeatOption Once => new(1); + + /// + /// Gets a value indicating an animation that repeats forever. + /// + public static RepeatOption Forever => new(-1); + + /// + /// Creates a value with the specified number of iterations. + /// + /// The number of iterations for the animation. + /// A value with the specified number of iterations. + /// Thrown when is negative. + [Pure] + public static RepeatOption Count(int count) + { + Guard.IsGreaterThanOrEqualTo(count, 0, nameof(count)); + + return new(count); + } + + /// + /// Parses a value from a . + /// The allowed values are either non-negative integers, or "Forever". + /// + /// The input text to parse. + /// The parsed value. + [Pure] + public static RepeatOption Parse(string text) + { + if (int.TryParse(text, out int count)) + { + return Count(count); + } + + if (text.Trim().Equals("Forever", StringComparison.InvariantCultureIgnoreCase)) + { + return Forever; + } + + return ThrowHelper.ThrowArgumentException("Invalid input text"); + } + + /// + /// Gets a value corresponding to the current value. + /// + /// A value matching the current value. + [Pure] + public RepeatBehavior ToRepeatBehavior() + { + if (this.value < 0) + { + return RepeatBehavior.Forever; + } + + return new(this.value); + } + + /// + /// Gets the and count values matching the current value. + /// If the current value represents an infinitely repeating animation, the returned count will be set to 1. + /// + /// The and count values matching the current value. + [Pure] + public (AnimationIterationBehavior Behavior, int Count) ToBehaviorAndCount() + { + if (this.value < 0) + { + return (AnimationIterationBehavior.Forever, 1); + } + + return (AnimationIterationBehavior.Count, this.value); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Properties/AssemblyInfo.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..3b172ff690a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Toolkit.Uwp.UI.Media")] diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Properties/IsExternalInit.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Properties/IsExternalInit.cs new file mode 100644 index 00000000000..cadcf5a8570 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Properties/IsExternalInit.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + /// + /// Reserved to be used by the compiler for tracking metadata. + /// This class should not be used by developers in source code. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + internal static class IsExternalInit + { + } +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/ReorderGridAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/ReorderGridAnimation.cs deleted file mode 100644 index f05d5b1d46c..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/ReorderGridAnimation.cs +++ /dev/null @@ -1,130 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Windows.UI.Composition; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Hosting; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// Provides the ability to assign a reorder animation to a GridView. - /// - public class ReorderGridAnimation - { - private static readonly DependencyProperty ReorderAnimationProperty = - DependencyProperty.RegisterAttached("ReorderAnimation", typeof(bool), typeof(ImplicitAnimationCollection), new PropertyMetadata(null)); - - /// - /// Identifies the Duration attached dependency property. - /// - /// The identifier for the Duration attached dependency property. - public static readonly DependencyProperty DurationProperty = DependencyProperty.RegisterAttached("Duration", typeof(double), typeof(ReorderGridAnimation), new PropertyMetadata(double.NaN, OnDurationChanged)); - - /// - /// Gets a value indicating the duration, in milliseconds, the animation should take. - /// - /// The object to get the value from. - /// A value indicating the duration for the animation. - public static double GetDuration(DependencyObject obj) - { - return (double)obj.GetValue(DurationProperty); - } - - /// - /// Sets a value for the duration, in milliseconds, the animation should take. - /// - /// the object to set the value on. - /// The duration in milliseconds. - public static void SetDuration(DependencyObject obj, double value) - { - obj.SetValue(DurationProperty, value); - } - - private static void OnDurationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) - { - return; - } - - GridView view = d as GridView; - if (view != null) - { - AssignReorderAnimation(view); - - view.ContainerContentChanging -= OnContainerContentChanging; - view.ContainerContentChanging += OnContainerContentChanging; - - view.ChoosingItemContainer -= OnChoosingItemContainer; - view.ChoosingItemContainer += OnChoosingItemContainer; - } - } - - private static void AssignReorderAnimation(GridView view) - { - var compositor = ElementCompositionPreview.GetElementVisual(view).Compositor; - var elementImplicitAnimation = view.GetValue(ReorderAnimationProperty) as ImplicitAnimationCollection; - if (elementImplicitAnimation == null) - { - elementImplicitAnimation = compositor.CreateImplicitAnimationCollection(); - view.SetValue(ReorderAnimationProperty, elementImplicitAnimation); - } - - double duration = (double)view.GetValue(DurationProperty); - if (double.IsNaN(duration)) - { - elementImplicitAnimation.Remove(nameof(Visual.Offset)); - } - else - { - elementImplicitAnimation[nameof(Visual.Offset)] = CreateOffsetAnimation(compositor, duration); - } - } - - private static void OnContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) - { - var elementVisual = ElementCompositionPreview.GetElementVisual(args.ItemContainer); - if (args.InRecycleQueue) - { - PokeUIElementZIndex(args.ItemContainer); - } - else - { - var elementImplicitAnimation = sender.GetValue(ReorderAnimationProperty) as ImplicitAnimationCollection; - elementVisual.ImplicitAnimations = elementImplicitAnimation; - } - } - - private static void OnChoosingItemContainer(ListViewBase sender, ChoosingItemContainerEventArgs args) - { - if (args.ItemContainer != null) - { - PokeUIElementZIndex(args.ItemContainer); - } - } - - private static CompositionAnimationGroup CreateOffsetAnimation(Compositor compositor, double duration) - { - Vector3KeyFrameAnimation offsetAnimation = compositor.CreateVector3KeyFrameAnimation(); - offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue"); - offsetAnimation.Duration = TimeSpan.FromMilliseconds(duration); - offsetAnimation.Target = nameof(Visual.Offset); - - CompositionAnimationGroup animationGroup = compositor.CreateAnimationGroup(); - animationGroup.Add(offsetAnimation); - - return animationGroup; - } - - private static void PokeUIElementZIndex(UIElement element) - { - var oldZIndex = Canvas.GetZIndex(element); - Canvas.SetZIndex(element, oldZIndex + 1); - Canvas.SetZIndex(element, oldZIndex); - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs b/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs deleted file mode 100644 index 8610c13fb83..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Animations/SurfaceLoader.cs +++ /dev/null @@ -1,190 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using System.Threading.Tasks; -using Microsoft.Graphics.Canvas; -using Microsoft.Graphics.Canvas.Text; -using Microsoft.Graphics.Canvas.UI.Composition; -using Windows.Foundation; -using Windows.Foundation.Metadata; -using Windows.Graphics.DirectX; -using Windows.UI; -using Windows.UI.Composition; - -namespace Microsoft.Toolkit.Uwp.UI.Animations -{ - /// - /// A delegate for load time effects. - /// - /// The bitmap. - /// The device. - /// The size target. - /// A CompositeDrawingSurface - public delegate CompositionDrawingSurface LoadTimeEffectHandler(CanvasBitmap bitmap, CompositionGraphicsDevice device, Size sizeTarget); - - /// - /// The SurfaceLoader is responsible to loading images into Composition Objects. - /// - [Deprecated("This class is deprecated, please use the SurfaceLoader class from the Microsoft.Toolkit.Uwp.UI.Media package.", DeprecationType.Deprecate, 6)] - public class SurfaceLoader - { - /// - /// A flag to store the initialized state. - /// - private static bool _intialized; - - /// - /// The compositor - /// - private static Compositor _compositor; - - /// - /// The canvas device - /// - private static CanvasDevice _canvasDevice; - - /// - /// The composition graphic device to determine which GPU is handling the request. - /// - private static CompositionGraphicsDevice _compositionDevice; - - /// - /// Initializes the specified compositor. - /// - /// The compositor. - public static void Initialize(Compositor compositor) - { - if (!_intialized) - { - _compositor = compositor; - _canvasDevice = new CanvasDevice(); - _compositionDevice = CanvasComposition.CreateCompositionGraphicsDevice(_compositor, _canvasDevice); - - _intialized = true; - } - } - - /// - /// Uninitializes this instance. - /// - public static void Uninitialize() - { - _compositor = null; - - if (_compositionDevice != null) - { - _compositionDevice.Dispose(); - _compositionDevice = null; - } - - if (_canvasDevice != null) - { - _canvasDevice.Dispose(); - _canvasDevice = null; - } - - _intialized = false; - } - - /// - /// Gets a value indicating whether this instance is initialized. - /// - /// - /// true if this instance is initialized; otherwise, false. - /// - public static bool IsInitialized - { - get - { - return _intialized; - } - } - - /// - /// Loads an image from the URI. - /// - /// The URI. - /// - public static async Task LoadFromUri(Uri uri) - { - return await LoadFromUri(uri, Size.Empty); - } - - /// - /// Loads an image from URI with a specified size. - /// - /// The URI. - /// The size target. - /// - public static async Task LoadFromUri(Uri uri, Size sizeTarget) - { - CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(_canvasDevice, uri); - Size sizeSource = bitmap.Size; - - if (sizeTarget.IsEmpty) - { - sizeTarget = sizeSource; - } - - CompositionDrawingSurface surface = _compositionDevice.CreateDrawingSurface( - sizeTarget, - DirectXPixelFormat.B8G8R8A8UIntNormalized, - DirectXAlphaMode.Premultiplied); - - using (var ds = CanvasComposition.CreateDrawingSession(surface)) - { - ds.Clear(Color.FromArgb(0, 0, 0, 0)); - ds.DrawImage(bitmap, new Rect(0, 0, sizeTarget.Width, sizeTarget.Height), new Rect(0, 0, sizeSource.Width, sizeSource.Height)); - } - - return surface; - } - - /// - /// Loads the text on to a . - /// - /// The text. - /// The size target. - /// The text format. - /// Color of the text. - /// Color of the bg. - /// - public static CompositionDrawingSurface LoadText(string text, Size sizeTarget, CanvasTextFormat textFormat, Color textColor, Color bgColor) - { - CompositionDrawingSurface surface = _compositionDevice.CreateDrawingSurface( - sizeTarget, - DirectXPixelFormat.B8G8R8A8UIntNormalized, - DirectXAlphaMode.Premultiplied); - - using (var ds = CanvasComposition.CreateDrawingSession(surface)) - { - ds.Clear(bgColor); - ds.DrawText(text, new Rect(0, 0, sizeTarget.Width, sizeTarget.Height), textColor, textFormat); - } - - return surface; - } - - /// - /// Loads an image from URI, with a specified size. - /// - /// The URI. - /// The size target. - /// The load effect handler callback. - /// - public static async Task LoadFromUri(Uri uri, Size sizeTarget, LoadTimeEffectHandler loadEffectHandler) - { - if (loadEffectHandler != null) - { - var bitmap = await CanvasBitmap.LoadAsync(_canvasDevice, uri); - return loadEffectHandler(bitmap, _compositionDevice, sizeTarget); - } - else - { - return await LoadFromUri(uri, sizeTarget); - } - } - } -} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/Animation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/Animation.cs new file mode 100644 index 00000000000..6e1590697f4 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/Animation.cs @@ -0,0 +1,109 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A base model representing an animation that can be used in XAML. + /// + public abstract class Animation : DependencyObject, ITimeline + { + /// + /// Gets or sets the optional initial delay for the animation. + /// + public TimeSpan? Delay + { + get => (TimeSpan?)GetValue(DelayProperty); + set => SetValue(DelayProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DelayProperty = DependencyProperty.Register( + nameof(Delay), + typeof(TimeSpan?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the animation duration. + /// + public TimeSpan? Duration + { + get => (TimeSpan?)GetValue(DurationProperty); + set => SetValue(DurationProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DurationProperty = DependencyProperty.Register( + nameof(Duration), + typeof(TimeSpan?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional easing function type for the animation. + /// + public EasingType? EasingType + { + get => (EasingType?)GetValue(EasingTypeProperty); + set => SetValue(EasingTypeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EasingTypeProperty = DependencyProperty.Register( + nameof(EasingType), + typeof(EasingType?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional easing function mode for the animation. + /// + public EasingMode? EasingMode + { + get => (EasingMode?)GetValue(EasingModeProperty); + set => SetValue(EasingModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EasingModeProperty = DependencyProperty.Register( + nameof(EasingMode), + typeof(EasingMode?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the repeat option for the animation. + /// + public RepeatOption Repeat + { + get => (RepeatOption)GetValue(RepeatOptionProperty); + set => SetValue(RepeatOptionProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty RepeatOptionProperty = DependencyProperty.Register( + nameof(Repeat), + typeof(RepeatOption), + typeof(Animation), + new PropertyMetadata(RepeatOption.Once)); + + /// + public abstract AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/Animation{TValue,TKeyFrame}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/Animation{TValue,TKeyFrame}.cs new file mode 100644 index 00000000000..ad90e43cb93 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/Animation{TValue,TKeyFrame}.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections.Generic; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A base model representing a typed animation that can be used in XAML. + /// + /// + /// The type to use for the public and properties. + /// This can differ from to facilitate XAML parsing. + /// + /// The actual type of keyframe values in use. + [ContentProperty(Name = nameof(KeyFrames))] + public abstract class Animation : Animation + where TKeyFrame : unmanaged + { + /// + /// Gets or sets the final value for the animation. + /// + public TValue? To + { + get => (TValue?)GetValue(ToProperty); + set => SetValue(ToProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ToProperty = DependencyProperty.Register( + nameof(To), + typeof(TValue?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional starting value for the animation. + /// + public TValue? From + { + get => (TValue?)GetValue(FromProperty); + set => SetValue(FromProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty FromProperty = DependencyProperty.Register( + nameof(From), + typeof(TValue?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional keyframe collection for the current animation. + /// Setting this will overwrite the and values. + /// + public IList> KeyFrames + { + get + { + if (GetValue(KeyFramesProperty) is not IList> keyFrames) + { + keyFrames = new List>(); + + SetValue(KeyFramesProperty, keyFrames); + } + + return keyFrames; + } + set => SetValue(KeyFramesProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty KeyFramesProperty = DependencyProperty.Register( + nameof(KeyFrames), + typeof(IList>), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets the explicit target for the animation. This is the primary target property that is animated. + /// + protected abstract string ExplicitTarget { get; } + + /// + public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint) + { + return builder.NormalizedKeyFrames This, EasingType? EasingTypeHint, EasingMode? EasingModeHint)>( + property: ExplicitTarget, + state: (this, easingTypeHint, easingModeHint), + delay: Delay ?? delayHint ?? DefaultDelay, + duration: Duration ?? durationHint ?? DefaultDuration, + repeatOption: Repeat, + build: static (b, s) => s.This.AppendToBuilder(b, s.EasingTypeHint, s.EasingModeHint)); + } + + /// + /// Gets the parsed values from . + /// + /// The parsed animation values as . + protected abstract (TKeyFrame? To, TKeyFrame? From) GetParsedValues(); + + /// + /// Appends the current keyframe values to a target instance. + /// This method will also automatically generate keyframes for and . + /// + /// The target instance to add the keyframe to. + /// A hint for the easing type, if present. + /// A hint for the easing mode, if present. + /// The same instance as . + protected INormalizedKeyFrameAnimationBuilder AppendToBuilder(INormalizedKeyFrameAnimationBuilder builder, EasingType? easingTypeHint, EasingMode? easingModeHint) + { + foreach (var keyFrame in KeyFrames) + { + builder = keyFrame.AppendToBuilder(builder); + } + + var (to, from) = GetParsedValues(); + + if (to is not null) + { + builder.KeyFrame( + 1.0, + to.Value, + EasingType ?? easingTypeHint ?? DefaultEasingType, + EasingMode ?? easingModeHint ?? DefaultEasingMode); + } + + if (from is not null) + { + builder.KeyFrame(0.0, from.Value, default, default); + } + + return builder; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs new file mode 100644 index 00000000000..5f070e6eb6d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/CustomAnimation{TValue,TKeyFrame}.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A custom animation types, that can target both the composition and the XAML layer, and that + /// can adapt based on the context it is being used from (eg. explicit or implicit animation). + /// + /// + public abstract class CustomAnimation : ImplicitAnimation + where TKeyFrame : unmanaged + { + /// + /// Gets or sets the target property for the animation. + /// + public string? Target { get; set; } + + /// + /// Gets or sets the target framework layer for the animation. This is only supported + /// for a set of animation types (see the docs for more on this). Furthermore, this is + /// ignored when the animation is being used as an implicit composition animation. + /// The default value is . + /// + public FrameworkLayer Layer { get; set; } + + /// + protected override string ExplicitTarget => Target!; + + /// + public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint) + { + return builder.NormalizedKeyFrames This, EasingType? EasingTypeHint, EasingMode? EasingModeHint)>( + property: ExplicitTarget, + state: (this, easingTypeHint, easingModeHint), + delay: Delay ?? delayHint ?? DefaultDelay, + duration: Duration ?? durationHint ?? DefaultDuration, + layer: Layer, + build: static (b, s) => s.This.AppendToBuilder(b, s.EasingTypeHint, s.EasingModeHint)); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/ImplicitAnimation{TValue,TKeyFrame}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/ImplicitAnimation{TValue,TKeyFrame}.cs new file mode 100644 index 00000000000..b989f90cc5b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/ImplicitAnimation{TValue,TKeyFrame}.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A base model representing a typed animation that can be used as an implicit composition animation. + /// + /// + public abstract class ImplicitAnimation : Animation, IImplicitTimeline + where TKeyFrame : unmanaged + { + /// + /// Gets or sets the optional implicit target for the animation. This can act as a trigger property for the animation. + /// + public string? ImplicitTarget { get; set; } + + /// + public CompositionAnimation GetAnimation(UIElement element, out string? target) + { + NormalizedKeyFrameAnimationBuilder.Composition builder = new( + ExplicitTarget, + Delay ?? DefaultDelay, + Duration ?? DefaultDuration, + Repeat); + + var (to, from) = GetParsedValues(); + + // If there are no values set for the animation at all (no initial/target values, nor + // keyframes), we just manually insert a single expression keyframe pointing to the final + // value for the current animation. This is often the case with implicit animations, as + // it is used to smoothly transition between two discrete property changes for a visual. + if (to is null && from is null && KeyFrames.Count == 0) + { + builder.ExpressionKeyFrame(1.0, "this.FinalValue", DefaultEasingType, DefaultEasingMode); + } + else + { + // Otherwise, we just insert the keyframes for the initial/target values, as well as the + // other keyframes that might be present into the current animation. The order is not + // important when inserting keyframes, as each one stores the normalized progress value. + if (to is not null) + { + builder.KeyFrame(1.0, to.Value, EasingType ?? DefaultEasingType, EasingMode ?? DefaultEasingMode); + } + + if (from is not null) + { + builder.KeyFrame(0.0, from.Value, default, default); + } + + foreach (var keyFrame in KeyFrames) + { + keyFrame.AppendToBuilder(builder); + } + } + + target = ImplicitTarget; + + return builder.GetAnimation(element.GetVisual(), out _); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/KeyFrame{TValue,TKeyFrame}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/KeyFrame{TValue,TKeyFrame}.cs new file mode 100644 index 00000000000..40d6c668bbc --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Abstract/KeyFrame{TValue,TKeyFrame}.cs @@ -0,0 +1,132 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A base model representing a typed keyframe that can be used in XAML. + /// + /// + /// The type to use for the public property. + /// This can differ from to facilitate XAML parsing. + /// + /// The actual type of keyframe values in use. + public abstract class KeyFrame : DependencyObject, IKeyFrame + { + /// + /// Gets or sets the key time for the current keyframe. This is a normalized + /// value in the [0, 1] range, relative to the total animation duration. + /// + public double Key + { + get => (double)GetValue(KeyProperty); + set => SetValue(KeyProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty KeyProperty = DependencyProperty.Register( + nameof(Key), + typeof(double), + typeof(KeyFrame), + new PropertyMetadata(0.0)); + + /// + /// Gets or sets the animation value for the current keyframe. + /// + public TValue? Value + { + get => (TValue?)GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( + nameof(Value), + typeof(TValue?), + typeof(KeyFrame), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional expression for the current keyframe. + /// If this is set, will be ignored. + /// + public string? Expression + { + get => (string?)GetValue(ExpressionProperty); + set => SetValue(ExpressionProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ExpressionProperty = DependencyProperty.Register( + nameof(Expression), + typeof(string), + typeof(KeyFrame), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional easing function type for the keyframe. + /// + public EasingType? EasingType + { + get => (EasingType?)GetValue(EasingTypeProperty); + set => SetValue(EasingTypeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EasingTypeProperty = DependencyProperty.Register( + nameof(EasingType), + typeof(EasingType?), + typeof(KeyFrame), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional easing function mode for the keyframe. + /// + public EasingMode? EasingMode + { + get => (EasingMode?)GetValue(EasingModeProperty); + set => SetValue(EasingModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EasingModeProperty = DependencyProperty.Register( + nameof(EasingMode), + typeof(EasingMode?), + typeof(KeyFrame), + new PropertyMetadata(null)); + + /// + public INormalizedKeyFrameAnimationBuilder AppendToBuilder(INormalizedKeyFrameAnimationBuilder builder) + { + if (Expression is not null) + { + return builder.ExpressionKeyFrame(Key, Expression, EasingType ?? DefaultEasingType, EasingMode ?? DefaultEasingMode); + } + + return builder.KeyFrame(Key, GetParsedValue()!, EasingType ?? DefaultEasingType, EasingMode ?? DefaultEasingMode); + } + + /// + /// Gets the parsed values for . + /// + /// The parsed keyframe values a . + protected abstract TKeyFrame? GetParsedValue(); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/Activity.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/Activity.cs new file mode 100644 index 00000000000..88ad912ab2f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/Activity.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// Base class to use when creating activities which accept a . + /// + public abstract class Activity : DependencyObject, IActivity + { + /// + /// Gets or sets the to wait before running the activity. + /// + public TimeSpan? Delay + { + get => (TimeSpan?)GetValue(DelayProperty); + set => SetValue(DelayProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DelayProperty = DependencyProperty.Register( + nameof(Delay), + typeof(TimeSpan?), + typeof(Activity), + new PropertyMetadata(null)); + + /// + public virtual Task InvokeAsync(UIElement element, CancellationToken token) + { + if (Delay is not null) + { + return Task.Delay(Delay.Value, token); + } + + return Task.CompletedTask; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/StartAnimationActivity.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/StartAnimationActivity.cs new file mode 100644 index 00000000000..3abc6a08e84 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/StartAnimationActivity.cs @@ -0,0 +1,77 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Toolkit.Diagnostics; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An which starts the provided when invoked. + /// + public class StartAnimationActivity : Activity + { + /// + /// Gets or sets the linked instance to start. + /// + public AnimationSet Animation + { + get => (AnimationSet)GetValue(AnimationProperty); + set => SetValue(AnimationProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AnimationProperty = DependencyProperty.Register( + nameof(Animation), + typeof(AnimationSet), + typeof(StartAnimationActivity), + new PropertyMetadata(null)); + + /// + /// Gets or sets the object to start the specified animation on. If not specified, will use the current object the parent animation is running on. + /// + public UIElement TargetObject + { + get => (UIElement)GetValue(TargetObjectProperty); + set => SetValue(TargetObjectProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register( + nameof(TargetObject), + typeof(UIElement), + typeof(StartAnimationActivity), + new PropertyMetadata(null)); + + /// + public override async Task InvokeAsync(UIElement element, CancellationToken token) + { + Guard.IsNotNull(Animation, nameof(Animation)); + + await base.InvokeAsync(element, token); + + // If we've specified an explicit target for the animation, we can use that. Otherwise, we can + // check whether the target animation has an implicit parent. If that's the case, we will use + // that to start the animation, or just use the input (usually the parent) as fallback. + if (TargetObject is not null) + { + await Animation.StartAsync(TargetObject, token); + } + else if (Animation.ParentReference is null) + { + await Animation.StartAsync(element, token); + } + else + { + await Animation.StartAsync(token); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/StopAnimationActivity.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/StopAnimationActivity.cs new file mode 100644 index 00000000000..58a174ea7f2 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Activities/StopAnimationActivity.cs @@ -0,0 +1,74 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Toolkit.Diagnostics; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An which stops the provided when invoked. + /// + public class StopAnimationActivity : Activity + { + /// + /// Gets or sets the linked instance to stop. + /// + public AnimationSet Animation + { + get => (AnimationSet)GetValue(AnimationProperty); + set => SetValue(AnimationProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AnimationProperty = DependencyProperty.Register( + nameof(Animation), + typeof(AnimationSet), + typeof(StartAnimationActivity), + new PropertyMetadata(null)); + + /// + /// Gets or sets the object to stop the specified animation for. If not specified, will use the current object the parent animation is running on. + /// + public UIElement TargetObject + { + get => (UIElement)GetValue(TargetObjectProperty); + set => SetValue(TargetObjectProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register( + nameof(TargetObject), + typeof(UIElement), + typeof(StartAnimationActivity), + new PropertyMetadata(null)); + + /// + public override async Task InvokeAsync(UIElement element, CancellationToken token) + { + Guard.IsNotNull(Animation, nameof(Animation)); + + await base.InvokeAsync(element, token); + + if (TargetObject is not null) + { + Animation.Stop(TargetObject); + } + else if (Animation.ParentReference is null) + { + Animation.Stop(element); + } + else + { + Animation.Stop(); + } + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationDictionary.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationDictionary.cs new file mode 100644 index 00000000000..cf1483c933a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationDictionary.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Collections; +using System.Collections.Generic; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A collection of animations that can be defined from XAML. + /// + public sealed class AnimationDictionary : DependencyObject, IList + { + /// + /// The underlying list of animations. + /// + private readonly List list = new(); + + /// + /// The reference to the parent that owns the current animation dictionary. + /// + private WeakReference? parent; + + /// + /// Sets the parent for the current animation dictionary. + /// + internal UIElement? Parent + { + set + { + WeakReference parent = this.parent = new(value!); + + foreach (var item in this.list) + { + item.ParentReference = parent; + } + } + } + + /// + public int Count => this.list.Count; + + /// + public bool IsReadOnly => false; + + /// + public AnimationSet this[int index] + { + get => this.list[index]; + set + { + this.list[index].ParentReference = null; + this.list[index] = value; + + value.ParentReference = this.parent; + } + } + + /// + public void Add(AnimationSet item) + { + this.list.Add(item); + + item.ParentReference = this.parent; + } + + /// + public void Clear() + { + foreach (var item in this.list) + { + item.ParentReference = this.parent; + } + + this.list.Clear(); + } + + /// + public bool Contains(AnimationSet item) + { + return this.list.Contains(item); + } + + /// + public void CopyTo(AnimationSet[] array, int arrayIndex) + { + this.list.CopyTo(array, arrayIndex); + } + + /// + public IEnumerator GetEnumerator() + { + return this.list.GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return this.list.GetEnumerator(); + } + + /// + public int IndexOf(AnimationSet item) + { + return this.list.IndexOf(item); + } + + /// + public void Insert(int index, AnimationSet item) + { + this.list.Insert(index, item); + + item.ParentReference = this.parent; + } + + /// + public bool Remove(AnimationSet item) + { + bool removed = this.list.Remove(item); + + if (removed) + { + item.ParentReference = null; + } + + return removed; + } + + /// + public void RemoveAt(int index) + { + this.list[index].ParentReference = null; + this.list.RemoveAt(index); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationScope.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationScope.cs new file mode 100644 index 00000000000..a917a2f0b6e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationScope.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A container of elements that can be used to conceptually group animations + /// together and to assign shared properties to be applied to all the contained items automatically. + /// + public sealed class AnimationScope : DependencyObjectCollection, ITimeline + { + /// + /// Gets or sets the optional initial delay for the animation. + /// + public TimeSpan? Delay + { + get => (TimeSpan?)GetValue(DelayProperty); + set => SetValue(DelayProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DelayProperty = DependencyProperty.Register( + nameof(Delay), + typeof(TimeSpan?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the animation duration. + /// + public TimeSpan? Duration + { + get => (TimeSpan?)GetValue(DurationProperty); + set => SetValue(DurationProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty DurationProperty = DependencyProperty.Register( + nameof(Duration), + typeof(TimeSpan?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional easing function type for the animation. + /// + public EasingType? EasingType + { + get => (EasingType?)GetValue(EasingTypeProperty); + set => SetValue(EasingTypeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EasingTypeProperty = DependencyProperty.Register( + nameof(EasingType), + typeof(EasingType?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + /// Gets or sets the optional easing function mode for the animation. + /// + public EasingMode? EasingMode + { + get => (EasingMode?)GetValue(EasingModeProperty); + set => SetValue(EasingModeProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EasingModeProperty = DependencyProperty.Register( + nameof(EasingMode), + typeof(EasingMode?), + typeof(Animation), + new PropertyMetadata(null)); + + /// + public AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint) + { + foreach (ITimeline element in this) + { + builder = element.AppendToBuilder(builder, Delay ?? delayHint, Duration ?? durationHint, EasingType ?? easingTypeHint, EasingMode ?? easingModeHint); + } + + return builder; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationSet.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationSet.cs new file mode 100644 index 00000000000..22441c8409f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/AnimationSet.cs @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Toolkit.Diagnostics; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A collection of animations that can be grouped together. This type represents a composite animation + /// (such as ) that can be executed on a given element. + /// + public sealed class AnimationSet : DependencyObjectCollection + { + /// + /// A conditional weak table storing instances associated with animations + /// that have been started from the current set. This can be used to defer stopping running animations for any + /// target instance that originated from the current . + /// + private readonly ConditionalWeakTable cancellationTokenMap = new(); + + /// + /// Raised whenever the current animation is started. + /// + public event EventHandler? Started; + + /// + /// Raised whenever the current animation ends. + /// + public event EventHandler? Ended; + + /// + /// An interface representing a node in an instance. + /// + public interface INode + { + } + + /// + /// Gets or sets a value indicating whether top level animation nodes in this collection are invoked + /// sequentially. This applies to both nodes (which will still trigger + /// contained animations at the same time), and other top level animation nodes. The default value + /// is , which means that all contained animations will start at the same time. + /// + /// Note that this property will also cause a change in behavior for the animation. With the default + /// configuration, with all animations starting at the same time, it's not possible to use multiple + /// animations targeting the same property (as they'll cause a conflict and be ignored when on the + /// composition layer, or cause a crash when on the XAML layer). When animations are started sequentially + /// instead, each sequential block will be able to share target properties with animations from other + /// sequential blocks, without issues. Note that especially for simple scenarios (eg. an opacity animation + /// that just transitions to a state and then back, or between two states), it is recommended to use a single + /// keyframe animation instead, which will result in less overhead when creating and starting the animation. + /// + /// + public bool IsSequential { get; set; } + + /// + /// Gets or sets the weak reference to the parent that owns the current animation collection. + /// + internal WeakReference? ParentReference { get; set; } + + /// + /// Thrown when there is no attached instance. + public async void Start() + { + // Here we're using an async void method on purpose, in order to be able to await + // the completion of the animation and rethrow exceptions. We can't just use the + // synchronous AnimationBuilder.Start method here, as we also need to await for the + // animation to complete in either case in order to raise the Ended event when that + // happens. So we add an async state machine here to work around this. + await StartAsync(); + } + + /// + public async void Start(UIElement element) + { + await StartAsync(element); + } + + /// + /// Thrown when there is no attached instance. + public async void Start(CancellationToken token) + { + await StartAsync(token); + } + + /// + /// Thrown when there is no attached instance. + public Task StartAsync() + { + return StartAsync(GetParent()); + } + + /// + public Task StartAsync(UIElement element) + { + Stop(element); + + CancellationTokenSource cancellationTokenSource = new(); + + this.cancellationTokenMap.AddOrUpdate(element, cancellationTokenSource); + + return StartAsync(element, cancellationTokenSource.Token); + } + + /// + /// Thrown when there is no attached instance. + public Task StartAsync(CancellationToken token) + { + return StartAsync(GetParent(), token); + } + + /// + public async Task StartAsync(UIElement element, CancellationToken token) + { + Started?.Invoke(this, EventArgs.Empty); + + if (IsSequential) + { + foreach (INode node in this) + { + if (node is ITimeline timeline) + { + var builder = AnimationBuilder.Create(); + + timeline.AppendToBuilder(builder); + + await builder.StartAsync(element, token); + } + else if (node is IActivity activity) + { + try + { + // Unlike with animations, activities can potentially throw if they execute + // an await operation on a task that was linked to a cancellation token. For + // instance, this is the case for the await operation for the initial delay, + // and the same can apply to 3rd party activities that would just integrate + // the input token into their logic. We can just catch these exceptions and + // stop the sequential execution immediately from the handler. + await activity.InvokeAsync(element, token); + } + catch (OperationCanceledException) + { + break; + } + } + + // This should in theory only be necessary in the timeline branch, but doing this check + // after running activities too help guard against 3rd party activities that might not + // properly monitor the token being in use, and still run fine after a cancellation. + if (token.IsCancellationRequested) + { + break; + } + } + } + else + { + var builder = AnimationBuilder.Create(); + + foreach (INode node in this) + { + switch (node) + { + case ITimeline timeline: + builder = timeline.AppendToBuilder(builder); + break; + case IActivity activity: + _ = activity.InvokeAsync(element, token); + break; + } + } + + await builder.StartAsync(element, token); + } + + Ended?.Invoke(this, EventArgs.Empty); + } + + /// + /// Cancels the current animation on the attached instance. + /// + /// Thrown when there is no attached instance. + public void Stop() + { + Stop(GetParent()); + } + + /// + /// Cancels the current animation for a target instance. + /// + /// The target instance to stop the animation for. + public void Stop(UIElement element) + { + if (this.cancellationTokenMap.TryGetValue(element, out CancellationTokenSource value)) + { + value.Cancel(); + } + } + + /// + /// Gets the current parent instance. + /// + /// The reference from . + /// Thrown if there is no parent available. + [Pure] + private UIElement GetParent() + { + UIElement? parent = null; + + if (ParentReference?.TryGetTarget(out parent) != true) + { + ThrowHelper.ThrowInvalidOperationException("The current animation collection isn't bound to a parent UIElement instance."); + } + + return parent!; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/ColorAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/ColorAnimation.cs new file mode 100644 index 00000000000..386748ba93a --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/ColorAnimation.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI; + +#pragma warning disable CS0419 + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A custom animation. + /// + public sealed class ColorAnimation : CustomAnimation + { + /// + protected override (Color?, Color?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/QuaternionAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/QuaternionAnimation.cs new file mode 100644 index 00000000000..dcd29250b9f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/QuaternionAnimation.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A custom animation. + /// + public sealed class QuaternionAnimation : CustomAnimation + { + /// + protected override (Quaternion?, Quaternion?) GetParsedValues() + { + return (To?.ToQuaternion(), From?.ToQuaternion()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/ScalarAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/ScalarAnimation.cs new file mode 100644 index 00000000000..65eea298f0d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/ScalarAnimation.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A custom scalar animation. + /// + public sealed class ScalarAnimation : CustomAnimation + { + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector2Animation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector2Animation.cs new file mode 100644 index 00000000000..5c080094181 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector2Animation.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A custom animation. + /// + public sealed class Vector2Animation : CustomAnimation + { + /// + protected override (Vector2?, Vector2?) GetParsedValues() + { + return (To?.ToVector2(), From?.ToVector2()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector3Animation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector3Animation.cs new file mode 100644 index 00000000000..ed1be8c4bbe --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector3Animation.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A custom animation. + /// + public sealed class Vector3Animation : CustomAnimation + { + /// + protected override (Vector3?, Vector3?) GetParsedValues() + { + return (To?.ToVector3(), From?.ToVector3()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector4Animation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector4Animation.cs new file mode 100644 index 00000000000..818343e55c5 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Custom/Vector4Animation.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A custom animation. + /// + public sealed class Vector4Animation : CustomAnimation + { + /// + protected override (Vector4?, Vector4?) GetParsedValues() + { + return (To?.ToVector4(), From?.ToVector4()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/AnchorPointAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/AnchorPointAnimation.cs new file mode 100644 index 00000000000..6ccc6fee709 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/AnchorPointAnimation.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An anchor point animation working on the composition layer. + /// + public sealed class AnchorPointAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.AnchorPoint); + + /// + protected override (Vector2?, Vector2?) GetParsedValues() + { + return (To?.ToVector2(), From?.ToVector2()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/CenterPointAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/CenterPointAnimation.cs new file mode 100644 index 00000000000..a9538e07269 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/CenterPointAnimation.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A center point animation working on the composition layer. + /// + public sealed class CenterPointAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.CenterPoint); + + /// + protected override (Vector3?, Vector3?) GetParsedValues() + { + return (To?.ToVector3(), From?.ToVector3()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/ClipAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/ClipAnimation.cs new file mode 100644 index 00000000000..2e340136c68 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/ClipAnimation.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A clip animation working on the composition layer. + /// + public sealed class ClipAnimation : Animation + { + /// + protected override string ExplicitTarget => throw new NotImplementedException(); + + /// + public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint) + { + return builder.Clip( + To!.Value, + From, + Delay ?? delayHint, + Duration ?? durationHint, + EasingType ?? easingTypeHint ?? DefaultEasingType, + EasingMode ?? easingModeHint ?? DefaultEasingMode); + } + + /// + protected override (Thickness?, Thickness?) GetParsedValues() + { + throw new NotImplementedException(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OffsetAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OffsetAnimation.cs new file mode 100644 index 00000000000..d5e24649ea4 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OffsetAnimation.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An offset animation working on the composition layer. + /// + public sealed class OffsetAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.Offset); + + /// + protected override (Vector3?, Vector3?) GetParsedValues() + { + return (To?.ToVector3(), From?.ToVector3()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OpacityAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OpacityAnimation.cs new file mode 100644 index 00000000000..16d477dd806 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OpacityAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An opacity animation working on the composition or layer. + /// + public sealed class OpacityAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.Opacity); + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OrientationAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OrientationAnimation.cs new file mode 100644 index 00000000000..6b5918971fe --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/OrientationAnimation.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An orientation animation working on the composition layer. + /// + public sealed class OrientationAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.Orientation); + + /// + protected override (Quaternion?, Quaternion?) GetParsedValues() + { + return (To?.ToQuaternion(), From?.ToQuaternion()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationAnimation.cs new file mode 100644 index 00000000000..b542db91b8c --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A rotation animation working on the composition or layer. + /// + public sealed class RotationAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.RotationAngle); + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationAxisAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationAxisAnimation.cs new file mode 100644 index 00000000000..677791da88d --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationAxisAnimation.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A rotation axis animation working on the composition layer. + /// + public sealed class RotationAxisAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.RotationAxis); + + /// + protected override (Vector3?, Vector3?) GetParsedValues() + { + return (To?.ToVector3(), From?.ToVector3()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationInDegreesAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationInDegreesAnimation.cs new file mode 100644 index 00000000000..946b3f318aa --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/RotationInDegreesAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A rotation in degrees animation working on the composition or layer. + /// + public sealed class RotationInDegreesAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.RotationAngleInDegrees); + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/ScaleAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/ScaleAnimation.cs new file mode 100644 index 00000000000..0ec6660364f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/ScaleAnimation.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A scale animation working on the composition or layer. + /// + public sealed class ScaleAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.Scale); + + /// + protected override (Vector3?, Vector3?) GetParsedValues() + { + return (To?.ToVector3(), From?.ToVector3()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/SizeAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/SizeAnimation.cs new file mode 100644 index 00000000000..9643f7670fd --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/SizeAnimation.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A size animation working on the composition or layer. + /// + public sealed class SizeAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => nameof(Visual.Size); + + /// + protected override (Vector2?, Vector2?) GetParsedValues() + { + return (To?.ToVector2(), From?.ToVector2()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/TranslationAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/TranslationAnimation.cs new file mode 100644 index 00000000000..80626f63b7f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Default/TranslationAnimation.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A translation animation working on the composition or layer. + /// + public sealed class TranslationAnimation : ImplicitAnimation + { + /// + protected override string ExplicitTarget => "Translation"; + + /// + protected override (Vector3?, Vector3?) GetParsedValues() + { + return (To?.ToVector3(), From?.ToVector3()); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/ImplicitAnimationSet.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/ImplicitAnimationSet.cs new file mode 100644 index 00000000000..2cbf0da37c2 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/ImplicitAnimationSet.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using System; +using System.Diagnostics.Contracts; +using Microsoft.Toolkit.Diagnostics; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A collection of implicit animations that can be grouped together. This type represents a composite animation + /// (such as ) that is executed on a given element. + /// + public sealed class ImplicitAnimationSet : DependencyObjectCollection + { + /// + /// Gets or sets the weak reference to the parent that owns the current implicit animation collection. + /// + internal WeakReference? ParentReference { get; set; } + + /// + /// Creates a for the current collection. + /// This can be used to be assigned to show/hide implicit composition animations. + /// + /// The instance to use. + [Pure] + internal CompositionAnimationGroup GetCompositionAnimationGroup() + { + UIElement parent = GetParent(); + Compositor compositor = ElementCompositionPreview.GetElementVisual(parent).Compositor; + CompositionAnimationGroup animations = compositor.CreateAnimationGroup(); + + foreach (IImplicitTimeline timeline in this) + { + animations.Add(timeline.GetAnimation(parent, out _)); + } + + return animations; + } + + /// + /// Creates an for the current collection. + /// This can be used to be assigned to implicit composition animations. + /// + /// The instance to use. + [Pure] + internal ImplicitAnimationCollection GetImplicitAnimationCollection() + { + UIElement parent = GetParent(); + Compositor compositor = ElementCompositionPreview.GetElementVisual(parent).Compositor; + ImplicitAnimationCollection animations = compositor.CreateImplicitAnimationCollection(); + + foreach (IImplicitTimeline timeline in this) + { + CompositionAnimation animation = timeline.GetAnimation(parent, out string? target); + + target ??= animation.Target; + + if (!animations.ContainsKey(target)) + { + animations[target] = animations.Compositor.CreateAnimationGroup(); + } + + ((CompositionAnimationGroup)animations[target]).Add(animation); + } + + return animations; + } + + /// + /// Gets the current parent instance. + /// + /// The reference from . + /// Thrown if there is no parent available. + [Pure] + private UIElement GetParent() + { + UIElement? parent = null; + + if (ParentReference?.TryGetTarget(out parent) != true) + { + ThrowHelper.ThrowInvalidOperationException("The current animation collection isn't bound to a parent UIElement instance."); + } + + return parent!; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IActivity.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IActivity.cs new file mode 100644 index 00000000000..7baa9a47d77 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IActivity.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading; +using System.Threading.Tasks; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An interface representing a XAML model for a custom activity or action within an . + /// + public interface IActivity : AnimationSet.INode + { + /// + /// Invokes the current activity. + /// + /// The target to invoke the activity for. + /// A cancellation token to cancel the activity before it completes. + /// A that indicates when the activity has completed its execution. + Task InvokeAsync(UIElement element, CancellationToken token); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IImplicitTimeline.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IImplicitTimeline.cs new file mode 100644 index 00000000000..963e07bd19e --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IImplicitTimeline.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Windows.UI.Composition; +using Windows.UI.Xaml; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An interface representing a XAML model for a custom implicit composition animation. + /// + public interface IImplicitTimeline + { + /// + /// Gets a from the current node. This animation might + /// be used either as an implicit show/hide animation, or as a direct implicit animation. + /// + /// The target the animation will be applied to. + /// + /// The optional target property for the animation. This might be used for direct implicit + /// animations that target a property but want to be triggered according to a separate property. + /// + /// A new instance. + CompositionAnimation GetAnimation(UIElement element, out string? target); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IKeyFrame{T}.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IKeyFrame{T}.cs new file mode 100644 index 00000000000..267d1bc7e17 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/IKeyFrame{T}.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An interface representing a XAML model for a custom keyframe. + /// + /// The type of values for the current keyframe. + public interface IKeyFrame + { + /// + /// Appends the current keyframe to a target instance. + /// + /// The target instance to add the keyframe to. + /// The same instance as . + INormalizedKeyFrameAnimationBuilder AppendToBuilder(INormalizedKeyFrameAnimationBuilder builder); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/ITimeline.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/ITimeline.cs new file mode 100644 index 00000000000..78635101acd --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/Interfaces/ITimeline.cs @@ -0,0 +1,32 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Windows.UI.Xaml.Media.Animation; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An interface representing a XAML model for a custom animation. + /// + public interface ITimeline : AnimationSet.INode + { + /// + /// Appens the current animation to a target instance. + /// This method is used when the current instance is explicitly triggered. + /// + /// The target instance to schedule the animation on. + /// A hint for the animation delay, if present. + /// A hint for the animation duration, if present. + /// A hint for the easing type, if present. + /// A hint for the easing mode, if present. + /// The same instance as . + AnimationBuilder AppendToBuilder( + AnimationBuilder builder, + TimeSpan? delayHint = null, + TimeSpan? durationHint = null, + EasingType? easingTypeHint = null, + EasingMode? easingModeHint = null); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector2KeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/ColorKeyFrame.cs similarity index 51% rename from Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector2KeyFrame.cs rename to Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/ColorKeyFrame.cs index c6286be85ef..4d2f59d1bb7 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/Vector2KeyFrame.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/ColorKeyFrame.cs @@ -2,14 +2,19 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -using System.Numerics; +using Windows.UI; namespace Microsoft.Toolkit.Uwp.UI.Animations { /// - /// of type + /// A type for color animations. /// - public class Vector2KeyFrame : TypedKeyFrame + public sealed class ColorKeyFrame : KeyFrame { + /// + protected override Color GetParsedValue() + { + return Value!.Value; + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/QuaternionKeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/QuaternionKeyFrame.cs new file mode 100644 index 00000000000..97ad030e566 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/QuaternionKeyFrame.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A type for animations. + /// + public sealed class QuaternionKeyFrame : KeyFrame + { + /// + protected override Quaternion GetParsedValue() + { + return Value!.ToQuaternion(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/ScalarKeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/ScalarKeyFrame.cs similarity index 52% rename from Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/ScalarKeyFrame.cs rename to Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/ScalarKeyFrame.cs index b27e05b6523..61fbb8fbfc2 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/CompositionAnimations/KeyFrames/ScalarKeyFrame.cs +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/ScalarKeyFrame.cs @@ -5,9 +5,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Animations { /// - /// of type + /// A type for scalar animations. /// - public class ScalarKeyFrame : TypedKeyFrame + public sealed class ScalarKeyFrame : KeyFrame { + /// + protected override double GetParsedValue() + { + return Value!.Value; + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector2KeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector2KeyFrame.cs new file mode 100644 index 00000000000..dd62fca4577 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector2KeyFrame.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A type for animations. + /// + public sealed class Vector2KeyFrame : KeyFrame + { + /// + protected override Vector2 GetParsedValue() + { + return Value!.ToVector2(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector3KeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector3KeyFrame.cs new file mode 100644 index 00000000000..7db405372ef --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector3KeyFrame.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A type for animations. + /// + public sealed class Vector3KeyFrame : KeyFrame + { + /// + protected override Vector3 GetParsedValue() + { + return Value!.ToVector3(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector4KeyFrame.cs b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector4KeyFrame.cs new file mode 100644 index 00000000000..cba7c1b197b --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Animations/Xaml/KeyFrames/Vector4KeyFrame.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Numerics; +using Microsoft.Toolkit.Uwp.UI.Extensions; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A type for animations. + /// + public sealed class Vector4KeyFrame : KeyFrame + { + /// + protected override Vector4 GetParsedValue() + { + return Value!.ToVector4(); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/AnimationEndBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/AnimationEndBehavior.cs new file mode 100644 index 00000000000..eb0a6442bd9 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/AnimationEndBehavior.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using Microsoft.Toolkit.Uwp.UI.Animations; +using Microsoft.Xaml.Interactivity; + +namespace Microsoft.Toolkit.Uwp.UI.Behaviors +{ + /// + /// A custom that fires whenever a linked ends. + /// + public sealed class AnimationEndBehavior : Trigger + { + /// + /// The current instance in use. + /// + private AnimationSet? animationCollection; + + /// + protected override void OnAttached() + { + base.OnAttached(); + + SetResolvedCollection(AssociatedObject); + } + + /// + protected override void OnDetaching() + { + base.OnDetaching(); + + SetResolvedCollection(null); + } + + /// + /// Sets the current instance in use. + /// + /// The instance in use. + private void SetResolvedCollection(AnimationSet? animationCollection) + { + if (this.animationCollection == animationCollection) + { + return; + } + + if (this.animationCollection is not null) + { + this.animationCollection.Ended -= AnimationCollection_Ended; + } + + this.animationCollection = animationCollection; + + if (animationCollection is not null) + { + animationCollection.Ended += AnimationCollection_Ended; + } + } + + /// + /// Invokes the current actions when the linked animations completes. + /// + /// The source instance. + /// The arguments for the event (unused). + private void AnimationCollection_Ended(object sender, System.EventArgs e) + { + Interaction.ExecuteActions(sender, Actions, e); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/AnimationStartBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/AnimationStartBehavior.cs new file mode 100644 index 00000000000..11c87b9cb52 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/AnimationStartBehavior.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#nullable enable + +using Microsoft.Toolkit.Uwp.UI.Animations; +using Microsoft.Xaml.Interactivity; + +namespace Microsoft.Toolkit.Uwp.UI.Behaviors +{ + /// + /// A custom that fires whenever a linked starts. + /// + public sealed class AnimationStartBehavior : Trigger + { + /// + /// The current instance in use. + /// + private AnimationSet? animationCollection; + + /// + protected override void OnAttached() + { + base.OnAttached(); + + SetResolvedCollection(AssociatedObject); + } + + /// + protected override void OnDetaching() + { + base.OnDetaching(); + + SetResolvedCollection(null); + } + + /// + /// Sets the current instance in use. + /// + /// The instance in use. + private void SetResolvedCollection(AnimationSet? animationCollection) + { + if (this.animationCollection == animationCollection) + { + return; + } + + if (this.animationCollection is not null) + { + this.animationCollection.Started -= AnimationCollection_Started; + } + + this.animationCollection = animationCollection; + + if (animationCollection is not null) + { + animationCollection.Started += AnimationCollection_Started; + } + } + + /// + /// Invokes the current actions when the linked animations starts. + /// + /// The source instance. + /// The arguments for the event (unused). + private void AnimationCollection_Started(object sender, System.EventArgs e) + { + Interaction.ExecuteActions(sender, Actions, e); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/InvokeActionsActivity.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/InvokeActionsActivity.cs new file mode 100644 index 00000000000..82780ce33d6 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/InvokeActionsActivity.cs @@ -0,0 +1,55 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Xaml.Interactivity; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Markup; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// The is an which allows bridging to performing any behavior based within the schedule. + /// + [ContentProperty(Name = nameof(Actions))] + public class InvokeActionsActivity : Activity + { + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty ActionsProperty = DependencyProperty.Register( + nameof(Actions), + typeof(ActionCollection), + typeof(InvokeActionsActivity), + new PropertyMetadata(null)); + + /// + /// Gets the collection of actions associated with the behavior. This is a dependency property. + /// + public ActionCollection Actions + { + get + { + if (GetValue(ActionsProperty) is not ActionCollection actionCollection) + { + actionCollection = new ActionCollection(); + + SetValue(ActionsProperty, actionCollection); + } + + return actionCollection; + } + } + + /// + public override async Task InvokeAsync(UIElement element, CancellationToken token) + { + await base.InvokeAsync(element, token); + + Interaction.ExecuteActions(element, Actions, EventArgs.Empty); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/StartAnimationAction.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/StartAnimationAction.cs new file mode 100644 index 00000000000..aa75926d7fc --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/StartAnimationAction.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Diagnostics; +using Microsoft.Toolkit.Uwp.UI.Animations; +using Microsoft.Xaml.Interactivity; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Behaviors +{ + /// + /// An implementation that can trigger a target instance. + /// + public sealed class StartAnimationAction : DependencyObject, IAction + { + /// + /// Gets or sets the linked instance to invoke. + /// + public AnimationSet Animation + { + get => (AnimationSet)GetValue(AnimationProperty); + set => SetValue(AnimationProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AnimationProperty = DependencyProperty.Register( + nameof(Animation), + typeof(AnimationSet), + typeof(StartAnimationAction), + new PropertyMetadata(null)); + + /// + /// Gets or sets the object to start the specified animation on. If not specified, will use the current object the parent animation is running on. + /// + public UIElement TargetObject + { + get => (UIElement)GetValue(TargetObjectProperty); + set => SetValue(TargetObjectProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register( + nameof(TargetObject), + typeof(UIElement), + typeof(StartAnimationActivity), + new PropertyMetadata(null)); + + /// + public object Execute(object sender, object parameter) + { + Guard.IsNotNull(Animation, nameof(Animation)); + + if (TargetObject is not null) + { + Animation.Start(TargetObject); + } + else + { + Animation.Start(); + } + + return null!; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/StopAnimationAction.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/StopAnimationAction.cs new file mode 100644 index 00000000000..52f889b6bdd --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Animations/StopAnimationAction.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Diagnostics; +using Microsoft.Toolkit.Uwp.UI.Animations; +using Microsoft.Xaml.Interactivity; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Behaviors +{ + /// + /// An implementation that can stop a target instance. + /// + public sealed class StopAnimationAction : DependencyObject, IAction + { + /// + /// Gets or sets the linked instance to stop. + /// + public AnimationSet Animation + { + get => (AnimationSet)GetValue(AnimationProperty); + set => SetValue(AnimationProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty AnimationProperty = DependencyProperty.Register( + nameof(Animation), + typeof(AnimationSet), + typeof(StartAnimationAction), + new PropertyMetadata(null)); + + /// + /// Gets or sets the object to stop the specified animation on. If not specified, will use the current object the parent animation is running on. + /// + public UIElement TargetObject + { + get => (UIElement)GetValue(TargetObjectProperty); + set => SetValue(TargetObjectProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register( + nameof(TargetObject), + typeof(UIElement), + typeof(StartAnimationActivity), + new PropertyMetadata(null)); + + /// + public object Execute(object sender, object parameter) + { + Guard.IsNotNull(Animation, nameof(Animation)); + + if (TargetObject is not null) + { + Animation.Stop(TargetObject); + } + else + { + Animation.Stop(); + } + + return null!; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/ApiInformationHelper.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/ApiInformationHelper.cs similarity index 72% rename from Microsoft.Toolkit.Uwp.UI.Animations/ApiInformationHelper.cs rename to Microsoft.Toolkit.Uwp.UI.Behaviors/ApiInformationHelper.cs index a81b7a5d8fe..a7875948b3b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/ApiInformationHelper.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/ApiInformationHelper.cs @@ -1,13 +1,14 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Windows.Foundation.Metadata; -namespace Microsoft.Toolkit.Uwp.UI.Animations +namespace Microsoft.Toolkit.Uwp.UI.Behaviors { internal class ApiInformationHelper { + // 1903 - 18362 public static bool IsXamlRootAvailable { get; } = ApiInformation.IsPropertyPresent("Windows.UI.Xaml.UIElement", "XamlRoot"); } -} +} \ No newline at end of file diff --git a/Microsoft.Toolkit.Uwp.UI/Behaviors/BehaviorBase.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/BehaviorBase.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI/Behaviors/BehaviorBase.cs rename to Microsoft.Toolkit.Uwp.UI.Behaviors/BehaviorBase.cs diff --git a/Microsoft.Toolkit.Uwp.UI/Behaviors/AutoFocusBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Focus/AutoFocusBehavior.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI/Behaviors/AutoFocusBehavior.cs rename to Microsoft.Toolkit.Uwp.UI.Behaviors/Focus/AutoFocusBehavior.cs diff --git a/Microsoft.Toolkit.Uwp.UI/Behaviors/FocusBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Focus/FocusBehavior.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI/Behaviors/FocusBehavior.cs rename to Microsoft.Toolkit.Uwp.UI.Behaviors/Focus/FocusBehavior.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/FadeHeaderBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/FadeHeaderBehavior.cs similarity index 98% rename from Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/FadeHeaderBehavior.cs rename to Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/FadeHeaderBehavior.cs index c965f783473..231a354515f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/FadeHeaderBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/FadeHeaderBehavior.cs @@ -3,13 +3,13 @@ // See the LICENSE file in the project root for more information. using Microsoft.Toolkit.Uwp.UI.Animations.Expressions; -using Microsoft.Toolkit.Uwp.UI.Behaviors; using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Hosting; -namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors +namespace Microsoft.Toolkit.Uwp.UI.Behaviors { /// /// Performs an fade animation on a ListView or GridView Header using composition. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/QuickReturnHeaderBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/QuickReturnHeaderBehavior.cs similarity index 99% rename from Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/QuickReturnHeaderBehavior.cs rename to Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/QuickReturnHeaderBehavior.cs index fe86fd75bbb..ffe9e518cd5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/QuickReturnHeaderBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/QuickReturnHeaderBehavior.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using Microsoft.Toolkit.Uwp.UI.Animations.Expressions; -using Microsoft.Toolkit.Uwp.UI.Behaviors; using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.Foundation; using Windows.UI.Composition; @@ -12,7 +11,7 @@ using Windows.UI.Xaml.Hosting; using Windows.UI.Xaml.Input; -namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors +namespace Microsoft.Toolkit.Uwp.UI.Behaviors { /// /// Performs an animation on a ListView or GridView Header to make it quick return using composition. diff --git a/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/StickyHeaderBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/StickyHeaderBehavior.cs similarity index 98% rename from Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/StickyHeaderBehavior.cs rename to Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/StickyHeaderBehavior.cs index ec322bf6eb3..5ca0fbf764e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Animations/Behaviors/StickyHeaderBehavior.cs +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Headers/StickyHeaderBehavior.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using Microsoft.Toolkit.Uwp.UI.Animations.Expressions; -using Microsoft.Toolkit.Uwp.UI.Behaviors; using Microsoft.Toolkit.Uwp.UI.Extensions; using Windows.Foundation; using Windows.UI.Composition; @@ -13,7 +12,7 @@ using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; -namespace Microsoft.Toolkit.Uwp.UI.Animations.Behaviors +namespace Microsoft.Toolkit.Uwp.UI.Behaviors { /// /// Performs an animation on a ListView or GridView Header to make it sticky using composition. diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Microsoft.Toolkit.Uwp.UI.Behaviors.csproj b/Microsoft.Toolkit.Uwp.UI.Behaviors/Microsoft.Toolkit.Uwp.UI.Behaviors.csproj new file mode 100644 index 00000000000..6ed65c4d436 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Microsoft.Toolkit.Uwp.UI.Behaviors.csproj @@ -0,0 +1,31 @@ + + + + uap10.0.17763 + 9.0 + Windows Community Toolkit UI Behaviors + + This library provides UI behaviors built on the XAML behaviors SDK. It is part of the Windows Community Toolkit. + + Behaviors: + - BehaviorBase: Helper for building Behaviors + - AutoFocusBehevior: Sets focus to the associated control. + - FocusBehavior: Sets focus to a specified control. + - ViewportBehavior: Listening for element to enter or exit the ScrollViewer viewport + - FadeHeaderBehavior, QuickReturnHeaderBehavior, StickyHeaderBehavior: Helpers for ListViewBase Header Behavior + + UWP Toolkit Windows UI Behaviors XAML + + true + + + + + + + + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Behaviors/Properties/Microsoft.Toolkit.Uwp.UI.Behaviors.rd.xml b/Microsoft.Toolkit.Uwp.UI.Behaviors/Properties/Microsoft.Toolkit.Uwp.UI.Behaviors.rd.xml new file mode 100644 index 00000000000..2a652962f91 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Behaviors/Properties/Microsoft.Toolkit.Uwp.UI.Behaviors.rd.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/Microsoft.Toolkit.Uwp.UI/Behaviors/ViewportBehavior.cs b/Microsoft.Toolkit.Uwp.UI.Behaviors/Viewport/ViewportBehavior.cs similarity index 100% rename from Microsoft.Toolkit.Uwp.UI/Behaviors/ViewportBehavior.cs rename to Microsoft.Toolkit.Uwp.UI.Behaviors/Viewport/ViewportBehavior.cs diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj index 793d9571766..10577dbb847 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Microsoft.Toolkit.Uwp.UI.Controls.csproj @@ -31,7 +31,6 @@ - RangeSelector: "Double slider" control for range values. - RemoteDevicePicker: Remote Device Picker Control for Project Rome. - RotatorTile: Rotates through a set of items one-by-one like a live-tile. - - ScrollHeader: A UI control that works as a ListView or GridView header control with quick return, sticky, and fade behavior. - StaggeredPanel: Layout of items in a column approach where an item will be added to whichever column has used the least amount of space. - TextToolbar: A Toolbar for Editing Text attached to a RichEditBox. It can format RTF, Markdown, or use a Custom Formatter. - TileControl: A ContentControl that show an image repeated many times. @@ -48,6 +47,7 @@ + diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeader.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeader.cs deleted file mode 100644 index 25fc85fbd06..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeader.cs +++ /dev/null @@ -1,96 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -using System; -using Microsoft.Toolkit.Uwp.UI.Animations.Behaviors; -using Microsoft.Toolkit.Uwp.UI.Extensions; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml; -using Windows.UI.Xaml.Controls; - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Scroll header control to be used with ListViews or GridViews - /// - public partial class ScrollHeader : ContentControl - { - /// - /// Initializes a new instance of the class. - /// - public ScrollHeader() - { - DefaultStyleKey = typeof(ScrollHeader); - HorizontalContentAlignment = HorizontalAlignment.Stretch; - } - - /// - /// Identifies the property. - /// - public static readonly DependencyProperty ModeProperty = - DependencyProperty.Register(nameof(Mode), typeof(ScrollHeaderMode), typeof(ScrollHeader), new PropertyMetadata(ScrollHeaderMode.None, OnModeChanged)); - - /// - /// Gets or sets a value indicating whether the current mode. - /// Default is none. - /// - public ScrollHeaderMode Mode - { - get { return (ScrollHeaderMode)GetValue(ModeProperty); } - set { SetValue(ModeProperty, value); } - } - - /// - /// Invoked whenever application code or internal processes (such as a rebuilding layout pass) call . - /// - protected override void OnApplyTemplate() - { - UpdateScrollHeaderBehavior(); - } - - private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - (d as ScrollHeader)?.UpdateScrollHeaderBehavior(); - } - - private static void OnTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - (d as ScrollHeader)?.OnApplyTemplate(); - } - - private void UpdateScrollHeaderBehavior() - { - var targetListViewBase = this.FindAscendant(); - - if (targetListViewBase == null) - { - return; - } - - // Remove previous behaviors - foreach (var behavior in Interaction.GetBehaviors(targetListViewBase)) - { - if (behavior is FadeHeaderBehavior || behavior is QuickReturnHeaderBehavior || behavior is StickyHeaderBehavior) - { - Interaction.GetBehaviors(targetListViewBase).Remove(behavior); - } - } - - switch (Mode) - { - case ScrollHeaderMode.None: - break; - case ScrollHeaderMode.QuickReturn: - Interaction.GetBehaviors(targetListViewBase).Add(new QuickReturnHeaderBehavior()); - break; - case ScrollHeaderMode.Sticky: - Interaction.GetBehaviors(targetListViewBase).Add(new StickyHeaderBehavior()); - break; - case ScrollHeaderMode.Fade: - Interaction.GetBehaviors(targetListViewBase).Add(new FadeHeaderBehavior()); - break; - } - } - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeader.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeader.xaml deleted file mode 100644 index 2521843c6f5..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeader.xaml +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeaderMode.cs b/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeaderMode.cs deleted file mode 100644 index 5774585a90d..00000000000 --- a/Microsoft.Toolkit.Uwp.UI.Controls/ScrollHeader/ScrollHeaderMode.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Toolkit.Uwp.UI.Controls -{ - /// - /// Define mode available for - /// - public enum ScrollHeaderMode - { - /// - /// No change. This is the default value. - /// - None, - - /// - /// Enable quick return mode. - /// - QuickReturn, - - /// - /// Enable sticky mode. - /// - Sticky, - - /// - /// Enable fade mode. - /// - Fade - } -} diff --git a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml index 6422f8690d2..a6c650d3a4b 100644 --- a/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml +++ b/Microsoft.Toolkit.Uwp.UI.Controls/Themes/Generic.xaml @@ -27,7 +27,6 @@ - diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/Abstract/EffectAnimation{TEffect,TValue,TKeyFrame}.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/Abstract/EffectAnimation{TEffect,TValue,TKeyFrame}.cs new file mode 100644 index 00000000000..b2f2f697824 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/Abstract/EffectAnimation{TEffect,TValue,TKeyFrame}.cs @@ -0,0 +1,76 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using Microsoft.Toolkit.Diagnostics; +using Microsoft.Toolkit.Uwp.UI.Media; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Media.Animation; +using static Microsoft.Toolkit.Uwp.UI.Animations.AnimationExtensions; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// A custom animation targeting a property on an instance. + /// + /// The type of effect to animate. + /// + /// The type to use for the public and + /// properties. This can differ from to facilitate XAML parsing. + /// + /// The actual type of keyframe values in use. + public abstract class EffectAnimation : Animation + where TEffect : class, IPipelineEffect + where TKeyFrame : unmanaged + { + /// + /// Gets or sets the linked instance to animate. + /// + public TEffect? Target + { + get => (TEffect?)GetValue(TargetProperty); + set => SetValue(TargetProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty TargetProperty = DependencyProperty.Register( + nameof(Target), + typeof(TEffect), + typeof(EffectAnimation), + new PropertyMetadata(null)); + + /// + public override AnimationBuilder AppendToBuilder(AnimationBuilder builder, TimeSpan? delayHint, TimeSpan? durationHint, EasingType? easingTypeHint, EasingMode? easingModeHint) + { + if (Target is not TEffect target) + { + return ThrowHelper.ThrowArgumentNullException("The target effect is null, make sure to set the Target property"); + } + + if (ExplicitTarget is not string explicitTarget) + { + return ThrowHelper.ThrowArgumentNullException( + "The target effect cannot be animated at this time. If you're targeting one of the " + + "built-in effects, make sure that the PipelineEffect.IsAnimatable property is set to true."); + } + + NormalizedKeyFrameAnimationBuilder.Composition keyFrameBuilder = new( + explicitTarget, + Delay ?? delayHint ?? DefaultDelay, + Duration ?? durationHint ?? DefaultDuration, + Repeat); + + AppendToBuilder(keyFrameBuilder, easingTypeHint, easingModeHint); + + CompositionAnimation animation = keyFrameBuilder.GetAnimation(target.Brush!, out _); + + return builder.ExternalAnimation(target.Brush, animation); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/BlurEffectAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/BlurEffectAnimation.cs new file mode 100644 index 00000000000..d10f32ab71f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/BlurEffectAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An effect animation that targets . + /// + public sealed class BlurEffectAnimation : EffectAnimation + { + /// + protected override string ExplicitTarget => Target.Id; + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/ColorEffectAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/ColorEffectAnimation.cs new file mode 100644 index 00000000000..242c574eb82 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/ColorEffectAnimation.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media; +using Windows.UI; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An effect animation that targets . + /// + public sealed class ColorEffectAnimation : EffectAnimation + { + /// + protected override string ExplicitTarget => Target.Id; + + /// + protected override (Color?, Color?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/CrossFadeEffectAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/CrossFadeEffectAnimation.cs new file mode 100644 index 00000000000..469c2b95eab --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/CrossFadeEffectAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An effect animation that targets . + /// + public sealed class CrossFadeEffectAnimation : EffectAnimation + { + /// + protected override string ExplicitTarget => Target.Id; + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/ExposureEffectAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/ExposureEffectAnimation.cs new file mode 100644 index 00000000000..ea500b24282 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/ExposureEffectAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An effect animation that targets . + /// + public sealed class ExposureEffectAnimation : EffectAnimation + { + /// + protected override string ExplicitTarget => Target.Id; + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/HueRotationEffectAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/HueRotationEffectAnimation.cs new file mode 100644 index 00000000000..a0b0e8dc40f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/HueRotationEffectAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An effect animation that targets . + /// + public sealed class HueRotationEffectAnimation : EffectAnimation + { + /// + protected override string ExplicitTarget => Target.Id; + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/OpacityEffectAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/OpacityEffectAnimation.cs new file mode 100644 index 00000000000..9a8f6509489 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/OpacityEffectAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An effect animation that targets . + /// + public sealed class OpacityEffectAnimation : EffectAnimation + { + /// + protected override string ExplicitTarget => Target.Id; + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/SaturationEffectAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/SaturationEffectAnimation.cs new file mode 100644 index 00000000000..14570b9cb39 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/SaturationEffectAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An effect animation that targets . + /// + public sealed class SaturationEffectAnimation : EffectAnimation + { + /// + protected override string ExplicitTarget => Target.Id; + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Animations/SepiaEffectAnimation.cs b/Microsoft.Toolkit.Uwp.UI.Media/Animations/SepiaEffectAnimation.cs new file mode 100644 index 00000000000..5c2f5e79b63 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Animations/SepiaEffectAnimation.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Animations +{ + /// + /// An effect animation that targets . + /// + public sealed class SepiaEffectAnimation : EffectAnimation + { + /// + protected override string ExplicitTarget => Target.Id; + + /// + protected override (double?, double?) GetParsedValues() + { + return (To, From); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AcrylicBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AcrylicBrush.cs index 30c67aa3a37..7d4df4e20a0 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AcrylicBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/AcrylicBrush.cs @@ -197,7 +197,7 @@ private static void OnTextureUriPropertyChanged(DependencyObject d, DependencyPr } /// - protected override PipelineBuilder OnBrushRequested() + protected override PipelineBuilder OnPipelineRequested() { switch (BackgroundSource) { diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs index 129fa289610..434eeada2b4 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropBlurBrush.cs @@ -54,7 +54,7 @@ private static void OnAmountChanged(DependencyObject d, DependencyPropertyChange } /// - protected override PipelineBuilder OnBrushRequested() + protected override PipelineBuilder OnPipelineRequested() { return PipelineBuilder.FromBackdrop().Blur((float)Amount, out this.amountSetter); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs index c225fa42fad..7bf161d6551 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropInvertBrush.cs @@ -15,7 +15,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Media public class BackdropInvertBrush : XamlCompositionEffectBrushBase { /// - protected override PipelineBuilder OnBrushRequested() + protected override PipelineBuilder OnPipelineRequested() { return PipelineBuilder.FromBackdrop().Invert(); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs index 699ebe04849..c055b20982a 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSaturationBrush.cs @@ -64,7 +64,7 @@ private static void OnSaturationChanged(DependencyObject d, DependencyPropertyCh } /// - protected override PipelineBuilder OnBrushRequested() + protected override PipelineBuilder OnPipelineRequested() { return PipelineBuilder.FromBackdrop().Saturation((float)Saturation, out setter); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs index 965d34de9ee..b7a36dcd514 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/BackdropSepiaBrush.cs @@ -64,7 +64,7 @@ private static void OnIntensityChanged(DependencyObject d, DependencyPropertyCha } /// - protected override PipelineBuilder OnBrushRequested() + protected override PipelineBuilder OnPipelineRequested() { return PipelineBuilder.FromBackdrop().Sepia((float)Intensity, out setter); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/Base/XamlCompositionEffectBrushBase.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/Base/XamlCompositionEffectBrushBase.cs index d9a4396e794..f92d3d16c6e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/Base/XamlCompositionEffectBrushBase.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/Base/XamlCompositionEffectBrushBase.cs @@ -10,30 +10,31 @@ namespace Microsoft.Toolkit.Uwp.UI.Media { /// - /// A custom that's ready to be used with a custom pipeline + /// A custom that's ready to be used with a custom pipeline. /// public abstract class XamlCompositionEffectBrushBase : XamlCompositionBrushBase { /// - /// The initialization instance + /// The initialization instance. /// private readonly AsyncMutex connectedMutex = new AsyncMutex(); /// /// A method that builds and returns the pipeline to use in the current instance. - /// This method can also be used to store any needed or instances in local fields, for later use (they will need to be called upon ). + /// This method can also be used to store any needed or + /// instances in local fields, for later use (they will need to be called upon ). /// - /// A instance to create the brush to display - protected abstract PipelineBuilder OnBrushRequested(); + /// A instance to create the brush to display. + protected abstract PipelineBuilder OnPipelineRequested(); - private bool _isEnabled = true; + private bool isEnabled = true; /// - /// Gets or sets a value indicating whether the current brush is using the provided pipeline, or the fallback color + /// Gets or sets a value indicating whether the current brush is using the provided pipeline, or the fallback color. /// public bool IsEnabled { - get => this._isEnabled; + get => this.isEnabled; set => this.OnEnabledToggled(value); } @@ -42,7 +43,7 @@ protected override async void OnConnected() { using (await this.connectedMutex.LockAsync()) { - if (this.CompositionBrush == null) + if (CompositionBrush == null) { // Abort if effects aren't supported if (!CompositionCapabilities.GetForCurrentView().AreEffectsSupported()) @@ -50,14 +51,16 @@ protected override async void OnConnected() return; } - if (this._isEnabled) + if (this.isEnabled) { - this.CompositionBrush = await this.OnBrushRequested().BuildAsync(); + CompositionBrush = await OnPipelineRequested().BuildAsync(); } else { - this.CompositionBrush = await PipelineBuilder.FromColor(this.FallbackColor).BuildAsync(); + CompositionBrush = await PipelineBuilder.FromColor(FallbackColor).BuildAsync(); } + + OnCompositionBrushUpdated(); } } @@ -69,10 +72,12 @@ protected override async void OnDisconnected() { using (await this.connectedMutex.LockAsync()) { - if (this.CompositionBrush != null) + if (CompositionBrush != null) { - this.CompositionBrush.Dispose(); - this.CompositionBrush = null; + CompositionBrush.Dispose(); + CompositionBrush = null; + + OnCompositionBrushUpdated(); } } @@ -80,21 +85,21 @@ protected override async void OnDisconnected() } /// - /// Updates the property depending on the input value + /// Updates the property depending on the input value. /// - /// The new value being set to the property + /// The new value being set to the property. protected async void OnEnabledToggled(bool value) { using (await this.connectedMutex.LockAsync()) { - if (this._isEnabled == value) + if (this.isEnabled == value) { return; } - this._isEnabled = value; + this.isEnabled = value; - if (this.CompositionBrush != null) + if (CompositionBrush != null) { // Abort if effects aren't supported if (!CompositionCapabilities.GetForCurrentView().AreEffectsSupported()) @@ -102,16 +107,25 @@ protected async void OnEnabledToggled(bool value) return; } - if (this._isEnabled) + if (this.isEnabled) { - this.CompositionBrush = await this.OnBrushRequested().BuildAsync(); + CompositionBrush = await OnPipelineRequested().BuildAsync(); } else { - this.CompositionBrush = await PipelineBuilder.FromColor(this.FallbackColor).BuildAsync(); + CompositionBrush = await PipelineBuilder.FromColor(FallbackColor).BuildAsync(); } + + OnCompositionBrushUpdated(); } } } + + /// + /// Invoked whenever the property is updated. + /// + protected virtual void OnCompositionBrushUpdated() + { + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs index 4bfa1dcd9d7..b2ad1036b97 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/ImageBlendBrush.cs @@ -10,6 +10,7 @@ using Windows.UI.Xaml; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Imaging; +using CanvasBlendEffect = Microsoft.Graphics.Canvas.Effects.BlendEffect; namespace Microsoft.Toolkit.Uwp.UI.Media { @@ -144,7 +145,7 @@ protected override void OnConnected() var backdrop = Window.Current.Compositor.CreateBackdropBrush(); // Use a Win2D invert affect applied to a CompositionBackdropBrush. - var graphicsEffect = new BlendEffect + var graphicsEffect = new CanvasBlendEffect { Name = "Invert", Mode = (BlendEffectMode)(int)Mode, diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs index cf8916b7da5..44c37fa38f6 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/PipelineBrush.cs @@ -3,8 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; -using Microsoft.Toolkit.Uwp.UI.Media.Effects; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Xaml; using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; @@ -17,26 +17,58 @@ namespace Microsoft.Toolkit.Uwp.UI.Media public sealed class PipelineBrush : XamlCompositionEffectBrushBase { /// - /// Gets or sets the source for the current pipeline (defaults to a with source). + /// Gets or sets the source for the current pipeline (defaults to a with source). /// public PipelineBuilder Source { get; set; } /// - /// Gets or sets the collection of effects to use in the current pipeline + /// Gets or sets the collection of effects to use in the current pipeline. /// - public IList Effects { get; set; } = new List(); + public IList Effects + { + get + { + if (GetValue(EffectsProperty) is not IList effects) + { + effects = new List(); + + SetValue(EffectsProperty, effects); + } + + return effects; + } + set => SetValue(EffectsProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EffectsProperty = DependencyProperty.Register( + nameof(Effects), + typeof(IList), + typeof(PipelineBrush), + new PropertyMetadata(null)); /// - protected override PipelineBuilder OnBrushRequested() + protected override PipelineBuilder OnPipelineRequested() { PipelineBuilder builder = Source ?? PipelineBuilder.FromBackdrop(); foreach (IPipelineEffect effect in Effects) { - builder = effect.AppendToPipeline(builder); + builder = effect.AppendToBuilder(builder); } return builder; } + + /// + protected override void OnCompositionBrushUpdated() + { + foreach (IPipelineEffect effect in Effects) + { + effect.NotifyCompositionBrushInUse(CompositionBrush); + } + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/TilesBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/TilesBrush.cs index 3d75b4f3c53..50d96d3cac6 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/TilesBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/TilesBrush.cs @@ -65,7 +65,7 @@ private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPr } /// - protected override PipelineBuilder OnBrushRequested() + protected override PipelineBuilder OnPipelineRequested() { if (TextureUri is Uri uri) { diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/XamlCompositionBrush.cs b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/XamlCompositionBrush.cs index 44f1b527f23..220a0261b77 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Brushes/XamlCompositionBrush.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Brushes/XamlCompositionBrush.cs @@ -76,7 +76,7 @@ public XamlCompositionBrush Bind(EffectAnimation animation, out XamlEffect } /// - protected override PipelineBuilder OnBrushRequested() => this.Pipeline; + protected override PipelineBuilder OnPipelineRequested() => this.Pipeline; /// /// Clones the current instance by rebuilding the source . Use this method to reuse the same effects pipeline on a different diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ImageSourceBaseExtension.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ImageSourceBaseExtension.cs index 6117203665a..b5048ffdef1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ImageSourceBaseExtension.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/ImageSourceBaseExtension.cs @@ -6,7 +6,7 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; using Windows.UI.Xaml.Markup; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// An image based effect that loads an image at the specified location diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/PipelineEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/PipelineEffect.cs new file mode 100644 index 00000000000..3d3639f3223 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Abstract/PipelineEffect.cs @@ -0,0 +1,38 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Composition; +using Windows.UI.Xaml; + +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// A base pipeline effect. + /// + public abstract class PipelineEffect : DependencyObject, IPipelineEffect + { + /// + public CompositionBrush? Brush { get; private set; } + + /// + /// Gets or sets a value indicating whether the effect can be animated. + /// + public bool IsAnimatable { get; set; } + + /// + public abstract PipelineBuilder AppendToBuilder(PipelineBuilder builder); + + /// + public virtual void NotifyCompositionBrushInUse(CompositionBrush brush) + { + Brush = brush; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlendEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlendEffect.cs index ce282c29ced..2c8c9ba8114 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlendEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlendEffect.cs @@ -5,21 +5,24 @@ using System.Collections.Generic; using Microsoft.Graphics.Canvas.Effects; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Composition; using Windows.UI.Xaml.Markup; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A blend effect that merges the current builder with an input one /// /// This effect maps to the Win2D effect [ContentProperty(Name = nameof(Effects))] - public sealed class BlendEffect : IPipelineEffect + public sealed class BlendEffect : PipelineEffect { /// /// Gets or sets the input to merge with the current instance (defaults to a with source). /// - public PipelineBuilder Source { get; set; } + public PipelineBuilder? Source { get; set; } /// /// Gets or sets the effects to apply to the input to merge with the current instance @@ -37,16 +40,27 @@ public sealed class BlendEffect : IPipelineEffect public Placement Placement { get; set; } = Placement.Foreground; /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { PipelineBuilder inputBuilder = Source ?? PipelineBuilder.FromBackdrop(); - foreach (IPipelineEffect effect in this.Effects) + foreach (IPipelineEffect effect in Effects) { - inputBuilder = effect.AppendToPipeline(inputBuilder); + inputBuilder = effect.AppendToBuilder(inputBuilder); } return builder.Blend(inputBuilder, (BlendEffectMode)Mode, Placement); } + + /// + public override void NotifyCompositionBrushInUse(CompositionBrush brush) + { + base.NotifyCompositionBrushInUse(brush); + + foreach (IPipelineEffect effect in Effects) + { + effect.NotifyCompositionBrushInUse(brush); + } + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlurEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlurEffect.cs index b1e6c9437c2..3e32cfca43e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlurEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/BlurEffect.cs @@ -5,13 +5,15 @@ using System; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A gaussian blur effect /// /// This effect maps to the Win2D effect - public sealed class BlurEffect : IPipelineEffect + public sealed class BlurEffect : PipelineEffect { private double amount; @@ -24,9 +26,23 @@ public double Amount set => this.amount = Math.Max(value, 0); } + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { + if (IsAnimatable) + { + builder = builder.Blur((float)Amount, out string id); + + Id = id; + + return builder; + } + return builder.Blur((float)Amount); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/CrossFadeEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/CrossFadeEffect.cs index 8bd4d183f55..5b39dc90692 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/CrossFadeEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/CrossFadeEffect.cs @@ -5,21 +5,24 @@ using System; using System.Collections.Generic; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Composition; using Windows.UI.Xaml.Markup; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A blend effect that merges the current builder with an input one /// /// This effect maps to the Win2D effect [ContentProperty(Name = nameof(Effects))] - public sealed class CrossFadeEffect : IPipelineEffect + public sealed class CrossFadeEffect : PipelineEffect { /// /// Gets or sets the input to merge with the current instance (defaults to a with source). /// - public PipelineBuilder Source { get; set; } + public PipelineBuilder? Source { get; set; } /// /// Gets or sets the effects to apply to the input to merge with the current instance @@ -37,17 +40,42 @@ public double Factor set => this.factor = Math.Clamp(value, 0, 1); } + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { PipelineBuilder inputBuilder = Source ?? PipelineBuilder.FromBackdrop(); - foreach (IPipelineEffect effect in this.Effects) + foreach (IPipelineEffect effect in Effects) { - inputBuilder = effect.AppendToPipeline(inputBuilder); + inputBuilder = effect.AppendToBuilder(inputBuilder); + } + + if (IsAnimatable) + { + builder = builder.CrossFade(inputBuilder, (float)Factor, out string id); + + Id = id; + + return builder; } return builder.CrossFade(inputBuilder, (float)Factor); } + + /// + public override void NotifyCompositionBrushInUse(CompositionBrush brush) + { + base.NotifyCompositionBrushInUse(brush); + + foreach (IPipelineEffect effect in Effects) + { + effect.NotifyCompositionBrushInUse(brush); + } + } } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/ExposureEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ExposureEffect.cs index eeb6a708f8c..fe31072abb8 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/ExposureEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ExposureEffect.cs @@ -5,13 +5,15 @@ using System; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// An exposure effect /// /// This effect maps to the Win2D effect - public sealed class ExposureEffect : IPipelineEffect + public sealed class ExposureEffect : PipelineEffect { private double amount; @@ -24,9 +26,23 @@ public double Amount set => this.amount = Math.Clamp(value, -2, 2); } + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { + if (IsAnimatable) + { + builder = builder.Exposure((float)Amount, out string id); + + Id = id; + + return builder; + } + return builder.Exposure((float)Amount); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/AcrylicSourceExtension.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/AcrylicSourceExtension.cs index ce9f0fd7f4f..004547cc7d9 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/AcrylicSourceExtension.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/AcrylicSourceExtension.cs @@ -8,7 +8,7 @@ using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A custom acrylic effect that can be inserted into a pipeline diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/BackdropSourceExtension.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/BackdropSourceExtension.cs index 7ae6824c4aa..4208cab6d4f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/BackdropSourceExtension.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/BackdropSourceExtension.cs @@ -7,7 +7,7 @@ using Windows.UI.Xaml.Markup; using Windows.UI.Xaml.Media; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A backdrop effect that can sample from a specified source diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/ImageSourceExtension.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/ImageSourceExtension.cs index 769aa67c88f..cd5479fbb36 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/ImageSourceExtension.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/ImageSourceExtension.cs @@ -4,7 +4,7 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// An image effect, which displays an image loaded as a Win2D surface diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/SolidColorSourceExtension.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/SolidColorSourceExtension.cs index 598e976ddd6..8d39c26cbce 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/SolidColorSourceExtension.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/SolidColorSourceExtension.cs @@ -6,7 +6,7 @@ using Windows.UI; using Windows.UI.Xaml.Markup; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// An effect that renders a standard 8bit SDR color on the available surface diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/TileSourceExtension.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/TileSourceExtension.cs index f45b7ba1b73..52357d7b59f 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/TileSourceExtension.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Extensions/TileSourceExtension.cs @@ -4,7 +4,7 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// An effect that loads an image and replicates it to cover all the available surface area diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/GrayscaleEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/GrayscaleEffect.cs index 5e2070d148b..525f11d5b9e 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/GrayscaleEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/GrayscaleEffect.cs @@ -4,16 +4,16 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A grayscale effect /// /// This effect maps to the Win2D effect - public sealed class GrayscaleEffect : IPipelineEffect + public sealed class GrayscaleEffect : PipelineEffect { /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { return builder.Grayscale(); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/HueRotationEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/HueRotationEffect.cs index bf76744f3b4..1c0c5408071 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/HueRotationEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/HueRotationEffect.cs @@ -4,22 +4,38 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A hue rotation effect /// /// This effect maps to the Win2D effect - public sealed class HueRotationEffect : IPipelineEffect + public sealed class HueRotationEffect : PipelineEffect { /// /// Gets or sets the angle to rotate the hue, in radians /// public double Angle { get; set; } + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { + if (IsAnimatable) + { + builder = builder.HueRotation((float)Angle, out string id); + + Id = id; + + return builder; + } + return builder.HueRotation((float)Angle); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Interfaces/IPipelineEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Interfaces/IPipelineEffect.cs index 24a867442c3..c4fa00040ed 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/Interfaces/IPipelineEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/Interfaces/IPipelineEffect.cs @@ -3,19 +3,33 @@ // See the LICENSE file in the project root for more information. using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Composition; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// - /// The base for all the builder effects to be used in a + /// The base for all the builder effects to be used in a . /// public interface IPipelineEffect { + /// + /// Gets the current instance, if one is in use. + /// + CompositionBrush? Brush { get; } + /// /// Appends the current effect to the input instance. /// /// The source instance to add the effect to. /// A new with the new effects added to it. - PipelineBuilder AppendToPipeline(PipelineBuilder builder); + PipelineBuilder AppendToBuilder(PipelineBuilder builder); + + /// + /// Notifies that a given is now in use. + /// + /// The in use. + void NotifyCompositionBrushInUse(CompositionBrush brush); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/InvertEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/InvertEffect.cs index 04c0fcd9bb8..ee503116bd1 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/InvertEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/InvertEffect.cs @@ -4,16 +4,16 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// An color inversion effect /// /// This effect maps to the Win2D effect - public sealed class InvertEffect : IPipelineEffect + public sealed class InvertEffect : PipelineEffect { /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { return builder.Invert(); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/LuminanceToAlphaEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/LuminanceToAlphaEffect.cs index 527b55840e0..efea25af216 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/LuminanceToAlphaEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/LuminanceToAlphaEffect.cs @@ -4,16 +4,16 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A luminance to alpha effect /// /// This effect maps to the Win2D effect - public sealed class LuminanceToAlphaEffect : IPipelineEffect + public sealed class LuminanceToAlphaEffect : PipelineEffect { /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { return builder.LuminanceToAlpha(); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/OpacityEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/OpacityEffect.cs index 82b8ea33c56..752feb79f3c 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/OpacityEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/OpacityEffect.cs @@ -5,13 +5,15 @@ using System; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// An opacity effect /// /// This effect maps to the Win2D effect - public sealed class OpacityEffect : IPipelineEffect + public sealed class OpacityEffect : PipelineEffect { private double value = 1; @@ -24,9 +26,23 @@ public double Value set => this.value = Math.Clamp(value, 0, 1); } + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { + if (IsAnimatable) + { + builder = builder.Opacity((float)Value, out string id); + + Id = id; + + return builder; + } + return builder.Opacity((float)Value); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/SaturationEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SaturationEffect.cs index cce6eee390f..8da67ac9912 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/SaturationEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SaturationEffect.cs @@ -5,13 +5,15 @@ using System; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A saturation effect /// /// This effect maps to the Win2D effect - public sealed class SaturationEffect : IPipelineEffect + public sealed class SaturationEffect : PipelineEffect { private double value = 1; @@ -24,9 +26,23 @@ public double Value set => this.value = Math.Clamp(value, 0, 1); } + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { + if (IsAnimatable) + { + builder = builder.Saturation((float)Value, out string id); + + Id = id; + + return builder; + } + return builder.Saturation((float)Value); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/SepiaEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SepiaEffect.cs index 8e3039d0980..ce6a4d556ef 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/SepiaEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/SepiaEffect.cs @@ -5,13 +5,15 @@ using System; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A sepia effect /// /// This effect maps to the Win2D effect - public sealed class SepiaEffect : IPipelineEffect + public sealed class SepiaEffect : PipelineEffect { private double intensity = 0.5; @@ -24,9 +26,23 @@ public double Intensity set => this.intensity = Math.Clamp(value, 0, 1); } + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { + if (IsAnimatable) + { + builder = builder.Sepia((float)Intensity, out string id); + + Id = id; + + return builder; + } + return builder.Sepia((float)Intensity); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/ShadeEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ShadeEffect.cs index 4b6e0debe2d..c0f72367dad 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/ShadeEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/ShadeEffect.cs @@ -6,12 +6,12 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; using Windows.UI; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// An effect that overlays a color layer over the current builder, with a specified intensity /// - public sealed class ShadeEffect : IPipelineEffect + public sealed class ShadeEffect : PipelineEffect { /// /// Gets or sets the color to use @@ -30,7 +30,7 @@ public double Intensity } /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { return builder.Shade(Color, (float)Intensity); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/TemperatureAndTintEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TemperatureAndTintEffect.cs index 2c31cbe1c65..9193aa0f1cd 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/TemperatureAndTintEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TemperatureAndTintEffect.cs @@ -5,13 +5,13 @@ using System; using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A temperature and tint effect /// /// This effect maps to the Win2D effect - public sealed class TemperatureAndTintEffect : IPipelineEffect + public sealed class TemperatureAndTintEffect : PipelineEffect { private double temperature; @@ -36,7 +36,7 @@ public double Tint } /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { return builder.TemperatureAndTint((float)Temperature, (float)Tint); } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Effects/TintEffect.cs b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TintEffect.cs index 11994b21182..7555391a982 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Effects/TintEffect.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Effects/TintEffect.cs @@ -5,22 +5,38 @@ using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; using Windows.UI; -namespace Microsoft.Toolkit.Uwp.UI.Media.Effects +#nullable enable + +namespace Microsoft.Toolkit.Uwp.UI.Media { /// /// A tint effect /// /// This effect maps to the Win2D effect - public sealed class TintEffect : IPipelineEffect + public sealed class TintEffect : PipelineEffect { /// /// Gets or sets the int color to use /// public Color Color { get; set; } + /// + /// Gets the unique id for the effect, if is set. + /// + internal string? Id { get; private set; } + /// - public PipelineBuilder AppendToPipeline(PipelineBuilder builder) + public override PipelineBuilder AppendToBuilder(PipelineBuilder builder) { + if (IsAnimatable) + { + builder = builder.Tint(Color, out string id); + + Id = id; + + return builder; + } + return builder.Tint(Color); } } diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Extensions/UIElementExtensions.cs b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/UIElementExtensions.cs new file mode 100644 index 00000000000..bd0c7bc9a35 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Extensions/UIElementExtensions.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Toolkit.Uwp.UI.Media.Extensions; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// Attached properties to support attaching custom pipelines to UI elements. + /// + public static class UIElementExtensions + { + /// + /// Identifies the VisualFactory XAML attached property. + /// + public static readonly DependencyProperty VisualFactoryProperty = DependencyProperty.RegisterAttached( + "VisualFactory", + typeof(AttachedVisualFactoryBase), + typeof(UIElementExtensions), + new PropertyMetadata(null, OnVisualFactoryPropertyChanged)); + + /// + /// Gets the value of . + /// + /// The to get the value for. + /// The retrieved item. + public static AttachedVisualFactoryBase GetVisualFactory(UIElement element) + { + return (AttachedVisualFactoryBase)element.GetValue(VisualFactoryProperty); + } + + /// + /// Sets the value of . + /// + /// The to set the value for. + /// The value to set. + public static void SetVisualFactory(UIElement element, AttachedVisualFactoryBase value) + { + element.SetValue(VisualFactoryProperty, value); + } + + /// + /// Callback to apply the visual for . + /// + /// The target object the property was changed for. + /// The instance for the current event. + private static async void OnVisualFactoryPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var element = (UIElement)d; + var attachedVisual = await ((AttachedVisualFactoryBase)e.NewValue).GetAttachedVisualAsync(element); + + attachedVisual.BindSize(element); + + ElementCompositionPreview.SetElementChildVisual(element, attachedVisual); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj b/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj index d7ba95d0111..e7ccf6e9bda 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj +++ b/Microsoft.Toolkit.Uwp.UI.Media/Microsoft.Toolkit.Uwp.UI.Media.csproj @@ -2,7 +2,7 @@ uap10.0.17763 - 8.0 + 9.0 Windows Community Toolkit UI Media This library provides UI brushes. It is part of the Windows Community Toolkit. @@ -43,4 +43,8 @@ + + + + diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.Internals.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.Internals.cs new file mode 100644 index 00000000000..3ba0c0e4764 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.Internals.cs @@ -0,0 +1,222 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; +using Microsoft.Graphics.Canvas.Effects; +using Microsoft.Toolkit.Uwp.UI.Media.Extensions; +using Windows.Graphics.Effects; +using Windows.UI; +using Windows.UI.Composition; +using CanvasCrossFadeEffect = Microsoft.Graphics.Canvas.Effects.CrossFadeEffect; +using CanvasExposureEffect = Microsoft.Graphics.Canvas.Effects.ExposureEffect; +using CanvasHueRotationEffect = Microsoft.Graphics.Canvas.Effects.HueRotationEffect; +using CanvasOpacityEffect = Microsoft.Graphics.Canvas.Effects.OpacityEffect; +using CanvasSaturationEffect = Microsoft.Graphics.Canvas.Effects.SaturationEffect; +using CanvasSepiaEffect = Microsoft.Graphics.Canvas.Effects.SepiaEffect; +using CanvasTintEffect = Microsoft.Graphics.Canvas.Effects.TintEffect; + +namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines +{ + /// + /// A that allows to build custom effects pipelines and create instances from them + /// + public sealed partial class PipelineBuilder + { + /// + /// Adds a new to the current pipeline + /// + /// The blur amount to apply + /// The target property to animate the resulting effect. + /// The parameter for the effect, defaults to + /// The parameter to use, defaults to + /// A new instance to use to keep adding new effects + [Pure] + internal PipelineBuilder Blur( + float blur, + out string target, + EffectBorderMode mode = EffectBorderMode.Hard, + EffectOptimization optimization = EffectOptimization.Balanced) + { + string name = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{name}.{nameof(GaussianBlurEffect.BlurAmount)}"; + + async ValueTask Factory() => new GaussianBlurEffect + { + BlurAmount = blur, + BorderMode = mode, + Optimization = optimization, + Source = await this.sourceProducer(), + Name = name + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Cross fades two pipelines using an instance + /// + /// The second instance to cross fade + /// The cross fade factor to blend the input effects (should be in the [0, 1] range) + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}"; + + async ValueTask Factory() => new CanvasCrossFadeEffect + { + CrossFade = factor, + Source1 = await this.sourceProducer(), + Source2 = await pipeline.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(Factory, this, pipeline, new[] { target }); + } + + /// + /// Applies an exposure effect on the current pipeline + /// + /// The initial exposure of tint to apply over the current effect (should be in the [-2, 2] range) + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Exposure(float amount, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasExposureEffect.Exposure)}"; + + async ValueTask Factory() => new CanvasExposureEffect + { + Exposure = amount, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Applies a hue rotation effect on the current pipeline + /// + /// The angle to rotate the hue, in radians + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder HueRotation(float angle, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasHueRotationEffect.Angle)}"; + + async ValueTask Factory() => new CanvasHueRotationEffect + { + Angle = angle, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The opacity value to apply to the pipeline + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Opacity(float opacity, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasOpacityEffect.Opacity)}"; + + async ValueTask Factory() => new CanvasOpacityEffect + { + Opacity = opacity, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The initial saturation amount for the new effect (should be in the [0, 1] range) + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Saturation(float saturation, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasSaturationEffect.Saturation)}"; + + async ValueTask Factory() => new CanvasSaturationEffect + { + Saturation = saturation, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Adds a new to the current pipeline + /// + /// The sepia effect intensity for the new effect + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Sepia(float intensity, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasSepiaEffect.Intensity)}"; + + async ValueTask Factory() => new CanvasSepiaEffect + { + Intensity = intensity, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + + /// + /// Applies a tint effect on the current pipeline + /// + /// The color to use + /// The target property to animate the resulting effect. + /// A new instance to use to keep adding new effects + [Pure] + public PipelineBuilder Tint(Color color, out string target) + { + string id = Guid.NewGuid().ToUppercaseAsciiLetters(); + + target = $"{id}.{nameof(CanvasTintEffect.Color)}"; + + async ValueTask Factory() => new CanvasTintEffect + { + Color = color, + Source = await this.sourceProducer(), + Name = id + }; + + return new PipelineBuilder(this, Factory, new[] { target }); + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.cs index 96529ce540b..4a110b40d43 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Effects.cs @@ -12,6 +12,16 @@ using Windows.Graphics.Effects; using Windows.UI; using Windows.UI.Composition; +using CanvasExposureEffect = Microsoft.Graphics.Canvas.Effects.ExposureEffect; +using CanvasGrayscaleEffect = Microsoft.Graphics.Canvas.Effects.GrayscaleEffect; +using CanvasHueRotationEffect = Microsoft.Graphics.Canvas.Effects.HueRotationEffect; +using CanvasInvertEffect = Microsoft.Graphics.Canvas.Effects.InvertEffect; +using CanvasLuminanceToAlphaEffect = Microsoft.Graphics.Canvas.Effects.LuminanceToAlphaEffect; +using CanvasOpacityEffect = Microsoft.Graphics.Canvas.Effects.OpacityEffect; +using CanvasSaturationEffect = Microsoft.Graphics.Canvas.Effects.SaturationEffect; +using CanvasSepiaEffect = Microsoft.Graphics.Canvas.Effects.SepiaEffect; +using CanvasTemperatureAndTintEffect = Microsoft.Graphics.Canvas.Effects.TemperatureAndTintEffect; +using CanvasTintEffect = Microsoft.Graphics.Canvas.Effects.TintEffect; namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines { @@ -96,14 +106,14 @@ public PipelineBuilder Blur(float blur, out EffectAnimation animation, Ef } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The saturation amount for the new effect (should be in the [0, 1] range) /// A new instance to use to keep adding new effects [Pure] public PipelineBuilder Saturation(float saturation) { - async ValueTask Factory() => new SaturationEffect + async ValueTask Factory() => new CanvasSaturationEffect { Saturation = saturation, Source = await this.sourceProducer() @@ -113,7 +123,7 @@ public PipelineBuilder Saturation(float saturation) } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The initial saturation amount for the new effect (should be in the [0, 1] range) /// The optional saturation setter for the effect @@ -123,20 +133,20 @@ public PipelineBuilder Saturation(float saturation, out EffectSetter sett { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new SaturationEffect + async ValueTask Factory() => new CanvasSaturationEffect { Saturation = saturation, Source = await this.sourceProducer(), Name = id }; - setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(SaturationEffect.Saturation)}", value); + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasSaturationEffect.Saturation)}", value); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(SaturationEffect.Saturation)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasSaturationEffect.Saturation)}" }); } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The initial saturation amount for the new effect (should be in the [0, 1] range) /// The optional saturation animation for the effect @@ -146,27 +156,27 @@ public PipelineBuilder Saturation(float saturation, out EffectAnimation a { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new SaturationEffect + async ValueTask Factory() => new CanvasSaturationEffect { Saturation = saturation, Source = await this.sourceProducer(), Name = id }; - animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(SaturationEffect.Saturation)}", value, duration); + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasSaturationEffect.Saturation)}", value, duration); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(SaturationEffect.Saturation)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasSaturationEffect.Saturation)}" }); } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The sepia effect intensity for the new effect /// A new instance to use to keep adding new effects [Pure] public PipelineBuilder Sepia(float intensity) { - async ValueTask Factory() => new SepiaEffect + async ValueTask Factory() => new CanvasSepiaEffect { Intensity = intensity, Source = await this.sourceProducer() @@ -176,7 +186,7 @@ public PipelineBuilder Sepia(float intensity) } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The sepia effect intensity for the new effect /// The optional sepia intensity setter for the effect @@ -186,20 +196,20 @@ public PipelineBuilder Sepia(float intensity, out EffectSetter setter) { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new SepiaEffect + async ValueTask Factory() => new CanvasSepiaEffect { Intensity = intensity, Source = await this.sourceProducer(), Name = id }; - setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(SepiaEffect.Intensity)}", value); + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasSepiaEffect.Intensity)}", value); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(SepiaEffect.Intensity)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasSepiaEffect.Intensity)}" }); } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The sepia effect intensity for the new effect /// The sepia intensity animation for the effect @@ -209,27 +219,27 @@ public PipelineBuilder Sepia(float intensity, out EffectAnimation animati { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new SepiaEffect + async ValueTask Factory() => new CanvasSepiaEffect { Intensity = intensity, Source = await this.sourceProducer(), Name = id }; - animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(SepiaEffect.Intensity)}", value, duration); + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasSepiaEffect.Intensity)}", value, duration); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(SepiaEffect.Intensity)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasSepiaEffect.Intensity)}" }); } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The opacity value to apply to the pipeline /// A new instance to use to keep adding new effects [Pure] public PipelineBuilder Opacity(float opacity) { - async ValueTask Factory() => new OpacityEffect + async ValueTask Factory() => new CanvasOpacityEffect { Opacity = opacity, Source = await this.sourceProducer() @@ -239,7 +249,7 @@ public PipelineBuilder Opacity(float opacity) } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The opacity value to apply to the pipeline /// The optional opacity setter for the effect @@ -249,20 +259,20 @@ public PipelineBuilder Opacity(float opacity, out EffectSetter setter) { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new OpacityEffect + async ValueTask Factory() => new CanvasOpacityEffect { Opacity = opacity, Source = await this.sourceProducer(), Name = id }; - setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(OpacityEffect.Opacity)}", value); + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasOpacityEffect.Opacity)}", value); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(OpacityEffect.Opacity)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasOpacityEffect.Opacity)}" }); } /// - /// Adds a new to the current pipeline + /// Adds a new to the current pipeline /// /// The opacity value to apply to the pipeline /// The optional opacity animation for the effect @@ -272,16 +282,16 @@ public PipelineBuilder Opacity(float opacity, out EffectAnimation animati { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new OpacityEffect + async ValueTask Factory() => new CanvasOpacityEffect { Opacity = opacity, Source = await this.sourceProducer(), Name = id }; - animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(OpacityEffect.Opacity)}", value, duration); + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasOpacityEffect.Opacity)}", value, duration); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(OpacityEffect.Opacity)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasOpacityEffect.Opacity)}" }); } /// @@ -292,7 +302,7 @@ public PipelineBuilder Opacity(float opacity, out EffectAnimation animati [Pure] public PipelineBuilder Exposure(float amount) { - async ValueTask Factory() => new ExposureEffect + async ValueTask Factory() => new CanvasExposureEffect { Exposure = amount, Source = await this.sourceProducer() @@ -312,16 +322,16 @@ public PipelineBuilder Exposure(float amount, out EffectSetter setter) { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new ExposureEffect + async ValueTask Factory() => new CanvasExposureEffect { Exposure = amount, Source = await this.sourceProducer(), Name = id }; - setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(ExposureEffect.Exposure)}", value); + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasExposureEffect.Exposure)}", value); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(ExposureEffect.Exposure)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasExposureEffect.Exposure)}" }); } /// @@ -335,16 +345,16 @@ public PipelineBuilder Exposure(float amount, out EffectAnimation animati { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new ExposureEffect + async ValueTask Factory() => new CanvasExposureEffect { Exposure = amount, Source = await this.sourceProducer(), Name = id }; - animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(ExposureEffect.Exposure)}", value, duration); + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasExposureEffect.Exposure)}", value, duration); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(ExposureEffect.Exposure)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasExposureEffect.Exposure)}" }); } /// @@ -355,7 +365,7 @@ public PipelineBuilder Exposure(float amount, out EffectAnimation animati [Pure] public PipelineBuilder HueRotation(float angle) { - async ValueTask Factory() => new HueRotationEffect + async ValueTask Factory() => new CanvasHueRotationEffect { Angle = angle, Source = await this.sourceProducer() @@ -375,16 +385,16 @@ public PipelineBuilder HueRotation(float angle, out EffectSetter setter) { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new HueRotationEffect + async ValueTask Factory() => new CanvasHueRotationEffect { Angle = angle, Source = await this.sourceProducer(), Name = id }; - setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(HueRotationEffect.Angle)}", value); + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasHueRotationEffect.Angle)}", value); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(HueRotationEffect.Angle)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasHueRotationEffect.Angle)}" }); } /// @@ -398,16 +408,16 @@ public PipelineBuilder HueRotation(float angle, out EffectAnimation anima { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new HueRotationEffect + async ValueTask Factory() => new CanvasHueRotationEffect { Angle = angle, Source = await this.sourceProducer(), Name = id }; - animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(HueRotationEffect.Angle)}", value, duration); + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasHueRotationEffect.Angle)}", value, duration); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(HueRotationEffect.Angle)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasHueRotationEffect.Angle)}" }); } /// @@ -418,7 +428,7 @@ public PipelineBuilder HueRotation(float angle, out EffectAnimation anima [Pure] public PipelineBuilder Tint(Color color) { - async ValueTask Factory() => new TintEffect + async ValueTask Factory() => new CanvasTintEffect { Color = color, Source = await this.sourceProducer() @@ -438,16 +448,16 @@ public PipelineBuilder Tint(Color color, out EffectSetter setter) { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new TintEffect + async ValueTask Factory() => new CanvasTintEffect { Color = color, Source = await this.sourceProducer(), Name = id }; - setter = (brush, value) => brush.Properties.InsertColor($"{id}.{nameof(TintEffect.Color)}", value); + setter = (brush, value) => brush.Properties.InsertColor($"{id}.{nameof(CanvasTintEffect.Color)}", value); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(TintEffect.Color)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasTintEffect.Color)}" }); } /// @@ -461,16 +471,16 @@ public PipelineBuilder Tint(Color color, out EffectAnimation animation) { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new TintEffect + async ValueTask Factory() => new CanvasTintEffect { Color = color, Source = await this.sourceProducer(), Name = id }; - animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(TintEffect.Color)}", value, duration); + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasTintEffect.Color)}", value, duration); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(TintEffect.Color)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasTintEffect.Color)}" }); } /// @@ -482,7 +492,7 @@ public PipelineBuilder Tint(Color color, out EffectAnimation animation) [Pure] public PipelineBuilder TemperatureAndTint(float temperature, float tint) { - async ValueTask Factory() => new TemperatureAndTintEffect + async ValueTask Factory() => new CanvasTemperatureAndTintEffect { Temperature = temperature, Tint = tint, @@ -509,7 +519,7 @@ public PipelineBuilder TemperatureAndTint( { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new TemperatureAndTintEffect + async ValueTask Factory() => new CanvasTemperatureAndTintEffect { Temperature = temperature, Tint = tint, @@ -517,11 +527,11 @@ public PipelineBuilder TemperatureAndTint( Name = id }; - temperatureSetter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(TemperatureAndTintEffect.Temperature)}", value); + temperatureSetter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasTemperatureAndTintEffect.Temperature)}", value); - tintSetter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(TemperatureAndTintEffect.Tint)}", value); + tintSetter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasTemperatureAndTintEffect.Tint)}", value); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(TemperatureAndTintEffect.Temperature)}", $"{id}.{nameof(TemperatureAndTintEffect.Tint)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasTemperatureAndTintEffect.Temperature)}", $"{id}.{nameof(CanvasTemperatureAndTintEffect.Tint)}" }); } /// @@ -541,7 +551,7 @@ public PipelineBuilder TemperatureAndTint( { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new TemperatureAndTintEffect + async ValueTask Factory() => new CanvasTemperatureAndTintEffect { Temperature = temperature, Tint = tint, @@ -549,11 +559,11 @@ public PipelineBuilder TemperatureAndTint( Name = id }; - temperatureAnimation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(TemperatureAndTintEffect.Temperature)}", value, duration); + temperatureAnimation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasTemperatureAndTintEffect.Temperature)}", value, duration); - tintAnimation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(TemperatureAndTintEffect.Tint)}", value, duration); + tintAnimation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasTemperatureAndTintEffect.Tint)}", value, duration); - return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(TemperatureAndTintEffect.Temperature)}", $"{id}.{nameof(TemperatureAndTintEffect.Tint)}" }); + return new PipelineBuilder(this, Factory, new[] { $"{id}.{nameof(CanvasTemperatureAndTintEffect.Temperature)}", $"{id}.{nameof(CanvasTemperatureAndTintEffect.Tint)}" }); } /// @@ -611,7 +621,7 @@ public PipelineBuilder Shade( [Pure] public PipelineBuilder LuminanceToAlpha() { - async ValueTask Factory() => new LuminanceToAlphaEffect + async ValueTask Factory() => new CanvasLuminanceToAlphaEffect { Source = await this.sourceProducer() }; @@ -626,7 +636,7 @@ public PipelineBuilder LuminanceToAlpha() [Pure] public PipelineBuilder Invert() { - async ValueTask Factory() => new InvertEffect + async ValueTask Factory() => new CanvasInvertEffect { Source = await this.sourceProducer() }; @@ -641,7 +651,7 @@ public PipelineBuilder Invert() [Pure] public PipelineBuilder Grayscale() { - async ValueTask Factory() => new GrayscaleEffect + async ValueTask Factory() => new CanvasGrayscaleEffect { Source = await this.sourceProducer() }; diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Merge.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Merge.cs index 9caba936497..8a52b851590 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Merge.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.Merge.cs @@ -11,6 +11,8 @@ using Microsoft.Toolkit.Uwp.UI.Media.Extensions; using Windows.Graphics.Effects; using Windows.UI.Composition; +using CanvasBlendEffect = Microsoft.Graphics.Canvas.Effects.BlendEffect; +using CanvasCrossFadeEffect = Microsoft.Graphics.Canvas.Effects.CrossFadeEffect; namespace Microsoft.Toolkit.Uwp.UI.Media.Pipelines { @@ -36,7 +38,7 @@ public PipelineBuilder Blend(PipelineBuilder pipeline, BlendEffectMode mode, Pla _ => throw new ArgumentException($"Invalid placement value: {placement}") }; - async ValueTask Factory() => new BlendEffect + async ValueTask Factory() => new CanvasBlendEffect { Foreground = await foreground.sourceProducer(), Background = await background.sourceProducer(), @@ -47,7 +49,7 @@ public PipelineBuilder Blend(PipelineBuilder pipeline, BlendEffectMode mode, Pla } /// - /// Cross fades two pipelines using an instance + /// Cross fades two pipelines using an instance /// /// The second instance to cross fade /// The cross fade factor to blend the input effects (default is 0.5, must be in the [0, 1] range) @@ -55,7 +57,7 @@ public PipelineBuilder Blend(PipelineBuilder pipeline, BlendEffectMode mode, Pla [Pure] public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor = 0.5f) { - async ValueTask Factory() => new CrossFadeEffect + async ValueTask Factory() => new CanvasCrossFadeEffect { CrossFade = factor, Source1 = await this.sourceProducer(), @@ -66,7 +68,7 @@ public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor = 0.5f) } /// - /// Cross fades two pipelines using an instance + /// Cross fades two pipelines using an instance /// /// The second instance to cross fade /// The cross fade factor to blend the input effects (should be in the [0, 1] range) @@ -77,7 +79,7 @@ public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out Eff { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new CrossFadeEffect + async ValueTask Factory() => new CanvasCrossFadeEffect { CrossFade = factor, Source1 = await this.sourceProducer(), @@ -85,13 +87,13 @@ public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out Eff Name = id }; - setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CrossFadeEffect.CrossFade)}", value); + setter = (brush, value) => brush.Properties.InsertScalar($"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}", value); - return new PipelineBuilder(Factory, this, pipeline, new[] { $"{id}.{nameof(CrossFadeEffect.CrossFade)}" }); + return new PipelineBuilder(Factory, this, pipeline, new[] { $"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}" }); } /// - /// Cross fades two pipelines using an instance + /// Cross fades two pipelines using an instance /// /// The second instance to cross fade /// The cross fade factor to blend the input effects (should be in the [0, 1] range) @@ -102,7 +104,7 @@ public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out Eff { string id = Guid.NewGuid().ToUppercaseAsciiLetters(); - async ValueTask Factory() => new CrossFadeEffect + async ValueTask Factory() => new CanvasCrossFadeEffect { CrossFade = factor, Source1 = await this.sourceProducer(), @@ -110,9 +112,9 @@ public PipelineBuilder CrossFade(PipelineBuilder pipeline, float factor, out Eff Name = id }; - animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CrossFadeEffect.CrossFade)}", value, duration); + animation = (brush, value, duration) => brush.StartAnimationAsync($"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}", value, duration); - return new PipelineBuilder(Factory, this, pipeline, new[] { $"{id}.{nameof(CrossFadeEffect.CrossFade)}" }); + return new PipelineBuilder(Factory, this, pipeline, new[] { $"{id}.{nameof(CanvasCrossFadeEffect.CrossFade)}" }); } /// diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.cs b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.cs index e6568aeee26..8f65d10a7f5 100644 --- a/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.cs +++ b/Microsoft.Toolkit.Uwp.UI.Media/Pipelines/PipelineBuilder.cs @@ -190,7 +190,7 @@ public async Task AttachAsync(UIElement target, UIElement referenc { var visual = Window.Current.Compositor.CreateSpriteVisual(); - visual.Brush = await this.BuildAsync(); + visual.Brush = await BuildAsync(); ElementCompositionPreview.SetElementChildVisual(target, visual); diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Visuals/AttachedVisualFactoryBase.cs b/Microsoft.Toolkit.Uwp.UI.Media/Visuals/AttachedVisualFactoryBase.cs new file mode 100644 index 00000000000..1badf68d02f --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Visuals/AttachedVisualFactoryBase.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Windows.UI.Composition; +using Windows.UI.Xaml; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// A type responsible for creating instances to attach to target elements. + /// + public abstract class AttachedVisualFactoryBase : DependencyObject + { + /// + /// Creates a to attach to the target element. + /// + /// The target the visual will be attached to. + /// A instance that the caller will attach to the target element. + public abstract ValueTask GetAttachedVisualAsync(UIElement element); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Visuals/PipelineVisualFactory.cs b/Microsoft.Toolkit.Uwp.UI.Media/Visuals/PipelineVisualFactory.cs new file mode 100644 index 00000000000..694c9a859da --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Visuals/PipelineVisualFactory.cs @@ -0,0 +1,80 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Markup; +using Windows.UI.Xaml.Media; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// A builder type for instance to apply to UI elements. + /// + [ContentProperty(Name = nameof(Effects))] + public sealed class PipelineVisualFactory : PipelineVisualFactoryBase + { + /// + /// Gets or sets the source for the current pipeline (defaults to a with source). + /// + public PipelineBuilder Source { get; set; } + + /// + /// Gets or sets the collection of effects to use in the current pipeline. + /// + public IList Effects + { + get + { + if (GetValue(EffectsProperty) is not IList effects) + { + effects = new List(); + + SetValue(EffectsProperty, effects); + } + + return effects; + } + set => SetValue(EffectsProperty, value); + } + + /// + /// Identifies the dependency property. + /// + public static readonly DependencyProperty EffectsProperty = DependencyProperty.Register( + nameof(Effects), + typeof(IList), + typeof(PipelineVisualFactory), + new PropertyMetadata(null)); + + /// + public override async ValueTask GetAttachedVisualAsync(UIElement element) + { + var visual = (SpriteVisual)await base.GetAttachedVisualAsync(element); + + foreach (IPipelineEffect effect in Effects) + { + effect.NotifyCompositionBrushInUse(visual.Brush); + } + + return visual; + } + + /// + protected override PipelineBuilder OnPipelineRequested() + { + PipelineBuilder builder = Source ?? PipelineBuilder.FromBackdrop(); + + foreach (IPipelineEffect effect in Effects) + { + builder = effect.AppendToBuilder(builder); + } + + return builder; + } + } +} diff --git a/Microsoft.Toolkit.Uwp.UI.Media/Visuals/PipelineVisualFactoryBase.cs b/Microsoft.Toolkit.Uwp.UI.Media/Visuals/PipelineVisualFactoryBase.cs new file mode 100644 index 00000000000..472167d5f63 --- /dev/null +++ b/Microsoft.Toolkit.Uwp.UI.Media/Visuals/PipelineVisualFactoryBase.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Threading.Tasks; +using Microsoft.Toolkit.Uwp.UI.Media.Pipelines; +using Windows.UI.Composition; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Hosting; + +namespace Microsoft.Toolkit.Uwp.UI.Media +{ + /// + /// A base class that extends by leveraging the APIs. + /// + public abstract class PipelineVisualFactoryBase : AttachedVisualFactoryBase + { + /// + public override async ValueTask GetAttachedVisualAsync(UIElement element) + { + var visual = ElementCompositionPreview.GetElementVisual(element).Compositor.CreateSpriteVisual(); + + visual.Brush = await OnPipelineRequested().BuildAsync(); + + return visual; + } + + /// + /// A method that builds and returns the pipeline to use in the current instance. + /// + /// A instance to create the to display. + protected abstract PipelineBuilder OnPipelineRequested(); + } +} diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/Enums/Axis.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/Enums/Axis.cs deleted file mode 100644 index 6be7a01a3f6..00000000000 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/ScrollViewer/Enums/Axis.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -namespace Microsoft.Toolkit.Uwp.UI.Extensions -{ - /// - /// Indicates an axis in the 2D space - /// - public enum Axis - { - /// - /// The X axis (horizontal) - /// - X, - - /// - /// The Y axis (vertical) - /// - Y - } -} diff --git a/Microsoft.Toolkit.Uwp.UI/Extensions/Visual/VisualExtensions.cs b/Microsoft.Toolkit.Uwp.UI/Extensions/Visual/VisualExtensions.cs index 1e5cc147912..85b3770dab1 100644 --- a/Microsoft.Toolkit.Uwp.UI/Extensions/Visual/VisualExtensions.cs +++ b/Microsoft.Toolkit.Uwp.UI/Extensions/Visual/VisualExtensions.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Linq; using System.Numerics; +using System.Runtime.CompilerServices; using Windows.UI.Composition; using Windows.UI.Xaml; using Windows.UI.Xaml.Hosting; @@ -159,12 +160,24 @@ public static Vector4 ToVector4(this string str) } } + /// + /// Converts a to + /// + /// A string in the format of "float, float, float, float" + /// + public static Quaternion ToQuaternion(this string str) + { + Vector4 vector = str.ToVector4(); + + return Unsafe.As(ref vector); + } + /// /// Retrieves the object of a /// /// The /// The backing the - public static Visual GetVisual(UIElement element) + public static Visual GetVisual(this UIElement element) { return ElementCompositionPreview.GetElementVisual(element); } @@ -444,7 +457,7 @@ public static void SetSize(DependencyObject obj, string value) /// is centered even when the visual is resized /// /// The - /// a string representing Vector3 as the normalized + /// a string representing Vector2 as the normalized public static string GetNormalizedCenterPoint(DependencyObject obj) { return (string)obj.GetValue(NormalizedCenterPointProperty); @@ -455,7 +468,7 @@ public static string GetNormalizedCenterPoint(DependencyObject obj) /// is centered even when the visual is resized /// /// The - /// A string representing a Vector3 normalized between 0.0 and 1.0 + /// A string representing a Vector2 normalized between 0.0 and 1.0 public static void SetNormalizedCenterPoint(DependencyObject obj, string value) { obj.SetValue(NormalizedCenterPointProperty, value); @@ -595,36 +608,23 @@ private static void OnSizeChanged(DependencyObject d, DependencyPropertyChangedE private static void OnNormalizedCenterPointChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - if (d is FrameworkElement element && !DesignTimeHelpers.IsRunningInLegacyDesignerMode) + if (d is FrameworkElement element && + !DesignTimeHelpers.IsRunningInLegacyDesignerMode && + e.NewValue is string newValue) { - SetupNormalizedCenterPoint(e, element); - } - } + Vector2 center = newValue.ToVector2(); + Visual visual = element.GetVisual(); + const string expression = "Vector2(this.Target.Size.X * X, this.Target.Size.Y * Y)"; + ExpressionAnimation animation = visual.Compositor.CreateExpressionAnimation(expression); - private static void SetupNormalizedCenterPoint(DependencyPropertyChangedEventArgs e, FrameworkElement element) - { - element.SizeChanged -= KeepCenteredElementSizeChanged; - - if (e.NewValue is string normalizedValue) - { - var vectorValue = normalizedValue.ToVector3(); - var visual = GetVisual(element); - visual.CenterPoint = new Vector3((float)element.ActualWidth * vectorValue.X, (float)element.ActualHeight * vectorValue.Y, 0); + animation.SetScalarParameter("X", center.X); + animation.SetScalarParameter("Y", center.Y); - element.SizeChanged += KeepCenteredElementSizeChanged; + visual.StopAnimation("CenterPoint.XY"); + visual.StartAnimation("CenterPoint.XY", animation); } } - private static void KeepCenteredElementSizeChanged(object sender, SizeChangedEventArgs e) - { - var element = sender as FrameworkElement; - - var normalizedValue = GetNormalizedCenterPoint(element); - var vectorValue = normalizedValue.ToVector3(); - var visual = GetVisual(element); - visual.CenterPoint = new Vector3((float)element.ActualWidth * vectorValue.X, (float)element.ActualHeight * vectorValue.Y, 0); - } - private static string GetAnchorPointForElement(UIElement element) { var visual = GetVisual(element); diff --git a/Microsoft.Toolkit.Uwp.UI/Microsoft.Toolkit.Uwp.UI.csproj b/Microsoft.Toolkit.Uwp.UI/Microsoft.Toolkit.Uwp.UI.csproj index d5c4be426c6..edb24421aca 100644 --- a/Microsoft.Toolkit.Uwp.UI/Microsoft.Toolkit.Uwp.UI.csproj +++ b/Microsoft.Toolkit.Uwp.UI/Microsoft.Toolkit.Uwp.UI.csproj @@ -9,10 +9,6 @@ AdvancedCollectionView: It's a collection view implementation that support filtering, sorting and incremental loading. It's meant to be used in a viewmodel. - Behaviors: - - BehaviorBase: Helper for building Behaviors - - ViewportBehavior: Listening for element to enter or exit the ScrollViewer viewport - CacheBase: Provides methods and tools to cache files in a folder. Converters: Commonly used converters that allow the data to be modified as it passes through the binding engine. @@ -47,11 +43,7 @@ true - - - - - + diff --git a/SmokeTests/Microsoft.Toolkit.Uwp.UI.Behaviors/MainPage.xaml b/SmokeTests/Microsoft.Toolkit.Uwp.UI.Behaviors/MainPage.xaml new file mode 100644 index 00000000000..cef24c59c88 --- /dev/null +++ b/SmokeTests/Microsoft.Toolkit.Uwp.UI.Behaviors/MainPage.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + diff --git a/SmokeTests/Microsoft.Toolkit.Uwp.UI.Behaviors/MainPage.xaml.cs b/SmokeTests/Microsoft.Toolkit.Uwp.UI.Behaviors/MainPage.xaml.cs new file mode 100644 index 00000000000..d1fe446c34f --- /dev/null +++ b/SmokeTests/Microsoft.Toolkit.Uwp.UI.Behaviors/MainPage.xaml.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Linq; +using Microsoft.Toolkit.Uwp.UI.Behaviors; +using Microsoft.Xaml.Interactivity; +using Windows.UI.Xaml.Media.Imaging; + +namespace SmokeTest +{ + public sealed partial class MainPage + { + public MainPage() + { + InitializeComponent(); + + Loaded += this.MainPage_Loaded; + } + + private void MainPage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) + { + var behaviors = Interaction.GetBehaviors(EffectElementHost); + var viewportBehavior = behaviors.OfType().FirstOrDefault(); + if (viewportBehavior != null) + { + viewportBehavior.EnteredViewport += EffectElementHost_EnteredViewport; + viewportBehavior.EnteringViewport += EffectElementHost_EnteringViewport; + viewportBehavior.ExitedViewport += EffectElementHost_ExitedViewport; + viewportBehavior.ExitingViewport += EffectElementHost_ExitingViewport; + } + } + + private void EffectElementHost_EnteredViewport(object sender, EventArgs e) + { + Debug.WriteLine("Entered viewport"); + } + + private void EffectElementHost_EnteringViewport(object sender, EventArgs e) + { + Debug.WriteLine("Entering viewport"); + + EffectElement.Source = new BitmapImage(new Uri("ms-appx:///Assets/ToolkitLogo.png")); + } + + private void EffectElementHost_ExitedViewport(object sender, EventArgs e) + { + Debug.WriteLine("Exited viewport"); + + EffectElement.Source = null; + } + + private void EffectElementHost_ExitingViewport(object sender, EventArgs e) + { + Debug.WriteLine("Exiting viewport"); + } + } +} diff --git a/SmokeTests/Microsoft.Toolkit.Uwp.UI/MainPage.xaml b/SmokeTests/Microsoft.Toolkit.Uwp.UI/MainPage.xaml index cef24c59c88..f6361b121cc 100644 --- a/SmokeTests/Microsoft.Toolkit.Uwp.UI/MainPage.xaml +++ b/SmokeTests/Microsoft.Toolkit.Uwp.UI/MainPage.xaml @@ -3,33 +3,19 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:SmokeTest" - xmlns:interactivity="using:Microsoft.Xaml.Interactivity" - xmlns:behaviors="using:Microsoft.Toolkit.Uwp.UI.Behaviors" + xmlns:extensions="using:Microsoft.Toolkit.Uwp.UI.Extensions" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> - - - - - - - - - - - + diff --git a/SmokeTests/Microsoft.Toolkit.Uwp.UI/MainPage.xaml.cs b/SmokeTests/Microsoft.Toolkit.Uwp.UI/MainPage.xaml.cs index d1fe446c34f..8acca918716 100644 --- a/SmokeTests/Microsoft.Toolkit.Uwp.UI/MainPage.xaml.cs +++ b/SmokeTests/Microsoft.Toolkit.Uwp.UI/MainPage.xaml.cs @@ -5,9 +5,8 @@ using System; using System.Diagnostics; using System.Linq; -using Microsoft.Toolkit.Uwp.UI.Behaviors; -using Microsoft.Xaml.Interactivity; -using Windows.UI.Xaml.Media.Imaging; +using Microsoft.Toolkit.Uwp.UI.Extensions; +using Windows.UI.Xaml.Controls; namespace SmokeTest { @@ -22,39 +21,7 @@ public MainPage() private void MainPage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) { - var behaviors = Interaction.GetBehaviors(EffectElementHost); - var viewportBehavior = behaviors.OfType().FirstOrDefault(); - if (viewportBehavior != null) - { - viewportBehavior.EnteredViewport += EffectElementHost_EnteredViewport; - viewportBehavior.EnteringViewport += EffectElementHost_EnteringViewport; - viewportBehavior.ExitedViewport += EffectElementHost_ExitedViewport; - viewportBehavior.ExitingViewport += EffectElementHost_ExitingViewport; - } - } - - private void EffectElementHost_EnteredViewport(object sender, EventArgs e) - { - Debug.WriteLine("Entered viewport"); - } - - private void EffectElementHost_EnteringViewport(object sender, EventArgs e) - { - Debug.WriteLine("Entering viewport"); - - EffectElement.Source = new BitmapImage(new Uri("ms-appx:///Assets/ToolkitLogo.png")); - } - - private void EffectElementHost_ExitedViewport(object sender, EventArgs e) - { - Debug.WriteLine("Exited viewport"); - - EffectElement.Source = null; - } - - private void EffectElementHost_ExitingViewport(object sender, EventArgs e) - { - Debug.WriteLine("Exiting viewport"); + var border = this.FindDescendant(); } } } diff --git a/SmokeTests/SmokeTests.proj b/SmokeTests/SmokeTests.proj index e4f08260c42..d856a867110 100644 --- a/SmokeTests/SmokeTests.proj +++ b/SmokeTests/SmokeTests.proj @@ -4,7 +4,27 @@ x86 Release - UWPBaseline;Microsoft.Toolkit;Microsoft.Toolkit.HighPerformance;Microsoft.Toolkit.Parsers;Microsoft.Toolkit.Mvvm;Microsoft.Toolkit.Services;Microsoft.Toolkit.Uwp;Microsoft.Toolkit.Uwp.Connectivity;Microsoft.Toolkit.Uwp.DeveloperTools;Microsoft.Toolkit.Uwp.Input.GazeInteraction;Microsoft.Toolkit.Uwp.Notifications;Microsoft.Toolkit.Uwp.UI;Microsoft.Toolkit.Uwp.UI.Animations;Microsoft.Toolkit.Uwp.UI.Controls;Microsoft.Toolkit.Uwp.UI.Controls.DataGrid;Microsoft.Toolkit.Uwp.UI.Controls.Layout;Microsoft.Toolkit.Uwp.UI.Media;Microsoft.Toolkit.Uwp.UI.Controls.Markdown + + UWPBaseline; + Microsoft.Toolkit; + Microsoft.Toolkit.HighPerformance; + Microsoft.Toolkit.Mvvm; + Microsoft.Toolkit.Parsers; + Microsoft.Toolkit.Services; + Microsoft.Toolkit.Uwp; + Microsoft.Toolkit.Uwp.Connectivity; + Microsoft.Toolkit.Uwp.DeveloperTools; + Microsoft.Toolkit.Uwp.Input.GazeInteraction; + Microsoft.Toolkit.Uwp.Notifications; + Microsoft.Toolkit.Uwp.UI; + Microsoft.Toolkit.Uwp.UI.Animations; + Microsoft.Toolkit.Uwp.UI.Behaviors; + Microsoft.Toolkit.Uwp.UI.Controls; + Microsoft.Toolkit.Uwp.UI.Controls.DataGrid; + Microsoft.Toolkit.Uwp.UI.Controls.Layout; + Microsoft.Toolkit.Uwp.UI.Controls.Markdown; + Microsoft.Toolkit.Uwp.UI.Media; + diff --git a/Windows Community Toolkit.sln b/Windows Community Toolkit.sln index 17df1370cd3..9f57b24e708 100644 --- a/Windows Community Toolkit.sln +++ b/Windows Community Toolkit.sln @@ -135,6 +135,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UITests.Tests.TAEF", "UITes EndProject Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UITests.Tests.Shared", "UITests\UITests.Tests.Shared\UITests.Tests.Shared.shproj", "{1D8B0260-5C17-41DA-9C38-1E37441B3925}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Toolkit.Uwp.UI.Behaviors", "Microsoft.Toolkit.Uwp.UI.Behaviors\Microsoft.Toolkit.Uwp.UI.Behaviors.csproj", "{D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution UITests\UITests.Tests.Shared\UITests.Tests.Shared.projitems*{05c83067-fa46-45e2-bec4-edee84ad18d0}*SharedItemsImports = 4 @@ -956,6 +958,26 @@ Global {C8182EF0-77FB-4B43-A588-C71748A309C7}.Release|x64.Build.0 = Release|Any CPU {C8182EF0-77FB-4B43-A588-C71748A309C7}.Release|x86.ActiveCfg = Release|Any CPU {C8182EF0-77FB-4B43-A588-C71748A309C7}.Release|x86.Build.0 = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|ARM.ActiveCfg = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|ARM.Build.0 = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|ARM64.Build.0 = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|x64.ActiveCfg = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|x64.Build.0 = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|x86.ActiveCfg = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Debug|x86.Build.0 = Debug|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|Any CPU.Build.0 = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|ARM.ActiveCfg = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|ARM.Build.0 = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|ARM64.ActiveCfg = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|ARM64.Build.0 = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|x64.ActiveCfg = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|x64.Build.0 = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|x86.ActiveCfg = Release|Any CPU + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -997,6 +1019,7 @@ Global {5F720475-E263-4A5A-8C88-2B805B45B5BC} = {6FAA1CFE-3368-4FD2-9DBD-F4700F69174C} {C8182EF0-77FB-4B43-A588-C71748A309C7} = {6FAA1CFE-3368-4FD2-9DBD-F4700F69174C} {1D8B0260-5C17-41DA-9C38-1E37441B3925} = {6FAA1CFE-3368-4FD2-9DBD-F4700F69174C} + {D4FF799D-0DF2-495A-ADC9-3BBC4AEF8971} = {F1AFFFA7-28FE-4770-BA48-10D76F3E59BC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5403B0C4-F244-4F73-A35C-FE664D0F4345}