From 56f986c233c468fede1e29e7a14186472533f794 Mon Sep 17 00:00:00 2001 From: aborziak-ms <83784664+aborziak-ms@users.noreply.github.com> Date: Mon, 27 Feb 2023 13:51:11 -0800 Subject: [PATCH] [LottieGen] Added new AnimationController API support for LottieGen (#488) --- .../Lottie-Windows-Uwp.csproj | 2 +- build/Install-WindowsSdkISO.ps1 | 5 ++ source/Lottie/AnimatedVisualFactory.cs | 7 +- source/Lottie/DisposableAnimatedVisual.cs | 10 ++- source/Lottie/Instantiator.cs | 43 +++++++-- source/Lottie/Loader.cs | 9 +- source/Lottie/LottieVisualSource.cs | 1 + source/LottieToWinComp/Animate.cs | 7 ++ .../CompositionObjectFactory.cs | 12 +++ source/LottieToWinComp/TranslationContext.cs | 15 ++++ source/UIData/Tools/GraphCompactor.cs | 90 ++++++++++++++----- source/UIData/Tools/ObjectGraph.cs | 6 ++ source/UIData/Tools/Optimizer.cs | 31 +++++-- source/UIData/Tools/Stats.cs | 2 + .../Cppwinrt/CppwinrtInstantiatorGenerator.cs | 5 +- .../CodeGen/InstantiatorGeneratorBase.cs | 53 +++++++++-- source/WinCompData/AnimationController.cs | 13 ++- source/WinCompData/CompositionObject.cs | 25 +++++- source/WinCompData/Compositor.cs | 2 + 19 files changed, 283 insertions(+), 55 deletions(-) diff --git a/Lottie-Windows/Lottie-Windows-Uwp/Lottie-Windows-Uwp.csproj b/Lottie-Windows/Lottie-Windows-Uwp/Lottie-Windows-Uwp.csproj index 85f5c6d..dcbe9f6 100644 --- a/Lottie-Windows/Lottie-Windows-Uwp/Lottie-Windows-Uwp.csproj +++ b/Lottie-Windows/Lottie-Windows-Uwp/Lottie-Windows-Uwp.csproj @@ -3,7 +3,7 @@ uap10.0.16299 winmdobj - + CommunityToolkit.Uwp.Lottie UWP Toolkit Windows Animations Lottie XAML diff --git a/build/Install-WindowsSdkISO.ps1 b/build/Install-WindowsSdkISO.ps1 index 5ac8ec0..8ec3436 100644 --- a/build/Install-WindowsSdkISO.ps1 +++ b/build/Install-WindowsSdkISO.ps1 @@ -242,6 +242,11 @@ if ($InstallWindowsSDK) # Note: there is a delay from Windows SDK announcements to availability via the static link $uri = "https://go.microsoft.com/fwlink/?prd=11966&pver=1.0&plcid=0x409&clcid=0x409&ar=Flight&sar=Sdsurl&o1=$buildNumber" + if ($buildNumber -eq "22621") + { + $uri = "https://go.microsoft.com/fwlink/p/?linkid=2196240" + } + if ($env:TEMP -eq $null) { $env:TEMP = Join-Path $env:SystemDrive 'temp' diff --git a/source/Lottie/AnimatedVisualFactory.cs b/source/Lottie/AnimatedVisualFactory.cs index 999998c..b8887c1 100644 --- a/source/Lottie/AnimatedVisualFactory.cs +++ b/source/Lottie/AnimatedVisualFactory.cs @@ -33,6 +33,7 @@ namespace CommunityToolkit.WinUI.Lottie Loader? _loader; WinCompData.Visual? _wincompDataRootVisual; WinCompData.CompositionPropertySet? _wincompDataThemingPropertySet; + IEnumerable? _wincompDataAnimationControllers; CompositionPropertySet? _themingPropertySet; double _width; double _height; @@ -62,6 +63,7 @@ namespace CommunityToolkit.WinUI.Lottie CompositionObjectNodes. Where(n => n.Object is WinCompData.CompositionPropertySet cps && cps.Owner is null). Select(n => (WinCompData.CompositionPropertySet)n.Object).FirstOrDefault(); + _wincompDataAnimationControllers = graph.CompositionObjectNodes.Where(n => (n.Object is WinCompData.AnimationController) && ((WinCompData.AnimationController)n.Object).IsCustom).Select(n => (WinCompData.AnimationController)n.Object); } internal bool CanInstantiate => _wincompDataRootVisual is not null; @@ -84,7 +86,10 @@ namespace CommunityToolkit.WinUI.Lottie var instantiator = new Instantiator(compositor, surfaceResolver: LoadImageFromUri); // _wincompDataRootVisual is not null is implied by CanInstantiate. - var result = new DisposableAnimatedVisual((Visual)instantiator.GetInstance(_wincompDataRootVisual!)) + Visual rootVisual = (Visual)instantiator.GetInstance(_wincompDataRootVisual!); + IEnumerable animationControllers = _wincompDataAnimationControllers!.Select(o => (AnimationController)instantiator.GetInstance(o)); + + var result = new DisposableAnimatedVisual(rootVisual, animationControllers) { Size = new System.Numerics.Vector2((float)_width, (float)_height), Duration = _duration, diff --git a/source/Lottie/DisposableAnimatedVisual.cs b/source/Lottie/DisposableAnimatedVisual.cs index d1390e3..587c16f 100644 --- a/source/Lottie/DisposableAnimatedVisual.cs +++ b/source/Lottie/DisposableAnimatedVisual.cs @@ -5,6 +5,7 @@ #nullable enable using System; +using System.Collections.Generic; using System.Numerics; using Microsoft.UI.Xaml.Controls; @@ -18,13 +19,20 @@ namespace CommunityToolkit.WinUI.Lottie { sealed class DisposableAnimatedVisual : IAnimatedVisual, IDisposable { - internal DisposableAnimatedVisual(Visual rootVisual) + internal DisposableAnimatedVisual(Visual rootVisual, IEnumerable customAnimationControllers) { RootVisual = rootVisual; + CustomAnimationControllers = customAnimationControllers; } public Visual RootVisual { get; } + /// + /// Keeps references to all custom AnimationController objects. + /// We need references because otherwise they will be destroyed from dwm. + /// + public IEnumerable CustomAnimationControllers { get; } + public TimeSpan Duration { get; set; } public Vector2 Size { get; set; } diff --git a/source/Lottie/Instantiator.cs b/source/Lottie/Instantiator.cs index 4c3b864..8fc4439 100644 --- a/source/Lottie/Instantiator.cs +++ b/source/Lottie/Instantiator.cs @@ -402,16 +402,27 @@ namespace CommunityToolkit.WinUI.Lottie foreach (var animator in source.Animators) { var animation = GetCompositionAnimation(animator.Animation); - target.StartAnimation(animator.AnimatedProperty, animation); - var controller = animator.Controller; - if (controller is not null) + if (animator.Controller is null || !animator.Controller.IsCustom) { - var animationController = GetAnimationController(controller); - if (controller.IsPaused) + target.StartAnimation(animator.AnimatedProperty, animation); + var controller = animator.Controller; + if (controller is not null) { - animationController.Pause(); + var animationController = GetAnimationController(controller); + if (controller.IsPaused) + { + animationController.Pause(); + } } } + else + { + throw new InvalidOperationException("LottieViewer and Instantiator does not support custom AnimationControllers yet"); + /* + We should retarget to SDK 22621 to support this + target.StartAnimation(animator.AnimatedProperty, animation, GetAnimationController(animator.Controller)); + */ + } } } @@ -422,9 +433,25 @@ namespace CommunityToolkit.WinUI.Lottie return result; } - var targetObject = GetCompositionObject(obj.TargetObject); + if (obj.IsCustom) + { + throw new InvalidOperationException("LottieViewer and Instantiator does not support custom AnimationControllers yet"); + /* + We should retarget to SDK 22621 to support this + result = CacheAndInitializeCompositionObject(obj, _c.CreateAnimationController()); + + if (obj.IsPaused) + { + result.Pause(); + } + */ + } + else + { + var targetObject = GetCompositionObject(obj.TargetObject!); + result = CacheAndInitializeCompositionObject(obj, targetObject.TryGetAnimationController(obj.TargetProperty)); + } - result = CacheAndInitializeCompositionObject(obj, targetObject.TryGetAnimationController(obj.TargetProperty)); StartAnimations(obj, result); return result; } diff --git a/source/Lottie/Loader.cs b/source/Lottie/Loader.cs index 7ab1a9a..e4e8481 100644 --- a/source/Lottie/Loader.cs +++ b/source/Lottie/Loader.cs @@ -219,17 +219,20 @@ namespace CommunityToolkit.WinUI.Lottie { // Start testing on version 2. We know that at least version 1 is supported because // we are running in UAP code. - var versionToTest = 2u; + var versionToTest = 1u; // Keep querying until IsApiContractPresent fails to find the version. - while (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", (ushort)versionToTest)) + while (ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", (ushort)(versionToTest + 1))) { // Keep looking ... versionToTest++; } + // TODO: we do not support UAP above 14 in Lottie-Windows yet, only in LottieGen. + versionToTest = Math.Min(versionToTest, 14); + // Query failed on versionToTest. Return the previous version. - return versionToTest - 1; + return versionToTest; } // Specializes the Stopwatch to do just the one thing we need of it - get the time diff --git a/source/Lottie/LottieVisualSource.cs b/source/Lottie/LottieVisualSource.cs index e379a78..ecb1252 100644 --- a/source/Lottie/LottieVisualSource.cs +++ b/source/Lottie/LottieVisualSource.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using System.Threading.Tasks; using CommunityToolkit.WinUI.Lottie; diff --git a/source/LottieToWinComp/Animate.cs b/source/LottieToWinComp/Animate.cs index 3e409b1..590c7b2 100644 --- a/source/LottieToWinComp/Animate.cs +++ b/source/LottieToWinComp/Animate.cs @@ -43,6 +43,13 @@ namespace CommunityToolkit.WinUI.Lottie.LottieToWinComp Debug.Assert(scale <= 1, "Precondition"); Debug.Assert(animation.KeyFrameCount > 0, "Precondition"); + if (scale == 1.0 && offset == 0.0 && context.ObjectFactory.IsUapApiAvailable(nameof(AnimationController)) && context.RootProgressController is not null) + { + // Special case when we can use root AnimationController (no time stretch, no time offset) + compObject.StartAnimation(target, animation, context.RootProgressController!); + return; + } + var state = context.GetStateCache(); // Start the animation ... diff --git a/source/LottieToWinComp/CompositionObjectFactory.cs b/source/LottieToWinComp/CompositionObjectFactory.cs index 6b9f8cb..d674a9a 100644 --- a/source/LottieToWinComp/CompositionObjectFactory.cs +++ b/source/LottieToWinComp/CompositionObjectFactory.cs @@ -86,6 +86,12 @@ namespace CommunityToolkit.WinUI.Lottie.LottieToWinComp case nameof(PathKeyFrameAnimation): return 11; + // AnimationController class was introduced in version 6, but + // it became possible to create it explicitly only after verstion 15 + // with compositor.CreateAnimationController() method + case nameof(AnimationController): + return 15; + default: throw new InvalidOperationException(); } @@ -304,5 +310,11 @@ namespace CommunityToolkit.WinUI.Lottie.LottieToWinComp HighestUapVersionUsed = Math.Max(HighestUapVersionUsed, uapVersion); } + + internal AnimationController CreateAnimationControllerList() + { + ConsumeVersionFeature(GetUapVersionForApi(nameof(AnimationController))); + return _compositor.CreateAnimationController(); + } } } \ No newline at end of file diff --git a/source/LottieToWinComp/TranslationContext.cs b/source/LottieToWinComp/TranslationContext.cs index 634289d..1b751a0 100644 --- a/source/LottieToWinComp/TranslationContext.cs +++ b/source/LottieToWinComp/TranslationContext.cs @@ -150,6 +150,11 @@ namespace CommunityToolkit.WinUI.Lottie.LottieToWinComp /// public ContainerVisual? RootVisual { get; private set; } + /// + /// AnimationController that has Progress property bound to RootVisual.Progress property. + /// + public AnimationController? RootProgressController { get; private set; } + /// /// True iff theme property bindings are enabled. /// @@ -223,6 +228,16 @@ namespace CommunityToolkit.WinUI.Lottie.LottieToWinComp // Add the master progress property to the visual. RootVisual.Properties.InsertScalar(ProgressPropertyName, 0); + // AnimationController that has Progress value bound to RootVisual.Progress + if (ObjectFactory.IsUapApiAvailable(nameof(AnimationController))) + { + RootProgressController = ObjectFactory.CreateAnimationControllerList(); + var rootProgressAnimation = context.ObjectFactory.CreateExpressionAnimation(ExpressionFactory.RootProgress); + rootProgressAnimation.SetReferenceParameter(ExpressionFactory.RootName, RootVisual); + RootProgressController.Pause(); + RootProgressController.StartAnimation("Progress", rootProgressAnimation); + } + // Add the translations of each layer to the root visual. This will recursively // add the tranlation of the layers in precomps. var contentsChildren = RootVisual.Children; diff --git a/source/UIData/Tools/GraphCompactor.cs b/source/UIData/Tools/GraphCompactor.cs index bf4d3ea..f7179be 100644 --- a/source/UIData/Tools/GraphCompactor.cs +++ b/source/UIData/Tools/GraphCompactor.cs @@ -700,16 +700,20 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools if (visibilityController is not null) { var animator = TryGetAnimatorByPropertyName(visibilityController, "Progress"); - if (animator is null) + + if (visibilityController.IsCustom) + { + ApplyVisibility(parent, GetVisiblityAnimationDescription(visual), null, visibilityController); + } + else if (animator is not null) + { + ApplyVisibility(parent, GetVisiblityAnimationDescription(visual), animator.Animation, null); + } + else { throw new InvalidOperationException(); } - ApplyVisibility( - parent, - GetVisiblityAnimationDescription(visual), - animator.Animation); - // Clear out the visibility property and animation from the visual. visual.IsVisible = null; visual.StopAnimation("IsVisible"); @@ -790,13 +794,20 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools if (visibilityController is not null) { var animator = TryGetAnimatorByPropertyName(visibilityController, "Progress"); - if (animator is null) + + if (visibilityController.IsCustom) + { + ApplyVisibility(visual, GetVisiblityAnimationDescription(shape), null, visibilityController); + } + else if (animator is not null) + { + ApplyVisibility(visual, GetVisiblityAnimationDescription(shape), animator.Animation, null); + } + else { throw new InvalidOperationException(); } - ApplyVisibility(visual, GetVisiblityAnimationDescription(shape), animator.Animation); - // Clear out the Scale properties and animations from the shape. shape.Scale = null; shape.StopAnimation("Scale"); @@ -806,8 +817,10 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools // Applies the given visibility to the given Visual, combining it with the // visibility it already has. - void ApplyVisibility(Visual to, VisibilityDescription fromVisibility, CompositionAnimation progressAnimation) + void ApplyVisibility(Visual to, VisibilityDescription fromVisibility, CompositionAnimation? progressAnimation, AnimationController? customController) { + Debug.Assert(progressAnimation is not null || customController is not null, "Precondition"); + var toVisibility = GetVisiblityAnimationDescription(to); var compositeVisibility = VisibilityDescription.Compose(fromVisibility, toVisibility); @@ -829,11 +842,17 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools animation.InsertKeyFrame(progress, isVisible); } - to.StartAnimation("IsVisible", animation); - - var controller = to.TryGetAnimationController("IsVisible")!; - controller.Pause(); - controller.StartAnimation("Progress", progressAnimation); + if (progressAnimation is not null) + { + to.StartAnimation("IsVisible", animation); + var controller = to.TryGetAnimationController("IsVisible")!; + controller.Pause(); + controller.StartAnimation("Progress", progressAnimation); + } + else + { + to.StartAnimation("IsVisible", animation, customController); + } } } @@ -1326,8 +1345,15 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools // Start the from's animations on the to. foreach (var anim in from.Animators) { - to.StartAnimation(anim.AnimatedProperty, anim.Animation); - if (anim.Controller is not null && (anim.Controller.IsPaused || anim.Controller.Animators.Count > 0)) + if (anim.Controller is null || !anim.Controller.IsCustom) + { + to.StartAnimation(anim.AnimatedProperty, anim.Animation); + } else + { + to.StartAnimation(anim.AnimatedProperty, anim.Animation, anim.Controller); + } + + if (anim.Controller is not null && !anim.Controller.IsCustom && (anim.Controller.IsPaused || anim.Controller.Animators.Count > 0)) { var controller = to.TryGetAnimationController(anim.AnimatedProperty)!; if (anim.Controller.IsPaused) @@ -1337,7 +1363,14 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools foreach (var controllerAnim in anim.Controller.Animators) { - controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation); + if (controllerAnim.Controller is null || !controllerAnim.Controller.IsCustom) + { + controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation); + } + else + { + controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation, controllerAnim.Controller); + } } } } @@ -1383,8 +1416,16 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools // Start the from's animations on the to. foreach (var anim in from.Animators) { - to.StartAnimation(anim.AnimatedProperty, anim.Animation); - if (anim.Controller is not null && (anim.Controller.IsPaused || anim.Controller.Animators.Count > 0)) + if (anim.Controller is null || !anim.Controller.IsCustom) + { + to.StartAnimation(anim.AnimatedProperty, anim.Animation); + } + else + { + to.StartAnimation(anim.AnimatedProperty, anim.Animation, anim.Controller); + } + + if (anim.Controller is not null && !anim.Controller.IsCustom && (anim.Controller.IsPaused || anim.Controller.Animators.Count > 0)) { var controller = to.TryGetAnimationController(anim.AnimatedProperty)!; if (anim.Controller.IsPaused) @@ -1394,7 +1435,14 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools foreach (var controllerAnim in anim.Controller.Animators) { - controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation); + if (controllerAnim.Controller is null || !controllerAnim.Controller.IsCustom) + { + controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation); + } + else + { + controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation, controllerAnim.Controller); + } } } } diff --git a/source/UIData/Tools/ObjectGraph.cs b/source/UIData/Tools/ObjectGraph.cs index 705ad24..cd3dac2 100644 --- a/source/UIData/Tools/ObjectGraph.cs +++ b/source/UIData/Tools/ObjectGraph.cs @@ -363,6 +363,12 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools return true; } + bool VisitAnimationControllerList(AnimationController obj, T node) + { + VisitCompositionObject(obj, node); + return true; + } + bool VisitCanvasGeometry(CanvasGeometry obj, T node) { return true; diff --git a/source/UIData/Tools/Optimizer.cs b/source/UIData/Tools/Optimizer.cs index e0c50dc..10e54e8 100644 --- a/source/UIData/Tools/Optimizer.cs +++ b/source/UIData/Tools/Optimizer.cs @@ -440,13 +440,21 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools foreach (var animator in source.Animators) { var animation = GetCompositionAnimation(animator.Animation); + var controller = animator.Controller; // Freeze the animation to indicate that it will not be mutated further. This // will ensure that it does not need to be copied when target.StartAnimation is called. animation.Freeze(); - target.StartAnimation(animator.AnimatedProperty, animation); - var controller = animator.Controller; - if (controller is not null) + if (controller is null || !controller.IsCustom) + { + target.StartAnimation(animator.AnimatedProperty, animation); + } + else + { + target.StartAnimation(animator.AnimatedProperty, animation, GetAnimationController(controller)); + } + + if (controller is not null && !controller.IsCustom) { var animationController = GetAnimationController(controller); if (controller.IsPaused) @@ -464,9 +472,22 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools return result; } - var targetObject = GetCompositionObject(obj.TargetObject); + if (obj.IsCustom) + { + result = CacheAndInitializeCompositionObject(obj, _c.CreateAnimationController()); + + if (obj.IsPaused) + { + result.Pause(); + } + } + else + { + var targetObject = GetCompositionObject(obj.TargetObject!); + + result = CacheAndInitializeCompositionObject(obj, targetObject.TryGetAnimationController(obj.TargetProperty!)!); + } - result = CacheAndInitializeCompositionObject(obj, targetObject.TryGetAnimationController(obj.TargetProperty)!); StartAnimationsAndFreeze(obj, result); return result; } diff --git a/source/UIData/Tools/Stats.cs b/source/UIData/Tools/Stats.cs index 2957ebc..1771a46 100644 --- a/source/UIData/Tools/Stats.cs +++ b/source/UIData/Tools/Stats.cs @@ -160,6 +160,8 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools } } + public int AnimationControllerListCount { get; } + public int AnimationControllerCount { get; } public int AnimatorCount { get; } diff --git a/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtInstantiatorGenerator.cs b/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtInstantiatorGenerator.cs index 7edd061..e3935fd 100644 --- a/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtInstantiatorGenerator.cs +++ b/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtInstantiatorGenerator.cs @@ -1092,7 +1092,10 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen.Cppwinrt builder.WriteBreakableLine($"auto result = winrt::make<{info.ClassName}>(", CommaSeparate(GetConstructorArguments(info)), ");"); if (info.ImplementCreateAndDestroyMethods) { - builder.WriteLine($"result.{CreateAnimationsMethod}();"); + builder.WriteLine($"if (auto result2 = result.try_as<{_animatedVisualTypeName2}>())"); + builder.OpenScope(); + builder.WriteLine($"result2.{CreateAnimationsMethod}();"); + builder.CloseScope(); } builder.WriteLine("return result;"); diff --git a/source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs b/source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs index b9f6efd..6087b9c 100644 --- a/source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs +++ b/source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs @@ -1134,7 +1134,7 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen node.RequiresStorage = true; } } - else if (configuration.ImplementCreateAndDestroyMethods && node.Object is CompositionObject obj && obj.Animators.Count > 0) + else if ((configuration.ImplementCreateAndDestroyMethods && node.Object is CompositionObject obj && obj.Animators.Count > 0) || (node.Object is AnimationController c && c.IsCustom)) { // If we are implementing IAnimatedVisual2 interface we need to store all the composition objects that have animators. node.RequiresStorage = true; @@ -1718,7 +1718,7 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen return obj.Type switch { // Do not generate code for animation controllers. It is done inline in the CompositionObject initialization. - CompositionObjectType.AnimationController => throw new InvalidOperationException(), + CompositionObjectType.AnimationController => GenerateCustomAnimationController(builder, (AnimationController)obj, node), CompositionObjectType.BooleanKeyFrameAnimation => GenerateBooleanKeyFrameAnimationFactory(builder, (BooleanKeyFrameAnimation)obj, node), CompositionObjectType.ColorKeyFrameAnimation => GenerateColorKeyFrameAnimationFactory(builder, (ColorKeyFrameAnimation)obj, node), CompositionObjectType.CompositionColorBrush => GenerateCompositionColorBrushFactory(builder, (CompositionColorBrush)obj, node), @@ -2002,10 +2002,17 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen if (_configuration.ImplementCreateAndDestroyMethods) { - _createAnimationsCodeBuilder - .WriteLine($"{localName}{Deref}StartAnimation({String(animator.AnimatedProperty)}, {animationFactoryCall});"); - - ConfigureAnimationController(_createAnimationsCodeBuilder, localName, ref controllerVariableAdded, animator); + if (animator.Controller is not null && animator.Controller.IsCustom) + { + _createAnimationsCodeBuilder + .WriteLine($"{localName}{Deref}StartAnimation({String(animator.AnimatedProperty)}, {animationFactoryCall}, {CallFactoryFromFor(node, NodeFor(animator.Controller))});"); + } + else + { + _createAnimationsCodeBuilder + .WriteLine($"{localName}{Deref}StartAnimation({String(animator.AnimatedProperty)}, {animationFactoryCall});"); + ConfigureAnimationController(_createAnimationsCodeBuilder, localName, ref controllerVariableAdded, animator); + } // If we are implementing IAnimatedVisual2 we should also write a destruction call. _destroyAnimationsCodeBuilder @@ -2013,8 +2020,15 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen } else { - builder.WriteLine($"{localName}{Deref}StartAnimation({String(animator.AnimatedProperty)}, {animationFactoryCall});"); - ConfigureAnimationController(builder, localName, ref controllerVariableAdded, animator); + if (animator.Controller is not null && animator.Controller.IsCustom) + { + builder.WriteLine($"{localName}{Deref}StartAnimation({String(animator.AnimatedProperty)}, {animationFactoryCall}, {CallFactoryFromFor(node, NodeFor(animator.Controller))});"); + } + else + { + builder.WriteLine($"{localName}{Deref}StartAnimation({String(animator.AnimatedProperty)}, {animationFactoryCall});"); + ConfigureAnimationController(builder, localName, ref controllerVariableAdded, animator); + } } } } @@ -2528,6 +2542,27 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen InitializeCompositionAnimation(builder, animation, node); } + bool GenerateCustomAnimationController(CodeBuilder builder, AnimationController obj, ObjectData node) + { + if (!obj.IsCustom) + { + throw new InvalidOperationException(); + } + + WriteObjectFactoryStart(builder, node); + + WriteCreateAssignment(builder, node, $"_c{Deref}Create{obj.Type}()"); + + if (obj.IsPaused) + { + builder.WriteLine($"result{Deref}Pause();"); + } + + WriteCompositionObjectFactoryEnd(builder, obj, node); + + return true; + } + bool GenerateBooleanKeyFrameAnimationFactory(CodeBuilder builder, BooleanKeyFrameAnimation obj, ObjectData node) { WriteObjectFactoryStart(builder, node); @@ -3442,7 +3477,7 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen { // AnimationController is never created explicitly - they result from // calling TryGetAnimationController(...). - CompositionObjectType.AnimationController => false, + CompositionObjectType.AnimationController => ((AnimationController)obj).IsCustom, // CompositionPropertySet is never created explicitly - they just exist // on the Properties property of every CompositionObject. diff --git a/source/WinCompData/AnimationController.cs b/source/WinCompData/AnimationController.cs index e15caa0..e358035 100644 --- a/source/WinCompData/AnimationController.cs +++ b/source/WinCompData/AnimationController.cs @@ -16,9 +16,18 @@ namespace CommunityToolkit.WinUI.Lottie.WinCompData TargetProperty = targetProperty; } - public CompositionObject TargetObject { get; } + internal AnimationController() + { + } - public string TargetProperty { get; } + // AnimationController can be created separately from an composition animation, + // in this case it will be marked as "Custom", it does not have TargetObject and TargetProperty. + // Custom controller should be configured only once and then can be used for many animations. + public bool IsCustom => TargetObject is null; + + public CompositionObject? TargetObject { get; } + + public string? TargetProperty { get; } public bool IsPaused { get; private set; } diff --git a/source/WinCompData/CompositionObject.cs b/source/WinCompData/CompositionObject.cs index 2c95452..e01d8c2 100644 --- a/source/WinCompData/CompositionObject.cs +++ b/source/WinCompData/CompositionObject.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace CommunityToolkit.WinUI.Lottie.WinCompData @@ -116,11 +117,13 @@ namespace CommunityToolkit.WinUI.Lottie.WinCompData public CompositionPropertySet Properties { get; } /// - /// Binds an animation to a property. + /// Binds an animation to a property with a given custom controller. /// /// The name of the property. /// The animation. - public void StartAnimation(string target, CompositionAnimation animation) + /// Custom controller. + /// New animator. + public Animator StartAnimation(string target, CompositionAnimation animation, AnimationController? customController) { // Remove any existing animation. StopAnimation(target); @@ -133,6 +136,13 @@ namespace CommunityToolkit.WinUI.Lottie.WinCompData ? null : new AnimationController(this, target); + if (customController is not null) + { + Debug.Assert(customController.IsCustom, "Should be custom!"); + Debug.Assert(animation is not ExpressionAnimation, "Should not be ExpressionAnimation!"); + controller = customController; + } + var animator = new Animator( animatedProperty: target, animatedObject: this, @@ -140,8 +150,17 @@ namespace CommunityToolkit.WinUI.Lottie.WinCompData controller: controller); _animators.Add(animator); + return animator; } + /// + /// Binds an animation to a property. + /// + /// The name of the property. + /// The animation. + /// New animator. + public Animator StartAnimation(string target, CompositionAnimation animation) => StartAnimation(target, animation, null); + /// /// Stops an animation that was previously started. /// @@ -230,7 +249,7 @@ namespace CommunityToolkit.WinUI.Lottie.WinCompData /// The controller for this or null /// if the animation is an . /// - public AnimationController? Controller { get; } + public AnimationController? Controller { get; private set; } /// public override string ToString() => AnimatedProperty; diff --git a/source/WinCompData/Compositor.cs b/source/WinCompData/Compositor.cs index 7324e2d..fafa825 100644 --- a/source/WinCompData/Compositor.cs +++ b/source/WinCompData/Compositor.cs @@ -13,6 +13,8 @@ namespace CommunityToolkit.WinUI.Lottie.WinCompData #endif sealed class Compositor { + public AnimationController CreateAnimationController() => new AnimationController(); + public BooleanKeyFrameAnimation CreateBooleanKeyFrameAnimation() => new BooleanKeyFrameAnimation(); public CompositionColorBrush CreateColorBrush() => new CompositionColorBrush();