From 1832ef714ba651a1543325205efbcc3678fc2b31 Mon Sep 17 00:00:00 2001 From: Simeon Date: Fri, 16 Oct 2020 12:13:56 -0700 Subject: [PATCH] =?UTF-8?q?Generate=20a=20way=20to=20convert=20between=20m?= =?UTF-8?q?arker=20names=20and=20progress=20values,=20a=E2=80=A6=20(#367)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Generate a way to convert between marker names and progress values, and set bound colors. To use this without having to cast to a particular class type, use the -AdditionalInterface command line option in LottieGen. Cppwinrt example: LottieGen.exe -inp MyLottieFile -AdditionalInterface MyNamespace.IMyInterface -l cppwinrt ...then in your cppwinrt project, add an IDL file that defines MyNamespace.IMyInterface and declares the methods you want to access, e.g.: namespace MyNamespace { interface IMyInterface { Windows.Foundation.Collections.IMapView Markers { get; }; void SetColorProperty(String propertyName, Windows.UI.Color value); }; } ...and finally, reference the interface from your code with an include of "MyNamespace.IMyInterface.h". --- LottieGen/CommandLineOptions.cs | 2 +- .../CompositionObjectFactory.cs | 2 + source/UIData/Tools/GraphCompactor.cs | 6 + .../CSharp/CSharpInstantiatorGenerator.cs | 144 ++++++++++-- .../Cppwinrt/CppwinrtInstantiatorGenerator.cs | 219 ++++++++++++++---- .../CodeGen/Cppwinrt/CppwinrtStringifier.cs | 2 +- .../CodeGen/Cx/CxInstantiatorGenerator.cs | 185 ++++++++++++--- .../CodeGen/IAnimatedVisualSourceInfo.cs | 7 +- .../CodeGen/InstantiatorGeneratorBase.cs | 16 +- source/UIDataCodeGen/CodeGen/MarkerInfo.cs | 3 + 10 files changed, 476 insertions(+), 110 deletions(-) diff --git a/LottieGen/CommandLineOptions.cs b/LottieGen/CommandLineOptions.cs index b5210cf..79847cc 100644 --- a/LottieGen/CommandLineOptions.cs +++ b/LottieGen/CommandLineOptions.cs @@ -238,7 +238,7 @@ sealed class CommandLineOptions result.Languages = languages.Distinct().ToArray(); // Sort any additional interfaces and remove duplicates. - var additionalInterfaces = result._additionalInterfaces.OrderBy(name => name).Distinct(); + var additionalInterfaces = result._additionalInterfaces.OrderBy(name => name).Distinct().ToArray(); result._additionalInterfaces.Clear(); result._additionalInterfaces.AddRange(additionalInterfaces); diff --git a/source/LottieToWinComp/CompositionObjectFactory.cs b/source/LottieToWinComp/CompositionObjectFactory.cs index 8d242f8..d8141c6 100644 --- a/source/LottieToWinComp/CompositionObjectFactory.cs +++ b/source/LottieToWinComp/CompositionObjectFactory.cs @@ -2,6 +2,8 @@ // 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 System.Diagnostics; diff --git a/source/UIData/Tools/GraphCompactor.cs b/source/UIData/Tools/GraphCompactor.cs index 311e27b..77e554f 100644 --- a/source/UIData/Tools/GraphCompactor.cs +++ b/source/UIData/Tools/GraphCompactor.cs @@ -1133,6 +1133,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.Tools // If childCount is >1, insert into the parent. var index = parent.Children.IndexOf(container); + if (index == -1) + { + // Container has already been removed. + continue; + } + var children = container.Children; // Get the children from the container. diff --git a/source/UIDataCodeGen/CodeGen/CSharp/CSharpInstantiatorGenerator.cs b/source/UIDataCodeGen/CodeGen/CSharp/CSharpInstantiatorGenerator.cs index 269f386..89042d2 100644 --- a/source/UIDataCodeGen/CodeGen/CSharp/CSharpInstantiatorGenerator.cs +++ b/source/UIDataCodeGen/CodeGen/CSharp/CSharpInstantiatorGenerator.cs @@ -111,6 +111,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.CSharp } namespaces.Add("System"); + namespaces.Add("System.Collections.Generic"); namespaces.Add("System.Numerics"); // Windows.UI is needed for Color. @@ -142,7 +143,18 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.CSharp WriteIAnimatedVisualSource(builder); } - builder.CloseScope(); + builder.WriteLine(); + + WriteFrameToProgressImpl(builder); + builder.WriteLine(); + + WriteMarkersPropertyImpl(builder); + builder.WriteLine(); + + WriteSetColorPropertyImpl(builder); + builder.WriteLine(); + + WriteSetScalarPropertyImpl(builder); builder.WriteLine(); } @@ -195,7 +207,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.CSharp { foreach (var prop in SourceInfo.SourceMetadata.PropertyBindings) { - builder.WriteComment($"Dependency property for {prop.BindingName}."); + builder.WriteSummaryComment($"Dependency property for {prop.BindingName}."); builder.WriteLine($"public static readonly DependencyProperty {prop.BindingName}Property ="); builder.Indent(); builder.WriteLine($"DependencyProperty.Register({_s.String(prop.BindingName)}, typeof({ExposedType(prop)}), typeof({SourceInfo.ClassName}),"); @@ -218,7 +230,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.CSharp { builder.WriteLine($"static void On{prop.BindingName}Changed(DependencyObject d, DependencyPropertyChangedEventArgs args)"); builder.OpenScope(); - WriteThemePropertyInitialization(builder, $"(({SourceInfo.ClassName})d)._themeProperties?", prop, $"({ExposedType(prop)})args.NewValue"); + WriteThemePropertyInitialization(builder, $"(({SourceInfo.ClassName})d).{SourceInfo.ThemePropertiesFieldName}?", prop, $"({ExposedType(prop)})args.NewValue"); builder.CloseScope(); builder.WriteLine(); } @@ -310,6 +322,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.CSharp builder.WriteLine("return null;"); } + + builder.CloseScope(); } void WriteThemeMethodsAndFields(CodeBuilder builder) @@ -342,26 +356,10 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.CSharp WriteThemeProperties(builder); } - // The GetThemeProperties method is designed to allow setting of properties when the actual - // type of the IAnimatedVisualSource is not known. It relies on a custom interface that declares - // it, so if we're not generating code for a custom interface, there's no reason to generate - // the method. - if (SourceInfo.IsInterfaceCustom) - { - builder.WriteLine("public CompositionPropertySet GetThemeProperties(Compositor compositor)"); - builder.OpenScope(); - builder.WriteLine("return EnsureThemeProperties(compositor);"); - builder.CloseScope(); - builder.WriteLine(); - } - if (SourceInfo.SourceMetadata.PropertyBindings.Any(pb => pb.ExposedType == PropertySetValueType.Color)) { // There's at least one themed color. They will need a helper method to convert to Vector4. - // If we're generating a custom interface then users may want to use GetThemeProperties - // to set a property color, so in that case make the helper method available to them. - var visibility = SourceInfo.IsInterfaceCustom ? "internal " : string.Empty; - builder.WriteLine($"{visibility}static Vector4 ColorAsVector4(Color color) => new Vector4(color.R, color.G, color.B, color.A);"); + builder.WriteLine("static Vector4 ColorAsVector4(Color color) => new Vector4(color.R, color.G, color.B, color.A);"); builder.WriteLine(); } @@ -382,7 +380,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.CSharp } builder.CloseScope(); - builder.WriteLine("return _themeProperties;"); + builder.WriteLine($"return {SourceInfo.ThemePropertiesFieldName};"); builder.CloseScope(); builder.WriteLine(); } @@ -553,6 +551,110 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.CSharp builder.WriteLine("EventRegistrationTokenTable> GetAnimatedVisualInvalidatedEventRegistrationTokenTable()"); builder.OpenScope(); builder.WriteLine("return EventRegistrationTokenTable>.GetOrCreateEventRegistrationTokenTable(ref _animatedVisualInvalidatedEventTokenTable);"); + builder.CloseScope(); + } + + /// + /// Generates the FrameToProgress(...) implementation. + /// + void WriteFrameToProgressImpl(CodeBuilder builder) + { + builder.WriteSummaryComment("Converts a frame number to the corresponding progress value."); + builder.WriteLine($"public double FrameToProgress(double frameNumber)"); + builder.OpenScope(); + builder.WriteLine($"return frameNumber / {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.Frames)};"); + builder.CloseScope(); + } + + /// + /// Generates the Markers property implementation. + /// + void WriteMarkersPropertyImpl(CodeBuilder builder) + { + builder.WriteSummaryComment("Returns a map from marker names to corresponding progress values."); + builder.WriteLine($"public IReadOnlyDictionary Markers =>"); + builder.Indent(); + builder.WriteLine("new Dictionary"); + builder.OpenScope(); + foreach (var marker in SourceInfo.Markers) + { + builder.WriteLine($"{{ {_s.String(marker.Name)}, {_s.Double(marker.StartProgress)} }},"); + } + + builder.CloseCppTypeScope(); + builder.UnIndent(); + } + + /// + /// Generates the SetColorProperty(...) method implementation. + /// + void WriteSetColorPropertyImpl(CodeBuilder builder) => + WriteSetPropertyImpl( + builder, + PropertySetValueType.Color, + "Color", + "Sets the color property with the given name, or does nothing if no such property exists."); + + /// + /// Generates the SetScalarProperty(...) method implementation. + /// + void WriteSetScalarPropertyImpl(CodeBuilder builder) => + WriteSetPropertyImpl( + builder, + PropertySetValueType.Scalar, + "double", + "Sets the scalar property with the given name, or does nothing if no such property exists."); + + void WriteSetPropertyImpl( + CodeBuilder builder, + PropertySetValueType propertyType, + string typeName, + string comment) + { + builder.WriteSummaryComment(comment); + var properties = SourceInfo.SourceMetadata.PropertyBindings.Where(p => p.ExposedType == propertyType).ToArray(); + + builder.WriteLine($"public void Set{propertyType}Property(string propertyName, {typeName} value)"); + if (properties.Length == 0) + { + // There are no properties. The method doesn't need to do anything. + builder.OpenScope(); + builder.CloseScope(); + } + else + { + var propertySetTypeName = PropertySetValueTypeName(properties[0].ActualType); + var valueInitializer = properties[0].ExposedType == PropertySetValueType.Color ? "ColorAsVector4(value)" : "value"; + + builder.OpenScope(); + + var firstSeen = false; + foreach (var prop in properties) + { + // If the propertyName is a known name, save it into its backing field. + builder.WriteLine($"{(firstSeen ? "else " : string.Empty)}if (propertyName == {_s.String(prop.BindingName)})"); + firstSeen = true; + builder.OpenScope(); + builder.WriteLine($"_theme{prop.BindingName} = value;"); + builder.CloseScope(); + } + + builder.WriteLine("else"); + builder.OpenScope(); + + // Ignore the name if it doesn't refer to a known property. + builder.WriteLine("return;"); + builder.CloseScope(); + builder.WriteLine(); + + // Update the CompositionPropertySet if it has been created. + builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != null)"); + builder.OpenScope(); + + builder.WriteLine($"{SourceInfo.ThemePropertiesFieldName}.Insert{propertySetTypeName}(propertyName, {valueInitializer});"); + builder.CloseScope(); + builder.CloseScope(); + } } /// diff --git a/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtInstantiatorGenerator.cs b/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtInstantiatorGenerator.cs index 5c56f13..22e19d8 100644 --- a/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtInstantiatorGenerator.cs +++ b/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtInstantiatorGenerator.cs @@ -112,20 +112,20 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt { var builder = new CodeBuilder(); builder.WriteLine(string.Join("\r\n", AutoGeneratedHeaderText)); - var imports = new List(); + var idlImports = new List(); if (SourceInfo.IsInterfaceCustom) { - imports.Add($"import \"{SourceInfo.InterfaceType.NormalizedQualifiedName}.idl\";"); + idlImports.Add(SourceInfo.InterfaceType.NormalizedQualifiedName); } - imports.AddRange(SourceInfo.AdditionalInterfaces.Select(n => n.GetQualifiedName(_s))); + idlImports.AddRange(SourceInfo.AdditionalInterfaces.Select(n => n.NormalizedQualifiedName)); - if (imports.Any()) + if (idlImports.Any()) { - foreach (var import in imports) + foreach (var import in idlImports) { - builder.WriteLine(import); + builder.WriteLine($"import \"{import}.idl\";"); } builder.WriteLine(); @@ -161,7 +161,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt builder.WriteLine(", Windows.UI.Xaml.Data.INotifyPropertyChanged"); } - foreach (var additionalInterface in SourceInfo.AdditionalInterfaces) + foreach (var additionalInterface in SourceInfo.AdditionalInterfaces.Select(n => n.NormalizedQualifiedName)) { builder.WriteLine($", {additionalInterface}"); } @@ -230,9 +230,29 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt { WriteIDynamicAnimatedVisualSourceHeaderText(builder); } - else + + WriteTryCreateAnimatedVisualDecl(builder.Class.Public); + + builder.Class.Public.WriteLine(); + + WriteFrameToProgressDecl(builder.Class.Public); + + builder.Class.Public.WriteLine(); + + WriteMarkersPropertyDecl(builder.Class.Public); + + builder.Class.Public.WriteLine(); + + WriteSetColorPropertyDecl(builder.Class.Public); + + builder.Class.Public.WriteLine(); + + WriteSetScalarPropertyDecl(builder.Class.Public); + + if (SourceInfo.IsThemed) { - WriteIAnimatedVisualSourceHeaderText(builder); + builder.Class.Private.WriteLine(); + builder.Class.Private.WriteLine($"static winrt::Windows::Foundation::Numerics::float4 ColorAsVector4(winrt::Windows::UI::Color color);"); } // Close the ::implementation namespace. @@ -292,7 +312,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt void WriteSourceDescriptionComments(CodeBuilder builder) => builder.WritePreformattedCommentLines(GetSourceDescriptionLines()); - void WriteTryCreateAnimatedVisualDeclaration(CodeBuilder builder) + void WriteTryCreateAnimatedVisualDecl(CodeBuilder builder) { builder.WriteLine($"winrt::{_animatedVisualTypeName} TryCreateAnimatedVisual("); builder.Indent(); @@ -301,22 +321,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt builder.UnIndent(); } - void WriteIAnimatedVisualSourceHeaderText(HeaderBuilder builder) - { - WriteTryCreateAnimatedVisualDeclaration(builder.Class.Public); - - if (SourceInfo.IsThemed) - { - // The GetThemeProperties method is designed to allow setting of properties when the actual - // type of the IAnimatedVisualSource is not known. - builder.Class.Public.WriteLine(); - builder.Class.Public.WriteLine($"winrt::{_wuc}::CompositionPropertySet GetThemeProperties(winrt::{_wuc}::Compositor compositor);"); - - builder.Class.Public.WriteLine(); - builder.Class.Public.WriteLine($"static winrt::Windows::Foundation::Numerics::float4 ColorAsVector4(winrt::Windows::UI::Color color);"); - } - } - void WriteThemeHeader(HeaderBuilder builder) { // Add a field to hold the theme property set. @@ -348,7 +352,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt } } - // EnsureThemeProperties is private. GetThemeProperties is the public version. + // EnsureThemeProperties is private. builder.Class.Private.WriteLine($"winrt::{_wuc}::CompositionPropertySet EnsureThemeProperties(winrt::{_wuc}::Compositor compositor);"); // Write properties declarations for each themed property. @@ -366,19 +370,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt { var propertyBindings = SourceInfo.SourceMetadata.PropertyBindings; - var sourceClassQualifier = $"{_sourceClassName}::"; - if (propertyBindings.Any(pb => pb.ExposedType == PropertySetValueType.Color)) { // Write the helper for converting a color to a vector 4. - builder.WriteLine($"float4 {sourceClassQualifier}ColorAsVector4(Color color)"); + builder.WriteLine($"float4 {_sourceClassName}::ColorAsVector4(Color color)"); builder.OpenScope(); builder.WriteLine("return { static_cast(color.R), static_cast(color.G), static_cast(color.B), static_cast(color.A) };"); builder.CloseScope(); builder.WriteLine(); } - builder.WriteLine($"CompositionPropertySet {sourceClassQualifier}EnsureThemeProperties(Compositor compositor)"); + builder.WriteLine($"CompositionPropertySet {_sourceClassName}::EnsureThemeProperties(Compositor compositor)"); builder.OpenScope(); builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} == nullptr)"); builder.OpenScope(); @@ -396,17 +398,11 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt builder.CloseScope(); builder.WriteLine(); - builder.WriteLine($"CompositionPropertySet {sourceClassQualifier}GetThemeProperties(Compositor compositor)"); - builder.OpenScope(); - builder.WriteLine("return EnsureThemeProperties(compositor);"); - builder.CloseScope(); - builder.WriteLine(); - // Write property implementations for each theme property. foreach (var prop in propertyBindings) { // Write the getter. This just reads the values out of the backing field. - builder.WriteLine($"{TypeName(prop.ExposedType)} {sourceClassQualifier}{prop.BindingName}()"); + builder.WriteLine($"{TypeName(prop.ExposedType)} {_sourceClassName}::{prop.BindingName}()"); builder.OpenScope(); builder.WriteLine($"return _theme{prop.BindingName};"); builder.CloseScope(); @@ -414,12 +410,12 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt // Write the setter. This saves to the backing field, and updates the theme property // set if one has been created. - builder.WriteLine($"void {sourceClassQualifier}{prop.BindingName}({TypeName(prop.ExposedType)} value)"); + builder.WriteLine($"void {_sourceClassName}::{prop.BindingName}({TypeName(prop.ExposedType)} value)"); builder.OpenScope(); builder.WriteLine($"_theme{prop.BindingName} = value;"); - builder.WriteLine("if (_themeProperties != nullptr)"); + builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != nullptr)"); builder.OpenScope(); - WriteThemePropertyInitialization(builder, "_themeProperties", prop); + WriteThemePropertyInitialization(builder, SourceInfo.ThemePropertiesFieldName, prop); builder.CloseScope(); builder.CloseScope(); builder.WriteLine(); @@ -481,8 +477,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt pub.WriteSummaryComment("Returns true if all images have finished loading."); pub.WriteLine("bool IsImageLoadingCompleted();"); pub.WriteLine(); - - WriteTryCreateAnimatedVisualDeclaration(builder.Class.Public); } /// @@ -553,10 +547,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt builder.WriteLine("#include \"pch.h\""); builder.WriteLine($"#include \"{_headerFileName}\""); builder.WriteLine($"#include \"{_cppwinrtGeneratedFileNameBase}.cpp\""); - - // floatY, floatYxZ - builder.WriteLine("#include "); - builder.WriteLine("#include "); if (SourceInfo.WinUi3) @@ -799,11 +789,146 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt WriteIAnimatedVisualSource(builder); } + builder.WriteLine(); + + WriteFrameToProgressImpl(builder); + + builder.WriteLine(); + + WriteMarkersPropertyImpl(builder); + + builder.WriteLine(); + + WriteSetColorPropertyImpl(builder); + + builder.WriteLine(); + + WriteSetScalarPropertyImpl(builder); + // Close the namespace. builder.UnIndent(); builder.WriteLine("} // end namespace"); } + /// + /// Generates the FrameToProgress(...) declaration. + /// + void WriteFrameToProgressDecl(CodeBuilder builder) + { + builder.WriteComment("Converts a frame number to the corresponding progress value."); + builder.WriteLine($"double FrameToProgress(double frameNumber);"); + } + + /// + /// Generates the FrameToProgress(...) implementation. + /// + void WriteFrameToProgressImpl(CodeBuilder builder) + { + builder.WriteLine($"double {_sourceClassName}::FrameToProgress(double frameNumber)"); + builder.OpenScope(); + builder.WriteLine($"return frameNumber / {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.Frames)};"); + builder.CloseScope(); + } + + /// + /// Generates the Markers property declaration. + /// + void WriteMarkersPropertyDecl(CodeBuilder builder) + { + builder.WriteComment("Returns a map from marker names to corresponding progress values."); + builder.WriteLine("winrt::Windows::Foundation::Collections::IMapView Markers();"); + } + + /// + /// Generates the Markers property implementation. + /// + void WriteMarkersPropertyImpl(CodeBuilder builder) + { + builder.WriteLine($"winrt::Windows::Foundation::Collections::IMapView {_sourceClassName}::Markers()"); + builder.OpenScope(); + builder.WriteLine("return winrt::single_threaded_map("); + builder.Indent(); + builder.WriteLine("std::map"); + + builder.OpenScope(); + foreach (var marker in SourceInfo.Markers) + { + builder.WriteLine($"{{ {_s.String(marker.Name)}, {_s.Double(marker.StartProgress)} }},"); + } + + builder.CloseScope(); + + builder.UnIndent(); + builder.WriteLine(").GetView();"); + builder.CloseScope(); + } + + void WriteSetColorPropertyDecl(CodeBuilder builder) + { + builder.WriteComment("Sets the color property with the given name, or does nothing if no such property exists."); + builder.WriteLine("void SetColorProperty(hstring const& propertyName, winrt::Windows::UI::Color value);"); + } + + void WriteSetScalarPropertyDecl(CodeBuilder builder) + { + builder.WriteComment("Sets the scalar property with the given name, or does nothing if no such property exists."); + builder.WriteLine("void SetScalarProperty(hstring const& propertyName, double value);"); + } + + void WriteSetColorPropertyImpl(CodeBuilder builder) => + WriteSetPropertyImpl(builder, PropertySetValueType.Color, "Color"); + + void WriteSetScalarPropertyImpl(CodeBuilder builder) => + WriteSetPropertyImpl(builder, PropertySetValueType.Scalar, "double"); + + void WriteSetPropertyImpl(CodeBuilder builder, PropertySetValueType propertyType, string typeName) + { + var properties = SourceInfo.SourceMetadata.PropertyBindings.Where(p => p.ExposedType == propertyType).ToArray(); + + if (properties.Length == 0) + { + // There are no properties. The method doesn't need to do anything. + builder.WriteLine($"void {_sourceClassName}::Set{propertyType}Property(hstring const&, {typeName})"); + builder.OpenScope(); + builder.CloseScope(); + } + else + { + var propertySetTypeName = PropertySetValueTypeName(properties[0].ActualType); + var valueInitializer = properties[0].ExposedType == PropertySetValueType.Color ? "ColorAsVector4(value)" : "value"; + + builder.WriteLine($"void {_sourceClassName}::Set{propertyType}Property(hstring const& propertyName, {typeName} value)"); + builder.OpenScope(); + + var firstSeen = false; + foreach (var prop in properties) + { + // If the propertyName is a known name, save it into its backing field. + builder.WriteLine($"{(firstSeen ? "else " : string.Empty)}if (propertyName == {_s.String(prop.BindingName)})"); + firstSeen = true; + builder.OpenScope(); + builder.WriteLine($"_theme{prop.BindingName} = value;"); + builder.CloseScope(); + } + + builder.WriteLine("else"); + builder.OpenScope(); + + // Ignore the name if it doesn't refer to a known property. + builder.WriteLine("return;"); + builder.CloseScope(); + builder.WriteLine(); + + // Update the CompositionPropertySet if it has been created. + builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != nullptr)"); + builder.OpenScope(); + + builder.WriteLine($"{SourceInfo.ThemePropertiesFieldName}.Insert{propertySetTypeName}(propertyName, {valueInitializer});"); + builder.CloseScope(); + builder.CloseScope(); + } + } + /// /// Generate the body of the TryCreateAnimatedVisual() method for a composition that does not contain LoadedImageSurfaces. /// diff --git a/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtStringifier.cs b/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtStringifier.cs index c61e549..f9ac054 100644 --- a/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtStringifier.cs +++ b/source/UIDataCodeGen/CodeGen/Cppwinrt/CppwinrtStringifier.cs @@ -49,7 +49,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cppwinrt public override string Double(double value) => Math.Floor(value) == value - ? value.ToString("0.0", CultureInfo.InvariantCulture) + "d" + ? value.ToString("0.0", CultureInfo.InvariantCulture) : value.ToString("G15", CultureInfo.InvariantCulture); public override string FilledRegionDetermination(Mgcg.CanvasFilledRegionDetermination value) => diff --git a/source/UIDataCodeGen/CodeGen/Cx/CxInstantiatorGenerator.cs b/source/UIDataCodeGen/CodeGen/Cx/CxInstantiatorGenerator.cs index dd4d4a3..6756da8 100644 --- a/source/UIDataCodeGen/CodeGen/Cx/CxInstantiatorGenerator.cs +++ b/source/UIDataCodeGen/CodeGen/Cx/CxInstantiatorGenerator.cs @@ -149,20 +149,16 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cx builder.Public.Indent(); builder.Public.WriteLine($"{_wuc}::Compositor^ compositor,"); builder.Public.WriteLine($"Object^* diagnostics);"); - - // The GetThemeProperties method is designed to allow setting of properties when the actual - // type of the IAnimatedVisualSource is not known. It relies on a custom interface that declares - // it, so if we're not generating code for a custom interface, there's no reason to generate - // the method. - if (SourceInfo.IsInterfaceCustom && SourceInfo.IsThemed) - { - var optionalVirtual = SourceInfo.InterfaceType is null ? string.Empty : "virtual "; - builder.Public.WriteLine($"{optionalVirtual}{_wuc}::CompositionPropertySet^ GetThemeProperties({_wuc}::Compositor^ compositor);"); - - builder.Public.WriteLine(); - } - builder.Public.UnIndent(); + + builder.Public.WriteLine(); + WriteMarkersPropertyDecl(builder.Public); + + builder.Public.WriteLine(); + WriteSetColorPropertyDecl(builder.Public); + + builder.Public.WriteLine(); + WriteSetScalarPropertyDecl(builder.Public); } void WriteHeaderClassStart(CodeBuilder builder, IReadOnlyList inherits) @@ -261,19 +257,6 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cx builder.CloseScope(); builder.WriteLine(); - // The GetThemeProperties method is designed to allow setting of properties when the actual - // type of the IAnimatedVisualSource is not known. It relies on a custom interface that declares - // it, so if we're not generating code for a custom interface, there's no reason to generate - // the method. - if (SourceInfo.IsInterfaceCustom) - { - builder.WriteLine($"CompositionPropertySet^ {sourceClassQualifier}GetThemeProperties(Compositor^ compositor)"); - builder.OpenScope(); - builder.WriteLine("return EnsureThemeProperties(compositor);"); - builder.CloseScope(); - builder.WriteLine(); - } - // Write property implementations for each theme property. foreach (var prop in propertyBindings) { @@ -291,7 +274,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cx builder.OpenScope(); builder.WriteLine($"auto self = ({sourceClassQualifier}^)d;"); builder.WriteLine(); - builder.WriteLine("if (self->_themeProperties != nullptr)"); + builder.WriteLine($"if (self->{SourceInfo.ThemePropertiesFieldName} != nullptr)"); builder.OpenScope(); WriteThemePropertyInitialization(builder, $"self->{SourceInfo.ThemePropertiesFieldName}", prop, "e->NewValue"); builder.CloseScope(); @@ -342,9 +325,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cx // This saves to the backing field, and updates the theme property // set if one has been created. builder.WriteLine($"_theme{prop.BindingName} = value;"); - builder.WriteLine("if (_themeProperties != nullptr)"); + builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != nullptr)"); builder.OpenScope(); - WriteThemePropertyInitialization(builder, "_themeProperties", prop); + WriteThemePropertyInitialization(builder, SourceInfo.ThemePropertiesFieldName, prop); builder.CloseScope(); } @@ -399,10 +382,17 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cx pub.WriteLine($"Object^* diagnostics);"); pub.UnIndent(); pub.WriteLine(); + + WriteFrameToProgressDecl(pub); + pub.WriteLine(); + + WriteMarkersPropertyDecl(pub); + pub.WriteLine(); + pub.WriteLine($"virtual event PropertyChangedEventHandler^ PropertyChanged;"); pub.WriteLine(); - pub.WriteSummaryComment("If this property is set to true, will" + + pub.WriteComment("If this property is set to true, will" + " return null until all images have loaded. When all images have loaded, " + " will return the AnimatedVisual. To use, set it when instantiating the AnimatedVisualSource. Once" + " is called, changes made to this property will be ignored." + @@ -414,7 +404,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cx pub.CloseScope(); pub.WriteLine(); - pub.WriteSummaryComment("Returns true if all images have finished loading."); + pub.WriteComment("Returns true if all images have finished loading."); pub.WriteLine("property bool IsImageLoadingCompleted"); pub.OpenScope(); pub.WriteLine("bool get() { return m_isImageLoadingCompleted; }"); @@ -669,6 +659,139 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Cx { WriteIAnimatedVisualSource(builder); } + + builder.WriteLine(); + + WriteFrameToProgressImpl(builder); + builder.WriteLine(); + + WriteMarkersPropertyImpl(builder); + builder.WriteLine(); + + WriteSetColorPropertyImpl(builder); + builder.WriteLine(); + + WriteSetScalarPropertyImpl(builder); + } + + /// + /// Generates the FrameToProgress(...) declaration. + /// + void WriteFrameToProgressDecl(CodeBuilder builder) + { + builder.WriteComment("Converts a frame number to the corresponding progress value."); + builder.WriteLine($"double FrameToProgress(double frameNumber);"); + } + + /// + /// Generates the FrameToProgress(...) implementation. + /// + void WriteFrameToProgressImpl(CodeBuilder builder) + { + builder.WriteLine($"double {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::FrameToProgress(double frameNumber);"); + builder.OpenScope(); + builder.WriteLine($"return frameNumber / {_s.Double(SourceInfo.SourceMetadata.LottieMetadata.Duration.Frames)}"); + builder.CloseScope(); + } + + /// + /// Generates the GetMarkerAsProgress(...) declaration. + /// + void WriteMarkersPropertyDecl(CodeBuilder builder) + { + builder.WriteComment("Returns a map from marker names to corresponding progress values."); + builder.WriteLine($"property Windows::Foundation::Collections::IMapView"); + builder.OpenScope(); + builder.WriteLine("Windows::Foundation::Collections::IMapView get();"); + builder.CloseScope(); + } + + /// + /// Generates the GetMarkerAsProgress(...) implementation. + /// + void WriteMarkersPropertyImpl(CodeBuilder builder) + { + builder.WriteLine($"Windows::Foundation::Collections::IMapView {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::Markers::get()"); + builder.OpenScope(); + + builder.WriteLine("return ref new Platforms::Collections::Map("); + builder.Indent(); + builder.OpenScope(); + foreach (var marker in SourceInfo.Markers) + { + builder.WriteLine($"{{ {_s.String(marker.Name)}, {_s.Double(marker.StartProgress)} }},"); + } + + builder.CloseScope(); + builder.UnIndent(); + builder.WriteLine(").GetView();"); + builder.CloseScope(); + } + + void WriteSetColorPropertyDecl(CodeBuilder builder) + { + builder.WriteComment("Sets the color property with the given name, or does nothing if no such property exists."); + builder.WriteLine("void SetColorProperty(Platform::String^ propertyName, Windows::UI::Color value);"); + } + + void WriteSetScalarPropertyDecl(CodeBuilder builder) + { + builder.WriteComment("Sets the scalar property with the given name, or does nothing if no such property exists."); + builder.WriteLine("void SetScalarProperty(Platform::String^ propertyName, double value);"); + } + + void WriteSetColorPropertyImpl(CodeBuilder builder) => + WriteSetPropertyImpl(builder, PropertySetValueType.Color, "Color"); + + void WriteSetScalarPropertyImpl(CodeBuilder builder) => + WriteSetPropertyImpl(builder, PropertySetValueType.Color, "Color"); + + void WriteSetPropertyImpl(CodeBuilder builder, PropertySetValueType propertyType, string typeName) + { + var properties = SourceInfo.SourceMetadata.PropertyBindings.Where(p => p.ExposedType == propertyType).ToArray(); + + if (properties.Length == 0) + { + // There are no properties. The method doesn't need to do anything. + builder.WriteLine($"void {_s.Namespace(SourceInfo.Namespace)}::{_sourceClassName}::Set{propertyType}Property(String^, {typeName})"); + builder.OpenScope(); + builder.CloseScope(); + } + else + { + var propertySetTypeName = PropertySetValueTypeName(properties[0].ActualType); + var valueInitializer = properties[0].ExposedType == PropertySetValueType.Color ? "ColorAsVector4(value)" : "value"; + + builder.WriteLine($"void {_sourceClassName}::Set{propertyType}Property(String^ propertyName, {typeName} value)"); + builder.OpenScope(); + + var firstSeen = false; + foreach (var prop in properties) + { + // If the propertyName is a known name, save it into its backing field. + builder.WriteLine($"{(firstSeen ? "else " : string.Empty)}if (propertyName == {_s.String(prop.BindingName)})"); + firstSeen = true; + builder.OpenScope(); + builder.WriteLine($"_theme{prop.BindingName} = value;"); + builder.CloseScope(); + } + + builder.WriteLine("else"); + builder.OpenScope(); + + // Ignore the name if it doesn't refer to a known property. + builder.WriteLine("return;"); + builder.CloseScope(); + builder.WriteLine(); + + // Update the CompositionPropertySet if it has been created. + builder.WriteLine($"if ({SourceInfo.ThemePropertiesFieldName} != nullptr)"); + builder.OpenScope(); + + builder.WriteLine($"{SourceInfo.ThemePropertiesFieldName}.Insert{propertySetTypeName}(propertyName, {valueInitializer});"); + builder.CloseScope(); + builder.CloseScope(); + } } /// diff --git a/source/UIDataCodeGen/CodeGen/IAnimatedVisualSourceInfo.cs b/source/UIDataCodeGen/CodeGen/IAnimatedVisualSourceInfo.cs index a6d0175..53d5912 100644 --- a/source/UIDataCodeGen/CodeGen/IAnimatedVisualSourceInfo.cs +++ b/source/UIDataCodeGen/CodeGen/IAnimatedVisualSourceInfo.cs @@ -123,9 +123,14 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen /// IReadOnlyList InternalConstants { get; } + /// + /// The markers. These are names for progress values. + /// + IReadOnlyList Markers { get; } + /// /// Accesses metadata associated with the source of the composition. This may contain - /// information such as the frame rate and markers from the source. The contents of + /// information such as the frame rate and theme bindings from the source. The contents of /// this data is source specific. /// SourceMetadata SourceMetadata { get; } diff --git a/source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs b/source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs index 5ef46c3..1c8f651 100644 --- a/source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs +++ b/source/UIDataCodeGen/CodeGen/InstantiatorGeneratorBase.cs @@ -5,11 +5,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using System.Text; -using System.Threading.Tasks.Dataflow; using Microsoft.Toolkit.Uwp.UI.Lottie.CompMetadata; using Microsoft.Toolkit.Uwp.UI.Lottie.GenericData; using Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen.Tables; @@ -60,7 +58,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen readonly TypeName _interfaceType; readonly IReadOnlyList _additionalInterfaces; readonly bool _isInterfaceCustom; - readonly IReadOnlyList _lottieMarkers; + readonly IReadOnlyList _markers; readonly IReadOnlyList _internalConstants; AnimatedVisualGenerator? _currentAnimatedVisualGenerator; @@ -85,7 +83,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen _interfaceType = new TypeName(configuration.InterfaceType); _isInterfaceCustom = _interfaceType.NormalizedQualifiedName != "Microsoft.UI.Xaml.Controls.IAnimatedVisual"; _additionalInterfaces = configuration.AdditionalInterfaces.Select(n => new TypeName(n)).ToArray(); - _lottieMarkers = MarkerInfo.GetMarkerInfos(_sourceMetadata.LottieMetadata.FilteredMarkers).ToArray(); + _markers = MarkerInfo.GetMarkerInfos(_sourceMetadata.LottieMetadata.FilteredMarkers).ToArray(); _internalConstants = GetInternalConstants().ToArray(); var graphs = configuration.ObjectGraphs; @@ -397,7 +395,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen _compositionDuration.Ticks); // Get the markers. - foreach (var marker in _lottieMarkers) + foreach (var marker in _markers) { yield return new NamedConstant(marker.StartConstant, $"Marker: {marker.Name}.", ConstantType.Float, (float)marker.StartProgress); if (marker.DurationInFrames > 0) @@ -448,9 +446,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen yield return $"Frame count: {metadata.Duration.Frames}"; yield return $"Duration: {metadata.Duration.Time.TotalMilliseconds:0.0} mS"; - if (_lottieMarkers.Count > 0) + if (_markers.Count > 0) { - foreach (var line in LottieMarkersMonospaceTableFormatter.GetMarkersDescriptionLines(_s, _lottieMarkers)) + foreach (var line in LottieMarkersMonospaceTableFormatter.GetMarkersDescriptionLines(_s, _markers)) { yield return line; } @@ -761,6 +759,8 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen bool IAnimatedVisualSourceInfo.UsesCompositeEffect => _animatedVisualGenerators.Any(f => f.UsesCompositeEffect); + IReadOnlyList IAnimatedVisualSourceInfo.Markers => _markers; + IReadOnlyList IAnimatedVisualSourceInfo.InternalConstants => _internalConstants; IReadOnlyList IAnimatedVisualSourceInfo.LoadedImageSurfaces => _loadedImageSurfaceInfos; @@ -945,7 +945,7 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen static IEnumerable OrderByTypeThenName(IEnumerable nodes) => nodes.OrderBy(n => n.TypeName).ThenBy(n => n.Name, AlphanumericStringComparer.Instance); - static string PropertySetValueTypeName(PropertySetValueType value) + private protected static string PropertySetValueTypeName(PropertySetValueType value) => value switch { PropertySetValueType.Color => "Color", diff --git a/source/UIDataCodeGen/CodeGen/MarkerInfo.cs b/source/UIDataCodeGen/CodeGen/MarkerInfo.cs index deb218d..9fdfdc8 100644 --- a/source/UIDataCodeGen/CodeGen/MarkerInfo.cs +++ b/source/UIDataCodeGen/CodeGen/MarkerInfo.cs @@ -13,6 +13,9 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie.UIData.CodeGen /// Information about a marker. Markers refer to points in time or segments of time in /// an animation. /// +#if PUBLIC_UIDataCodeGen + public +#endif sealed class MarkerInfo { readonly Marker _marker;