diff --git a/src/AddIns/Uno.UI.Lottie/DynamicReloadedLottieAnimatedVisualSource.cs b/src/AddIns/Uno.UI.Lottie/DynamicReloadedLottieAnimatedVisualSource.cs new file mode 100644 index 000000000000..7f03b750024b --- /dev/null +++ b/src/AddIns/Uno.UI.Lottie/DynamicReloadedLottieAnimatedVisualSource.cs @@ -0,0 +1,282 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Windows.Storage.Streams; +using Windows.UI.Xaml.Data; +using Uno.Disposables; +using Windows.UI; +using Windows.UI.Xaml.Controls; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Uno.UI.Lottie; +using System.Linq; +using Uno.Extensions; + +namespace Microsoft.Toolkit.Uwp.UI.Lottie +{ + [Bindable] + public class DynamicReloadedLottieAnimatedVisualSource : LottieVisualSourceBase, IDynamicAnimatedVisualSource + { + private JObject? _currentDocument; + + private readonly Dictionary _colorsBindings + = new Dictionary(2); + + private UpdatedAnimation? _updateCallback; + private string? _sourceCacheKey; + + public void SetColorProperty(string propertyName, Color? color) + { + if(_colorsBindings.TryGetValue(propertyName, out var existing)) + { + existing.NextValue = color; + } + else + { + _colorsBindings[propertyName] = new ColorBinding {NextValue = color}; + } + + if (_currentDocument == null) + { + return; // no document to change yet + } + + if (ApplyProperties()) + { + NotifyCallback(); + } + } + + protected override bool IsPayloadNeedsToBeUpdated => true; + +#if NETFRAMEWORK + public Task LoadForTests( + IInputStream sourceJson, + string sourceCacheKey, + UpdatedAnimation updateCallback) + { + _updateCallback = updateCallback; + return LoadAndUpdate(default, sourceCacheKey, sourceJson); + } + + public string? GetJson() + { + return _currentDocument?.ToString(Formatting.Indented); + } +#endif + + protected override IDisposable? LoadAndObserveAnimationData( + IInputStream sourceJson, + string sourceCacheKey, + UpdatedAnimation updateCallback) + { + var cts = new CancellationTokenSource(); + + _updateCallback = updateCallback; + + var t = LoadAndUpdate(cts.Token, sourceCacheKey, sourceJson); + + return Disposable.Create(() => + { + cts.Cancel(); + cts.Dispose(); + }); + } + + private async Task LoadAndUpdate( + CancellationToken ct, + string sourceCacheKey, + IInputStream sourceJson) + { + _sourceCacheKey = sourceCacheKey; + + // Note: we're using Newtownsoft JSON.NET here + // because System.Text.Json does not support changing the + // parsed document - it's read only. + + // LOAD JSON + JObject document; + using (var stream = sourceJson.AsStreamForRead(0)) + { + using var streamReader = new StreamReader(stream); + using var reader = new JsonTextReader(streamReader); + document = JObject.Load(reader); + } + + // PARSE JSON + ParseDocument(document); + + // APPLY PROPERTIES + ApplyProperties(); + + // NOTIFY + NotifyCallback(); + } + + private void ParseDocument(JObject document) + { + _currentDocument = document; + + foreach (var colorBinding in _colorsBindings) + { + colorBinding.Value.Elements.Clear(); + } + + void ParseLayers(JToken layersElement) + { + if (!(layersElement is JArray layers)) + { + return; // potentially invalid lottie file + } + + foreach (var layer in layers) + { + if (layer is JObject l) + { + var shapesValue = l.GetValue("shapes"); + + if (shapesValue is JArray shapes) + { + foreach (var shape in shapes) + { + if (shape is JObject s) + { + ParseShape(s); + } + } + } + } + } + } + + void ParseShape(JObject shapeElement) + { + var typeValue = shapeElement.GetValue("ty"); + if (typeValue.Type != JTokenType.String) + { + return; // potentially invalid lottie file + } + + var shapeType = typeValue.Value(); + + if (shapeType != null && shapeType.Equals("gr")) + { + // That's a group + + var itemsProperty = shapeElement.GetValue("it"); + + if (itemsProperty is JArray items) + { + foreach (var item in items) + { + if (item is JObject s) + { + ParseShape(s); + } + } + } + + return; + } + + var nameProperty = shapeElement.GetValue("nm"); + + if (nameProperty.Type != JTokenType.String) + { + return; // No name + } + + var name = nameProperty.Value(); + + if (!string.IsNullOrWhiteSpace(name)) + { + var elementBindings = PropertyBindingsParser.ParseBindings(name); + if (elementBindings.Length > 0) + { + foreach (var binding in elementBindings) + { + if (binding.propertyName.Equals("Color", StringComparison.Ordinal)) + { + if (_colorsBindings.TryGetValue(binding.bindingName, out var colorBinding)) + { + colorBinding.Elements.Add(shapeElement); + } + else + { + colorBinding = new ColorBinding(); + colorBinding.Elements.Add(shapeElement); + _colorsBindings[binding.bindingName] = colorBinding; + } + } + } + } + } + } + + if (document.TryGetValue("layers", out var layers)) + { + ParseLayers(layers); + } + } + + private bool ApplyProperties() + { + var changed = false; + foreach (var colorBinding in _colorsBindings) + { + if (!(colorBinding.Value.NextValue is {} color)) + { + continue; // nothing to change + } + + var colorComponents = new[] {color.R / 255f, color.G / 255f, color.B / 255f, color.A / 255f}; + + foreach (var element in colorBinding.Value.Elements) + { + var k = (element.GetValue("c") as JObject)?.GetValue("k") as JArray; + + if (k != null) + { + k.Clear(); + k.Add(new JValue(colorComponents[0])); + k.Add(new JValue(colorComponents[1])); + k.Add(new JValue(colorComponents[2])); + k.Add(new JValue(colorComponents[3])); + + changed = true; + } + } + colorBinding.Value.CurrentValue = colorBinding.Value.NextValue; + colorBinding.Value.NextValue = null; + } + + return changed; + } + + private void NotifyCallback() + { + if (_updateCallback is {} callback) + { + var json = _currentDocument?.ToString(Formatting.None); + if (json is { }) + { + var propertiesKey = _colorsBindings + .SelectToArray(kvp => $"{kvp.Key}-{kvp.Value.CurrentValue}") + .JoinBy("-"); + + callback(json, _sourceCacheKey + "-" + propertiesKey); + } + } + } + + + private class ColorBinding + { + internal List Elements { get; } = new List(1); + internal Color? CurrentValue { get; set; } + internal Color? NextValue { get; set; } + } + } +} diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.iOSmacOS.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.iOSmacOS.cs index c87d01cc7e3d..078d733541d6 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.iOSmacOS.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSource.iOSmacOS.cs @@ -1,13 +1,11 @@ using System; +using System.Threading; using Windows.Foundation; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media; using Airbnb.Lottie; using Foundation; -using Microsoft.Extensions.Logging; -using Uno.Extensions; -using Uno.Logging; -using Uno.UI; +using System.Threading.Tasks; +using Uno.Disposables; #if __IOS__ using _ViewContentMode = UIKit.UIViewContentMode; #else @@ -16,39 +14,57 @@ namespace Microsoft.Toolkit.Uwp.UI.Lottie { - partial class LottieVisualSource + partial class LottieVisualSourceBase { - private LOTAnimationView _animation; + private LOTAnimationView? _animation; public bool UseHardwareAcceleration { get; set; } = true; private Uri? _lastSource; private (double fromProgress, double toProgress, bool looped)? _playState; - partial void InnerUpdate() + private readonly SerialDisposable _animationDataSubscription = new SerialDisposable(); + + async Task InnerUpdate(CancellationToken ct) { var player = _player; - SetProperties(); - void SetProperties() + if (player == null) { - var source = UriSource; - if (_lastSource == null || !_lastSource.Equals(source)) - { - _lastSource = source; + return; + } - if (TryLoadEmbeddedJson(source, out var json)) + await SetProperties(); + + async Task SetProperties() + { + var sourceUri = UriSource; + if (_lastSource == null || !_lastSource.Equals(sourceUri)) + { + _lastSource = sourceUri; + if ((await TryLoadDownloadJson(sourceUri, ct)) is { } jsonStream) { - var jsonData = NSJsonSerialization.Deserialize(NSData.FromString(json), default, out var _) as NSDictionary; - if (jsonData != null) + var cacheKey = sourceUri.OriginalString; + _animationDataSubscription.Disposable = null; + _animationDataSubscription.Disposable = + LoadAndObserveAnimationData(jsonStream, cacheKey, OnJsonChanged); + + void OnJsonChanged(string updatedJson, string updatedCacheKey) { + var jsonData = NSJsonSerialization.Deserialize(NSData.FromString(updatedJson), default, out var _) as NSDictionary; var animation = LOTAnimationView.AnimationFromJSON(jsonData); SetAnimation(animation); + + if (_playState != null) + { + var (fromProgress, toProgress, looped) = _playState.Value; + Play(fromProgress, toProgress, looped); + } } } else { - var path = source?.PathAndQuery ?? ""; + var path = sourceUri?.PathAndQuery ?? ""; if (path.StartsWith("/")) { path = path.Substring(1); @@ -57,7 +73,7 @@ void SetProperties() if (_animation == null) { var animation = new LOTAnimationView(); - SetAnimation(animation); + _animation = SetAnimation(animation); } _animation.SetAnimationNamed(path); @@ -78,6 +94,11 @@ void SetProperties() } } + if (_animation == null) + { + return; + } + switch (player.Stretch) { case Windows.UI.Xaml.Media.Stretch.None: @@ -109,18 +130,19 @@ void SetProperties() } } - private void SetAnimation(LOTAnimationView animation) + private LOTAnimationView SetAnimation(LOTAnimationView animation) { if (!ReferenceEquals(_animation, animation)) { _animation?.RemoveFromSuperview(); } #if __IOS__ - _player.Add(animation); + _player?.Add(animation); #else - _player.AddSubview(animation); + _player?.AddSubview(animation); #endif _animation = animation; + return animation; } public void Play(double fromProgress, double toProgress, bool looped) @@ -180,7 +202,7 @@ public void SetProgress(double progress) public void Load() { - if (_player.IsPlaying) + if (_player?.IsPlaying ?? false) { _animation?.Play(); } @@ -188,7 +210,7 @@ public void Load() public void Unload() { - if (_player.IsPlaying) + if (_player?.IsPlaying ?? false) { _animation?.Pause(); } diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.Android.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.Android.cs similarity index 97% rename from src/AddIns/Uno.UI.Lottie/LottieVisualSource.Android.cs rename to src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.Android.cs index 1fae36954773..38fff0796c4d 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.Android.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.Android.cs @@ -89,6 +89,12 @@ async Task SetProperties() void OnJsonChanged(string updatedJson, string updatedCacheKey) { _animation.SetAnimationFromJson(updatedJson, updatedCacheKey); + + if (_playState != null) + { + var (fromProgress, toProgress, looped) = _playState.Value; + Play(fromProgress, toProgress, looped); + } } } else diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.cs index 42381efd709c..04007393e790 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.cs @@ -28,7 +28,7 @@ public abstract partial class LottieVisualSourceBase : DependencyObject, IAnimat public static DependencyProperty UriSourceProperty { get ; } = DependencyProperty.Register( "UriSource", typeof(Uri), - typeof(LottieVisualSource), + typeof(LottieVisualSourceBase), new FrameworkPropertyMetadata( default(Uri), FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsArrange, @@ -41,7 +41,7 @@ public Uri UriSource } public static DependencyProperty OptionsProperty { get ; } = DependencyProperty.Register( - "Options", typeof(LottieVisualOptions), typeof(LottieVisualSource), new FrameworkPropertyMetadata(LottieVisualOptions.None)); + "Options", typeof(LottieVisualOptions), typeof(LottieVisualSourceBase), new FrameworkPropertyMetadata(LottieVisualOptions.None)); [NotImplemented] public LottieVisualOptions Options diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.wasm.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.wasm.cs similarity index 68% rename from src/AddIns/Uno.UI.Lottie/LottieVisualSource.wasm.cs rename to src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.wasm.cs index a36f793b3ed2..ec2ff00a1f05 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSource.wasm.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceBase.wasm.cs @@ -1,29 +1,29 @@ using System; -using System.Collections.Generic; using System.Globalization; using System.Threading; using Uno.Foundation; using Windows.Foundation; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Media; using Uno.Extensions; +using System.Threading.Tasks; +using Uno.Disposables; namespace Microsoft.Toolkit.Uwp.UI.Lottie { - public partial class LottieVisualSource + partial class LottieVisualSourceBase { private static readonly string UNO_BOOTSTRAP_APP_BASE = global::System.Environment.GetEnvironmentVariable(nameof(UNO_BOOTSTRAP_APP_BASE)); private AnimatedVisualPlayer? _initializedPlayer; - private bool _isPlaying; private Size _compositionSize = new Size(0, 0); - private Uri? _loadedEmbeddedUri; private (double fromProgress, double toProgress, bool looped)? _playState; private bool _isUpdating; - partial void InnerUpdate() + private readonly SerialDisposable _animationDataSubscription = new SerialDisposable(); + + async Task InnerUpdate(CancellationToken ct) { var player = _player; if(_initializedPlayer != player) @@ -32,44 +32,66 @@ partial void InnerUpdate() player?.RegisterHtmlCustomEventHandler("lottie_state", OnStateChanged, isDetailJson: false); } - if (player == null) + if (player == null || _isUpdating) { return; } string[] js; - var uri = UriSource; + var sourceUri = UriSource; - if (uri.Scheme == "embedded") + if ((await TryLoadDownloadJson(sourceUri, ct)) is { } jsonStream) { - string jsonString; + var firstLoad = true; - if (!uri.Equals(_loadedEmbeddedUri) && TryLoadEmbeddedJson(uri, out jsonString)) - { - _loadedEmbeddedUri = uri; - } - else - { - jsonString = "null"; - } + var cacheKey = sourceUri.OriginalString; + _animationDataSubscription.Disposable = null; + _animationDataSubscription.Disposable = + LoadAndObserveAnimationData(jsonStream, cacheKey, OnJsonChanged); - js = new[] + void OnJsonChanged(string updatedJson, string updatedCacheKey) { - "Uno.UI.Lottie.setAnimationProperties({", - "elementId:", - player.HtmlId.ToString(), - ",jsonPath: null", - ",autoplay:", - player.AutoPlay ? "true" : "false", - ",stretch:\"", - player.Stretch.ToString(), - "\",rate:", - player.PlaybackRate.ToStringInvariant(), - "},", - jsonString, - ");" - }; + var play = _playState != null + || (firstLoad && player.AutoPlay); + + if (play && _playState == null) + { + _playState = (0, 1, true); + } + else if (!play) + { + _playState = null; + } + + firstLoad = false; + + js = new[] + { + "Uno.UI.Lottie.setAnimationProperties({", + "elementId:", + player.HtmlId.ToString(), + ",jsonPath: null,autoplay:", + play ? "true" : "false", + ",stretch:\"", + player.Stretch.ToString(), + "\",rate:", + player.PlaybackRate.ToStringInvariant(), + ",cacheKey:\"", + updatedCacheKey, + "\"},", + updatedJson, + ");" + }; + + ExecuteJs(js); + + if (_playState != null) + { + var (fromProgress, toProgress, looped) = _playState.Value; + Play(fromProgress, toProgress, looped); + } + } } else { @@ -90,16 +112,32 @@ partial void InnerUpdate() player.Stretch.ToString(), "\",rate:", player.PlaybackRate.ToStringInvariant(), - "});" + ",cacheKey:\"", + documentPath ?? "-n-", + "\"};" }; + + ExecuteJs(js); + + if (player.AutoPlay) + { + _playState = (0, 1, true); + } + else + { + _playState = null; + } } - _isUpdating = true; + void ExecuteJs(string[] js) + { + _isUpdating = true; + + InvokeJs(js); - WebAssemblyRuntime.InvokeJS(string.Concat(js)); - _isPlaying = player.AutoPlay; + _isUpdating = false; + } - _isUpdating = false; ApplyPlayState(); } @@ -168,8 +206,8 @@ public void Play(double fromProgress, double toProgress, bool looped) looped ? "true" : "false", ");" }; - WebAssemblyRuntime.InvokeJS(string.Concat(js)); - _isPlaying = true; + + InvokeJs(js); } void IAnimatedVisualSource.Stop() @@ -187,8 +225,8 @@ void IAnimatedVisualSource.Stop() _player.HtmlId.ToString(), ");" }; - WebAssemblyRuntime.InvokeJS(string.Concat(js)); - _isPlaying = false; + + InvokeJs(js); } void IAnimatedVisualSource.Pause() @@ -204,8 +242,8 @@ void IAnimatedVisualSource.Pause() _player.HtmlId.ToString(), ");" }; - WebAssemblyRuntime.InvokeJS(string.Concat(js)); - _isPlaying = false; + + InvokeJs(js); } void IAnimatedVisualSource.Resume() @@ -221,8 +259,8 @@ void IAnimatedVisualSource.Resume() _player.HtmlId.ToString(), ");" }; - WebAssemblyRuntime.InvokeJS(string.Concat(js)); - _isPlaying = true; + + InvokeJs(js); } public void SetProgress(double progress) @@ -240,8 +278,8 @@ public void SetProgress(double progress) progress.ToStringInvariant(), ");" }; - WebAssemblyRuntime.InvokeJS(string.Concat(js)); - _isPlaying = true; + + InvokeJs(js); } void IAnimatedVisualSource.Load() @@ -251,25 +289,28 @@ void IAnimatedVisualSource.Load() return; } - ApplyPlayState(); - - if (!_isPlaying) + if (_playState == null) { return; } + ApplyPlayState(); + var js = new[] { "Uno.UI.Lottie.resume(", _player.HtmlId.ToString(), ");" }; - WebAssemblyRuntime.InvokeJS(string.Concat(js)); + + InvokeJs(js); } + private static string InvokeJs(string[] js) => WebAssemblyRuntime.InvokeJS(string.Concat(js)); + void IAnimatedVisualSource.Unload() { - if (_player == null || !_isPlaying) + if (_player == null || _playState == null) { return; } @@ -280,7 +321,8 @@ void IAnimatedVisualSource.Unload() _player.HtmlId.ToString(), ");" }; - WebAssemblyRuntime.InvokeJS(string.Concat(js)); + + InvokeJs(js); } private Size CompositionSize => _compositionSize; diff --git a/src/AddIns/Uno.UI.Lottie/LottieVisualSourceProvider.cs b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceProvider.cs index a65d4d12b2b1..354dea27f8ca 100644 --- a/src/AddIns/Uno.UI.Lottie/LottieVisualSourceProvider.cs +++ b/src/AddIns/Uno.UI.Lottie/LottieVisualSourceProvider.cs @@ -21,5 +21,7 @@ public LottieVisualSourceProvider(object owner) } public IAnimatedVisualSource CreateFromLottieAsset(Uri sourceFile) => new LottieVisualSource {UriSource = sourceFile}; + + public IDynamicAnimatedVisualSource CreateDynamicFromLottieAsset(Uri sourceFile) => new DynamicReloadedLottieAnimatedVisualSource {UriSource = sourceFile}; } } diff --git a/src/AddIns/Uno.UI.Lottie/PropertyBindingsParser.cs b/src/AddIns/Uno.UI.Lottie/PropertyBindingsParser.cs new file mode 100644 index 000000000000..3da824a5f93a --- /dev/null +++ b/src/AddIns/Uno.UI.Lottie/PropertyBindingsParser.cs @@ -0,0 +1,92 @@ +// 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.Linq; +using System.Text.RegularExpressions; + +namespace Uno.UI.Lottie +{ + // This file is coming from there: https://github.com/windows-toolkit/Lottie-Windows/blob/3b731941af5f3665b3d52333f82abf5d378be6b3/source/LottieToWinComp/PropertyBindingsParser.cs + + internal static class PropertyBindingsParser + { + // Property binding language definition + // ==================================== + // Property bindings are lists of property-name+binding-name pairs. + // They are specified using a regular language (i.e. parseable by a + // regular expression) that is designed to look similar to CSS var + // functions. Just like CSS, the property bindings are enclosed within + // curly braces and multiple property bindings are separated by semicolons. + // + // ::= "{" "}" + // ::= | ";" + // ::= ":" "var(" ")" + // ::= + // ::= + // ::= | "" + // ::= a word starting with an alpha character. + // ------------------------------------ + // Example: "{ color :var( Foreground);color1:var(Foreground1) ; color3:var(L33t ) }" + // ------------------------------------ + const string PropertyNameSelector = "P"; + const string BindingNameSelector = "B"; + + const string PropertyBindingRegex = + + // PropertyName followed by optional whitespace. + @"(?<" + PropertyNameSelector + @">\D\w*)\s*" + + + // ':' followed by optional whitespace. + @"\:\s*" + + + // 'var(' followed by optional whitespace. + @"var\(\s*" + + + // BindingName followed by optional whitespace. + @"(?<" + BindingNameSelector + @">\D\w*)\s*" + + + // ')' followed by optional whitespace. + @"\)\s*"; + + const string PropertyBindingsListRegex = + + // At least one property binding. + PropertyBindingRegex + + + // Optional: more property bindings separated by semicolons + @"(;\s*" + PropertyBindingRegex + @"\s*)*"; + + static readonly Regex s_regex = new Regex(@"{\s*" + PropertyBindingsListRegex + @"}"); + + // Parses property bindings from the given string. + internal static (string propertyName, string bindingName)[] ParseBindings(string str) + { + if (string.IsNullOrEmpty(str)) + { + return Array.Empty<(string, string)>(); + } + + var matches = s_regex.Matches(str); + if (matches.Count == 0) + { + return Array.Empty<(string, string)>(); + } + + return + (from Match match in matches + let bindingCaptures = match.Groups[BindingNameSelector].Captures +#if NETFRAMEWORK || __NETSTD__ + .Cast() +#endif + let propertyCaptures = match.Groups[PropertyNameSelector].Captures +#if NETFRAMEWORK || __NETSTD__ + .Cast() +#endif + from pair in propertyCaptures + .Zip(bindingCaptures, (p, b) => (p.Value, b.Value)) + select pair).ToArray(); + } + } +} diff --git a/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.Wasm.csproj b/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.Wasm.csproj index 12397999cb7e..0c303b7c3305 100644 --- a/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.Wasm.csproj +++ b/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.Wasm.csproj @@ -11,6 +11,7 @@ WebAssembly .\ + enable @@ -26,6 +27,7 @@ + diff --git a/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj b/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj index 4c338e5b4426..0ba8b1e6dbed 100644 --- a/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj +++ b/src/AddIns/Uno.UI.Lottie/Uno.UI.Lottie.csproj @@ -36,6 +36,7 @@ + diff --git a/src/AddIns/Uno.UI.Lottie/WasmScripts/uno-lottie.d.ts b/src/AddIns/Uno.UI.Lottie/WasmScripts/uno-lottie.d.ts index e52cdd5382be..f13a4b861a8e 100644 --- a/src/AddIns/Uno.UI.Lottie/WasmScripts/uno-lottie.d.ts +++ b/src/AddIns/Uno.UI.Lottie/WasmScripts/uno-lottie.d.ts @@ -8,6 +8,7 @@ declare namespace Uno.UI { autoplay: boolean; stretch: string; rate: number; + cacheKey: string; } interface RunningLottieAnimation { animation: Lottie.AnimationItem; diff --git a/src/AddIns/Uno.UI.Lottie/WasmScripts/uno-lottie.js b/src/AddIns/Uno.UI.Lottie/WasmScripts/uno-lottie.js index c4be858846fa..df04aedbce88 100644 --- a/src/AddIns/Uno.UI.Lottie/WasmScripts/uno-lottie.js +++ b/src/AddIns/Uno.UI.Lottie/WasmScripts/uno-lottie.js @@ -74,6 +74,9 @@ var Uno; return state; } static needNewPlayerAnimation(current, newProperties) { + if (current.cacheKey !== newProperties.cacheKey) { + return true; + } if (current.jsonPath !== newProperties.jsonPath) { return true; } diff --git a/src/AddIns/Uno.UI.Lottie/ts/Uno.UI.Lottie.ts b/src/AddIns/Uno.UI.Lottie/ts/Uno.UI.Lottie.ts index 3c0f13bccf34..ced7b26ca1c0 100644 --- a/src/AddIns/Uno.UI.Lottie/ts/Uno.UI.Lottie.ts +++ b/src/AddIns/Uno.UI.Lottie/ts/Uno.UI.Lottie.ts @@ -10,6 +10,7 @@ namespace Uno.UI { autoplay: boolean; stretch: string; rate: number; + cacheKey: string; } export interface RunningLottieAnimation { @@ -22,7 +23,9 @@ namespace Uno.UI { private static _runningAnimations: { [id: number]: RunningLottieAnimation } = {}; private static _numberOfFrames: number; - public static setAnimationProperties(newProperties: LottieAnimationProperties, animationData?: AnimationData): string { + public static setAnimationProperties( + newProperties: LottieAnimationProperties, + animationData?: AnimationData): string { const elementId = newProperties.elementId; Lottie.withPlayer(p => { @@ -117,6 +120,9 @@ namespace Uno.UI { private static needNewPlayerAnimation(current: LottieAnimationProperties, newProperties: LottieAnimationProperties): boolean { + if (current.cacheKey !== newProperties.cacheKey) { + return true; + } if (current.jsonPath !== newProperties.jsonPath) { return true; } diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 8fe834505aef..607cfce19496 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -46,6 +46,7 @@ + diff --git a/src/SamplesApp/UITests.Shared/Lottie/LottieCustomThemeProperty.xaml b/src/SamplesApp/UITests.Shared/Lottie/LottieCustomThemeProperty.xaml new file mode 100644 index 000000000000..863d949c2b57 --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Lottie/LottieCustomThemeProperty.xaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/src/SamplesApp/UITests.Shared/Lottie/LottieCustomThemeProperty.xaml.cs b/src/SamplesApp/UITests.Shared/Lottie/LottieCustomThemeProperty.xaml.cs new file mode 100644 index 000000000000..a7681af51a3c --- /dev/null +++ b/src/SamplesApp/UITests.Shared/Lottie/LottieCustomThemeProperty.xaml.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=234238 + +namespace UITests.Lottie +{ + /// + /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class LottieCustomThemeProperty : Page + { + public LottieCustomThemeProperty() + { + this.InitializeComponent(); + } + } +} diff --git a/src/SamplesApp/UITests.Shared/Microsoft_UI_Xaml_Controls/ProgressRing/WinUIProgressRingPage.xaml b/src/SamplesApp/UITests.Shared/Microsoft_UI_Xaml_Controls/ProgressRing/WinUIProgressRingPage.xaml index 982356788043..a3f1ff83461e 100644 --- a/src/SamplesApp/UITests.Shared/Microsoft_UI_Xaml_Controls/ProgressRing/WinUIProgressRingPage.xaml +++ b/src/SamplesApp/UITests.Shared/Microsoft_UI_Xaml_Controls/ProgressRing/WinUIProgressRingPage.xaml @@ -18,5 +18,35 @@ IsActive LOAD/UNLOAD + Custom Colors: + + + + + Blue + Red + Green + Yellow + Pink + Violet + + + + + Blue + Red + Green + Yellow + Pink + Violet + + + + + + diff --git a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems index ac02aaad86b7..dd1024d784ca 100644 --- a/src/SamplesApp/UITests.Shared/UITests.Shared.projitems +++ b/src/SamplesApp/UITests.Shared/UITests.Shared.projitems @@ -9,6 +9,10 @@ UITests + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -3646,6 +3650,9 @@ + + LottieCustomThemeProperty.xaml + LottieEmbeddedJson.xaml diff --git a/src/Uno.UI.Tests/Lottie/Given_DynamicReloadedLottieAnimatedVisualSource.cs b/src/Uno.UI.Tests/Lottie/Given_DynamicReloadedLottieAnimatedVisualSource.cs new file mode 100644 index 000000000000..cf178fe40043 --- /dev/null +++ b/src/Uno.UI.Tests/Lottie/Given_DynamicReloadedLottieAnimatedVisualSource.cs @@ -0,0 +1,104 @@ +#nullable enable +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Windows.Storage.Streams; +using Windows.UI; +using FluentAssertions; +using Microsoft.Toolkit.Uwp.UI.Lottie; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Uno.Threading; + +namespace Uno.UI.Tests.Lottie +{ + [TestClass] + public class Given_DynamicReloadedLottieAnimatedVisualSource + { + [TestMethod] + public async Task DynamicReloadedLottieAnimatedVisualSource_SimpleLoading() + { + var sut = new DynamicReloadedLottieAnimatedVisualSource(); + + var results = new List(); + + await sut.LoadForTests(GetStream(), "cache-key", Callback); + + void Callback(string animationjson, string cachekey) + { + results.Add(animationjson); + } + + results.Should().HaveCount(1); + } + + [TestMethod] + public async Task DynamicReloadedLottieAnimatedVisualSource_ValueSet_BeforeLoading() + { + var reference = new DynamicReloadedLottieAnimatedVisualSource(); + + await reference.LoadForTests(GetStream(), "cache-key", (x, y) => { }); + + var sut = new DynamicReloadedLottieAnimatedVisualSource(); + + sut.SetColorProperty("Foreground", Color.FromArgb(1, 2, 3, 4)); + + var results = new List(); + + await sut.LoadForTests(GetStream(), "cache-key", Callback); + + void Callback(string animationjson, string cachekey) + { + results.Add(animationjson); + } + + results.Should().HaveCount(1); + + var jsonReference = reference.GetJson(); + sut.GetJson().Should().NotBe(jsonReference); + } + + [TestMethod] + public async Task DynamicReloadedLottieAnimatedVisualSource_ValueSet_AfterLoading() + { + var reference = new DynamicReloadedLottieAnimatedVisualSource(); + + await reference.LoadForTests(GetStream(), "cache-key", (x, y) => { }); + + var sut = new DynamicReloadedLottieAnimatedVisualSource(); + + var results = new List(); + + await sut.LoadForTests(GetStream(), "cache-key", Callback); + + void Callback(string animationjson, string cachekey) + { + results.Add(animationjson); + } + + results.Should().HaveCount(1); + + sut.SetColorProperty("Foreground", Color.FromArgb(1, 2, 3, 4)); + + results.Should().HaveCount(2); + + var jsonReference = reference.GetJson(); + sut.GetJson().Should().NotBe(jsonReference); + } + + private IInputStream GetStream(string name = "animation.json") + { + var type = GetType(); + var assembly = type.Assembly; + var resourceName = "Uno.UI.Tests.Lottie." + name; + var stream = assembly.GetManifestResourceStream(resourceName); + + if (stream == null) + { + throw new InvalidOperationException("Unable to find embedded resource named " + resourceName); + } + + return stream.AsInputStream(); + } + } +} diff --git a/src/Uno.UI.Tests/Lottie/animation.json b/src/Uno.UI.Tests/Lottie/animation.json new file mode 100644 index 000000000000..85a91fff1532 --- /dev/null +++ b/src/Uno.UI.Tests/Lottie/animation.json @@ -0,0 +1,653 @@ +{ + "v": "5.5.7", + "meta": { + "g": "LottieFiles AE 0.1.20", + "a": "nventive", + "k": "progression-ring", + "d": "progression-ring", + "tc": "" + }, + "fr": 30, + "ip": 0, + "op": 140, + "w": 36, + "h": 36, + "nm": "pregression-ring", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "blue_ellipse Silhouettes", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 180, + "s": [ + 3240 + ] + } + ], + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 18, + 18, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 18, + 18, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -8.008, + 0 + ], + [ + 0, + -8.008 + ] + ], + "o": [ + [ + 0, + -8.008 + ], + [ + 8.008, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -14.5, + 7.25 + ], + [ + 0, + -7.25 + ], + [ + 14.5, + 7.25 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Tracé 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.161000001197, + 0.416000007181, + 0.795999983245, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 3, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 10, + "bm": 0, + "nm": "{ Color : var(Foreground) }", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 18, + 10.75 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transformer " + } + ], + "nm": "Groupe 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 36, + "s": [ + 95 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 72, + "s": [ + 0 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 108, + "s": [ + 95 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 144, + "s": [ + 0 + ] + }, + { + "t": 180, + "s": [ + 90 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": -1, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "t": 144, + "s": [ + 100 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Réduire les tracés 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 0, + "op": 180, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "grey_ellipse Silhouettes", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 18, + 18, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 18, + 18, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + -8.008 + ], + [ + 8.008, + 0 + ], + [ + 0, + 8.008 + ], + [ + -8.008, + 0 + ] + ], + "o": [ + [ + 0, + 8.008 + ], + [ + -8.008, + 0 + ], + [ + 0, + -8.008 + ], + [ + 8.008, + 0 + ] + ], + "v": [ + [ + 14.5, + 0 + ], + [ + 0, + 14.5 + ], + [ + -14.5, + 0 + ], + [ + 0, + -14.5 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Tracé 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.8, + 0.8, + 0.8, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 3, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 10, + "bm": 0, + "nm": "{ Color : var(Background) }", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 18, + 18 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transformer " + } + ], + "nm": "Groupe 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 180, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/src/Uno.UI.Tests/Uno.UI.Tests.csproj b/src/Uno.UI.Tests/Uno.UI.Tests.csproj index 5e0171bbee57..2ad1e51b7748 100644 --- a/src/Uno.UI.Tests/Uno.UI.Tests.csproj +++ b/src/Uno.UI.Tests/Uno.UI.Tests.csproj @@ -86,6 +86,7 @@ + MSBuild:UpdateDesignTimeXaml @@ -100,12 +101,14 @@ + + diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/AnimatedVisualPlayer/ILottieVisualSourceProvider.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/AnimatedVisualPlayer/ILottieVisualSourceProvider.cs index f11c7e9d3dff..d7afcc2997c4 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/AnimatedVisualPlayer/ILottieVisualSourceProvider.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/AnimatedVisualPlayer/ILottieVisualSourceProvider.cs @@ -8,5 +8,6 @@ namespace Microsoft.UI.Xaml.Controls public interface ILottieVisualSourceProvider { IAnimatedVisualSource CreateFromLottieAsset(Uri sourceFile); + IDynamicAnimatedVisualSource CreateDynamicFromLottieAsset(Uri sourceFile); } } diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ProgressRing/ProgressRing.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ProgressRing/ProgressRing.cs index 9983f9f8f529..c0805399b3fb 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ProgressRing/ProgressRing.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ProgressRing/ProgressRing.cs @@ -38,7 +38,7 @@ public ProgressRing() } RegisterPropertyChangedCallback(ForegroundProperty, OnForegroundPropertyChanged); - RegisterPropertyChangedCallback(BackgroundProperty, OnbackgroundPropertyChanged); + RegisterPropertyChangedCallback(BackgroundProperty, OnBackgroundPropertyChanged); } protected override AutomationPeer OnCreateAutomationPeer() => new ProgressRingAutomationPeer(progressRing: this); @@ -48,6 +48,9 @@ protected override void OnApplyTemplate() _player = GetTemplateChild("IndeterminateAnimatedVisualPlayer") as Windows.UI.Xaml.Controls.AnimatedVisualPlayer; _layoutRoot = GetTemplateChild("LayoutRoot") as Panel; + OnForegroundPropertyChanged(this, ForegroundProperty); + OnBackgroundPropertyChanged(this, BackgroundProperty); + SetAnimatedVisualPlayerSource(); ChangeVisualState(); @@ -55,17 +58,19 @@ protected override void OnApplyTemplate() private void OnForegroundPropertyChanged(DependencyObject sender, DependencyProperty dp) { - if (Background is SolidColorBrush background) + if (_player?.Source is IDynamicAnimatedVisualSource source + && Brush.TryGetColorWithOpacity(Foreground, out var foreground)) { - // TODO + source.SetColorProperty("Foreground", foreground); } } - private void OnbackgroundPropertyChanged(DependencyObject sender, DependencyProperty dp) + private void OnBackgroundPropertyChanged(DependencyObject sender, DependencyProperty dp) { - if (Foreground is SolidColorBrush foreground) + if (_player?.Source is IDynamicAnimatedVisualSource source + && Brush.TryGetColorWithOpacity(Background, out var background)) { - // TODO + source.SetColorProperty("Background", background); } } @@ -78,7 +83,7 @@ private void SetAnimatedVisualPlayerSource() { if (_lottieProvider != null && _player != null) { - var animatedVisualSource = _lottieProvider.CreateFromLottieAsset(FeatureConfiguration.ProgressRing.ProgressRingAsset); + var animatedVisualSource = _lottieProvider.CreateDynamicFromLottieAsset(FeatureConfiguration.ProgressRing.ProgressRingAsset); _player.Source = animatedVisualSource; ChangeVisualState(); } diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ProgressRing/ProgressRingIntdeterminate.json b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ProgressRing/ProgressRingIntdeterminate.json index 347ce39c4ce0..07eee80d5236 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/ProgressRing/ProgressRingIntdeterminate.json +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/ProgressRing/ProgressRingIntdeterminate.json @@ -1 +1,653 @@ -{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"nventive","k":"progression-ring","d":"progression-ring","tc":""},"fr":30,"ip":0,"op":140,"w":36,"h":36,"nm":"pregression-ring","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"blue_ellipse Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":180,"s":[3240]}],"ix":10},"p":{"a":0,"k":[18,18,0],"ix":2},"a":{"a":0,"k":[18,18,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-8.008,0],[0,-8.008]],"o":[[0,-8.008],[8.008,0],[0,0]],"v":[[-14.5,7.25],[0,-7.25],[14.5,7.25]],"c":false},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.161000001197,0.416000007181,0.795999983245,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[18,10.75],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Groupe 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":36,"s":[95]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":72,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":108,"s":[95]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":144,"s":[0]},{"t":180,"s":[90]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":-1,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":144,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Réduire les tracés 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"grey_ellipse Silhouettes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[18,18,0],"ix":2},"a":{"a":0,"k":[18,18,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-8.008],[8.008,0],[0,8.008],[-8.008,0]],"o":[[0,8.008],[-8.008,0],[0,-8.008],[8.008,0]],"v":[[14.5,0],[0,14.5],[-14.5,0],[0,-14.5]],"c":true},"ix":2},"nm":"Tracé 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8,0.8,0.8,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Contour 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[18,18],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformer "}],"nm":"Groupe 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[]} \ No newline at end of file +{ + "v": "5.5.7", + "meta": { + "g": "LottieFiles AE 0.1.20", + "a": "nventive", + "k": "progression-ring", + "d": "progression-ring", + "tc": "" + }, + "fr": 30, + "ip": 0, + "op": 140, + "w": 36, + "h": 36, + "nm": "pregression-ring", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "blue_ellipse Silhouettes", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 180, + "s": [ + 3240 + ] + } + ], + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 18, + 18, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 18, + 18, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + -8.008, + 0 + ], + [ + 0, + -8.008 + ] + ], + "o": [ + [ + 0, + -8.008 + ], + [ + 8.008, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -14.5, + 7.25 + ], + [ + 0, + -7.25 + ], + [ + 14.5, + 7.25 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Tracé 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.161000001197, + 0.416000007181, + 0.795999983245, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 3, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 10, + "bm": 0, + "nm": "{ Color : var(Foreground) }", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 18, + 10.75 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transformer " + } + ], + "nm": "Groupe 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 36, + "s": [ + 95 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 72, + "s": [ + 0 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 108, + "s": [ + 95 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 144, + "s": [ + 0 + ] + }, + { + "t": 180, + "s": [ + 90 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": -1, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "t": 144, + "s": [ + 100 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Réduire les tracés 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 0, + "op": 180, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "grey_ellipse Silhouettes", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 18, + 18, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 18, + 18, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + -8.008 + ], + [ + 8.008, + 0 + ], + [ + 0, + 8.008 + ], + [ + -8.008, + 0 + ] + ], + "o": [ + [ + 0, + 8.008 + ], + [ + -8.008, + 0 + ], + [ + 0, + -8.008 + ], + [ + 8.008, + 0 + ] + ], + "v": [ + [ + 14.5, + 0 + ], + [ + 0, + 14.5 + ], + [ + -14.5, + 0 + ], + [ + 0, + -14.5 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Tracé 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0.8, + 0.8, + 0.8, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 3, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 10, + "bm": 0, + "nm": "{ Color: var(Background) }", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 18, + 18 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transformer " + } + ], + "nm": "Groupe 1", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 180, + "st": 0, + "bm": 0 + } + ], + "markers": [] +} diff --git a/src/Uno.UI/UI/Xaml/Controls/AnimatedVisualPlayer/AnimatedVisualPlayer.cs b/src/Uno.UI/UI/Xaml/Controls/AnimatedVisualPlayer/AnimatedVisualPlayer.cs index 5d61ac160a8c..6ca07af9acbb 100644 --- a/src/Uno.UI/UI/Xaml/Controls/AnimatedVisualPlayer/AnimatedVisualPlayer.cs +++ b/src/Uno.UI/UI/Xaml/Controls/AnimatedVisualPlayer/AnimatedVisualPlayer.cs @@ -84,11 +84,12 @@ public TimeSpan Duration private static void UpdateSourceOnChanged(DependencyObject source, DependencyPropertyChangedEventArgs args) { - if (source is AnimatedVisualPlayer player) + if (source is AnimatedVisualPlayer {IsLoaded: true} player) { - if (player.IsLoaded) + if (player.Source != null) { - player?.Source?.Update(player); + player.Source.Update(player); + player.InvalidateMeasure(); } } } @@ -118,7 +119,16 @@ private protected override void OnUnloaded() base.OnUnloaded(); } - protected override Size MeasureOverride(Size availableSize) => - Source?.Measure(availableSize) ?? base.MeasureOverride(availableSize); + protected override Size MeasureOverride(Size availableSize) + { + if (Source?.Measure(availableSize) != null) + { + return Source.Measure(availableSize); + } + else + { + return base.MeasureOverride(availableSize); + } + } } } diff --git a/src/Uno.UI/UI/Xaml/Controls/AnimatedVisualPlayer/IDynamicAnimatedVisualSource.cs b/src/Uno.UI/UI/Xaml/Controls/AnimatedVisualPlayer/IDynamicAnimatedVisualSource.cs new file mode 100644 index 000000000000..619dc6abe5e8 --- /dev/null +++ b/src/Uno.UI/UI/Xaml/Controls/AnimatedVisualPlayer/IDynamicAnimatedVisualSource.cs @@ -0,0 +1,7 @@ +namespace Windows.UI.Xaml.Controls +{ + public partial interface IDynamicAnimatedVisualSource : IAnimatedVisualSource + { + void SetColorProperty(string propertyName, Color? color); + } +}