[LottieGen] Added new AnimationController API support for LottieGen (#488)

This commit is contained in:
aborziak-ms 2023-02-27 13:51:11 -08:00 коммит произвёл GitHub
Родитель a3ee250679
Коммит 56f986c233
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 283 добавлений и 55 удалений

Просмотреть файл

@ -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'

Просмотреть файл

@ -33,6 +33,7 @@ namespace CommunityToolkit.WinUI.Lottie
Loader? _loader;
WinCompData.Visual? _wincompDataRootVisual;
WinCompData.CompositionPropertySet? _wincompDataThemingPropertySet;
IEnumerable<WinCompData.AnimationController>? _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<AnimationController> 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,

Просмотреть файл

@ -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<AnimationController> customAnimationControllers)
{
RootVisual = rootVisual;
CustomAnimationControllers = customAnimationControllers;
}
public Visual RootVisual { get; }
/// <summary>
/// Keeps references to all custom AnimationController objects.
/// We need references because otherwise they will be destroyed from dwm.
/// </summary>
public IEnumerable<AnimationController> CustomAnimationControllers { get; }
public TimeSpan Duration { get; set; }
public Vector2 Size { get; set; }

Просмотреть файл

@ -402,6 +402,8 @@ namespace CommunityToolkit.WinUI.Lottie
foreach (var animator in source.Animators)
{
var animation = GetCompositionAnimation(animator.Animation);
if (animator.Controller is null || !animator.Controller.IsCustom)
{
target.StartAnimation(animator.AnimatedProperty, animation);
var controller = animator.Controller;
if (controller is not null)
@ -413,6 +415,15 @@ namespace CommunityToolkit.WinUI.Lottie
}
}
}
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));
*/
}
}
}
Wc.AnimationController GetAnimationController(Wd.AnimationController obj)
@ -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));
}
StartAnimations(obj, result);
return result;
}

Просмотреть файл

@ -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

Просмотреть файл

@ -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;

Просмотреть файл

@ -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<StateCache>();
// Start the animation ...

Просмотреть файл

@ -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();
}
}
}

Просмотреть файл

@ -150,6 +150,11 @@ namespace CommunityToolkit.WinUI.Lottie.LottieToWinComp
/// </summary>
public ContainerVisual? RootVisual { get; private set; }
/// <summary>
/// AnimationController that has Progress property bound to RootVisual.Progress property.
/// </summary>
public AnimationController? RootProgressController { get; private set; }
/// <summary>
/// True iff theme property bindings are enabled.
/// </summary>
@ -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;

Просмотреть файл

@ -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,12 +842,18 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools
animation.InsertKeyFrame(progress, isVisible);
}
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);
}
}
}
// Returns a description of the visibility over time of the given visual.
@ -1325,9 +1344,16 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools
// Start the from's animations on the to.
foreach (var anim in from.Animators)
{
if (anim.Controller is null || !anim.Controller.IsCustom)
{
to.StartAnimation(anim.AnimatedProperty, anim.Animation);
if (anim.Controller is not null && (anim.Controller.IsPaused || anim.Controller.Animators.Count > 0))
} 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)
@ -1336,9 +1362,16 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools
}
foreach (var controllerAnim in anim.Controller.Animators)
{
if (controllerAnim.Controller is null || !controllerAnim.Controller.IsCustom)
{
controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation);
}
else
{
controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation, controllerAnim.Controller);
}
}
}
}
}
@ -1382,9 +1415,17 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools
// Start the from's animations on the to.
foreach (var anim in from.Animators)
{
if (anim.Controller is null || !anim.Controller.IsCustom)
{
to.StartAnimation(anim.AnimatedProperty, anim.Animation);
if (anim.Controller is not null && (anim.Controller.IsPaused || anim.Controller.Animators.Count > 0))
}
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)
@ -1393,9 +1434,16 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools
}
foreach (var controllerAnim in anim.Controller.Animators)
{
if (controllerAnim.Controller is null || !controllerAnim.Controller.IsCustom)
{
controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation);
}
else
{
controller.StartAnimation(controllerAnim.AnimatedProperty, controllerAnim.Animation, controllerAnim.Controller);
}
}
}
}
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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();
if (controller is null || !controller.IsCustom)
{
target.StartAnimation(animator.AnimatedProperty, animation);
var controller = animator.Controller;
if (controller is not null)
}
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;
}

Просмотреть файл

@ -160,6 +160,8 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.Tools
}
}
public int AnimationControllerListCount { get; }
public int AnimationControllerCount { get; }
public int AnimatorCount { get; }

Просмотреть файл

@ -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;");

Просмотреть файл

@ -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),
@ -2001,23 +2001,37 @@ namespace CommunityToolkit.WinUI.Lottie.UIData.CodeGen
}
if (_configuration.ImplementCreateAndDestroyMethods)
{
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
.WriteLine($"{localName}{Deref}StopAnimation({String(animator.AnimatedProperty)});");
}
else
{
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);
}
}
}
}
void ConfigureAnimationController(CodeBuilder builder, string localName, ref bool controllerVariableAdded, CompositionObject.Animator 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.

Просмотреть файл

@ -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; }

Просмотреть файл

@ -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; }
/// <summary>
/// Binds an animation to a property.
/// Binds an animation to a property with a given custom controller.
/// </summary>
/// <param name="target">The name of the property.</param>
/// <param name="animation">The animation.</param>
public void StartAnimation(string target, CompositionAnimation animation)
/// <param name="customController">Custom controller.</param>
/// <returns>New animator.</returns>
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;
}
/// <summary>
/// Binds an animation to a property.
/// </summary>
/// <param name="target">The name of the property.</param>
/// <param name="animation">The animation.</param>
/// <returns>New animator.</returns>
public Animator StartAnimation(string target, CompositionAnimation animation) => StartAnimation(target, animation, null);
/// <summary>
/// Stops an animation that was previously started.
/// </summary>
@ -230,7 +249,7 @@ namespace CommunityToolkit.WinUI.Lottie.WinCompData
/// The controller for this <see cref="Animator"/> or null
/// if the animation is an <see cref="ExpressionAnimation"/>.
/// </summary>
public AnimationController? Controller { get; }
public AnimationController? Controller { get; private set; }
/// <inheritdoc/>
public override string ToString() => AnimatedProperty;

Просмотреть файл

@ -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();