330 строки
13 KiB
C#
330 строки
13 KiB
C#
// Licensed to the .NET Foundation under one or more agreements.
|
|
// The .NET Foundation licenses this file to you under the MIT license.
|
|
// See the LICENSE file in the project root for more information.
|
|
|
|
#nullable enable
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices.WindowsRuntime;
|
|
using System.Threading.Tasks;
|
|
using CommunityToolkit.WinUI.Lottie;
|
|
using Microsoft.UI.Xaml.Controls;
|
|
using Windows.Foundation;
|
|
using Windows.Foundation.Metadata;
|
|
using Windows.Storage;
|
|
using Windows.Storage.Streams;
|
|
|
|
#if WINAPPSDK
|
|
using Microsoft.UI.Composition;
|
|
using Microsoft.UI.Xaml;
|
|
#else
|
|
using Windows.UI.Composition;
|
|
using Windows.UI.Xaml;
|
|
#endif
|
|
|
|
namespace CommunityToolkit.WinUI.Lottie
|
|
{
|
|
/// <summary>
|
|
/// An <see cref="IAnimatedVisualSource"/> for a Lottie composition. This allows
|
|
/// a Lottie to be specified as the source for a <see cref="AnimatedVisualPlayer"/>.
|
|
/// </summary>
|
|
public sealed class LottieVisualSource : DependencyObject, IDynamicAnimatedVisualSource
|
|
{
|
|
#if WINAPPSDK
|
|
HashSet<TypedEventHandler<IDynamicAnimatedVisualSource?, object?>> _compositionInvalidatedEventTokenTable = new HashSet<TypedEventHandler<IDynamicAnimatedVisualSource?, object?>>();
|
|
#else
|
|
EventRegistrationTokenTable<TypedEventHandler<IDynamicAnimatedVisualSource?, object?>>? _compositionInvalidatedEventTokenTable;
|
|
#endif
|
|
|
|
int _loadVersion;
|
|
Uri? _uriSource;
|
|
AnimatedVisualFactory? _animatedVisualFactory;
|
|
ImageAssetHandler? _imageAssetHandler;
|
|
|
|
/// <summary>
|
|
/// Gets the options for the <see cref="LottieVisualSource"/>.
|
|
/// </summary>
|
|
// Optimize Lotties by default. Optimization takes a little longer but usually produces much
|
|
// more efficient translations. The only reason someone would turn optimization off is if
|
|
// the time to translate is too high, but in that case the Lottie is probably going to perform
|
|
// so badly on the machine that it won't really be usable with our without optimization.
|
|
public static DependencyProperty OptionsProperty { get; } =
|
|
RegisterDp(nameof(Options), LottieVisualOptions.Optimize);
|
|
|
|
/// <summary>
|
|
/// Gets the URI from which to load a JSON Lottie file.
|
|
/// </summary>
|
|
public static DependencyProperty UriSourceProperty { get; } =
|
|
RegisterDp<Uri>(nameof(UriSource), null,
|
|
(owner, oldValue, newValue) => owner.HandleUriSourcePropertyChanged(oldValue, newValue));
|
|
|
|
static DependencyProperty RegisterDp<T>(string propertyName, T defaultValue) =>
|
|
DependencyProperty.Register(propertyName, typeof(T), typeof(LottieVisualSource), new PropertyMetadata(defaultValue));
|
|
|
|
static DependencyProperty RegisterDp<T>(string propertyName, T? defaultValue, Action<LottieVisualSource, T, T> callback)
|
|
where T : class
|
|
=>
|
|
DependencyProperty.Register(propertyName, typeof(T), typeof(LottieVisualSource),
|
|
new PropertyMetadata(defaultValue, (d, e) => callback((LottieVisualSource)d, (T)e.OldValue, (T)e.NewValue)));
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="LottieVisualSource"/> class.
|
|
/// </summary>
|
|
public LottieVisualSource()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets options for how the Lottie is loaded.
|
|
/// </summary>
|
|
public LottieVisualOptions Options
|
|
{
|
|
get => (LottieVisualOptions)GetValue(OptionsProperty);
|
|
set => SetValue(OptionsProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the Uniform Resource Identifier (URI) of the JSON source file for this <see cref="LottieVisualSource"/>.
|
|
/// </summary>
|
|
public Uri UriSource
|
|
{
|
|
get => (Uri)GetValue(UriSourceProperty);
|
|
set => SetValue(UriSourceProperty, value);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Called by XAML to convert a string to an <see cref="IAnimatedVisualSource"/>.
|
|
/// </summary>
|
|
/// <returns>The <see cref="LottieVisualSource"/> for the given url.</returns>
|
|
public static LottieVisualSource? CreateFromString(string uri)
|
|
{
|
|
var uriUri = Uris.StringToUri(uri);
|
|
if (uriUri is null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return new LottieVisualSource { UriSource = uriUri };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the source for the <see cref="LottieVisualSource"/>.
|
|
/// </summary>
|
|
/// <param name="stream">A stream containing the text of a JSON Lottie file encoded as UTF-8.</param>
|
|
/// <returns>An <see cref="IAsyncAction"/> that completes when the load completes or fails.</returns>
|
|
[Overload("SetSourceStreamAsync")]
|
|
public IAsyncAction SetSourceAsync(IInputStream stream)
|
|
{
|
|
_uriSource = null;
|
|
return LoadAsync(InputStreamLoader.LoadAsync(_imageAssetHandler, stream, Options)).AsAsyncAction();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the source for the <see cref="LottieVisualSource"/>.
|
|
/// </summary>
|
|
/// <param name="file">A file that is a JSON Lottie file.</param>
|
|
/// <returns>An <see cref="IAsyncAction"/> that completes when the load completes or fails.</returns>
|
|
[Overload("SetSourceFileAsync")]
|
|
public IAsyncAction SetSourceAsync(StorageFile file)
|
|
{
|
|
_uriSource = null;
|
|
return LoadAsync(StorageFileLoader.LoadAsync(_imageAssetHandler, file, Options)).AsAsyncAction();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the source for the <see cref="LottieVisualSource"/>.
|
|
/// </summary>
|
|
/// <param name="sourceUri">A URI that refers to a JSON Lottie file.</param>
|
|
/// <returns>An <see cref="IAsyncAction"/> that completes when the load completes or fails.</returns>
|
|
[DefaultOverload]
|
|
[Overload("SetSourceUriAsync")]
|
|
public IAsyncAction SetSourceAsync(Uri sourceUri)
|
|
{
|
|
_uriSource = sourceUri;
|
|
|
|
// Update the dependency property to keep it in sync with _uriSource.
|
|
// This will not trigger loading because it will be seen as no change
|
|
// from the current (just set) _uriSource value.
|
|
UriSource = sourceUri;
|
|
|
|
return LoadAsync(UriLoader.LoadAsync(_imageAssetHandler, sourceUri, Options)).AsAsyncAction();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements <see cref="IDynamicAnimatedVisualSource"/>.
|
|
/// </summary>
|
|
// TODO: currently explicitly implemented interfaces are causing a problem with .NET Native. Make them implicit for now.
|
|
public event TypedEventHandler<IDynamicAnimatedVisualSource?, object?> AnimatedVisualInvalidated
|
|
{
|
|
add
|
|
{
|
|
#if WINAPPSDK
|
|
_compositionInvalidatedEventTokenTable.Add(value);
|
|
#else
|
|
return EventRegistrationTokenTable<TypedEventHandler<IDynamicAnimatedVisualSource?, object?>>
|
|
.GetOrCreateEventRegistrationTokenTable(ref _compositionInvalidatedEventTokenTable)
|
|
.AddEventHandler(value);
|
|
#endif
|
|
}
|
|
|
|
remove
|
|
{
|
|
#if WINAPPSDK
|
|
_compositionInvalidatedEventTokenTable.Remove(value);
|
|
#else
|
|
EventRegistrationTokenTable<TypedEventHandler<IDynamicAnimatedVisualSource?, object?>>
|
|
.GetOrCreateEventRegistrationTokenTable(ref _compositionInvalidatedEventTokenTable)
|
|
.RemoveEventHandler(value);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets a delegate that returns an <see cref="ICompositionSurface"/> for the given image uri.
|
|
/// If this is null, no images will be loaded from references to external images.
|
|
/// </summary>
|
|
/// <remarks>Most Lottie files do not reference external images, but those that do
|
|
/// will refer to the files via a uri. It is up to the user of <see cref="LottieVisualSource"/>
|
|
/// to manage the loading of the image, and return an <see cref="ICompositionSurface"/> for
|
|
/// that image. Alternatively the delegate may return null, and the image will not be
|
|
/// displayed.</remarks>
|
|
public void SetImageAssetHandler(ImageAssetHandler? imageAssetHandler)
|
|
{
|
|
_imageAssetHandler = imageAssetHandler;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Implements <see cref="IAnimatedVisualSource"/>.
|
|
/// </summary>
|
|
/// <param name="compositor">The <see cref="Compositor"/> that can be used as a factory for the resulting <see cref="IAnimatedVisual"/>.</param>
|
|
/// <param name="diagnostics">An optional object that may provide extra information about the result.</param>
|
|
/// <returns>An <see cref="IAnimatedVisual"/>.</returns>
|
|
// TODO: currently explicitly implemented interfaces are causing a problem with .NET Native. Make them implicit for now.
|
|
//bool IAnimatedVisualSource.TryCreateAnimatedVisual(
|
|
public IAnimatedVisual? TryCreateAnimatedVisual(
|
|
Compositor compositor,
|
|
out object? diagnostics)
|
|
{
|
|
if (_animatedVisualFactory is null)
|
|
{
|
|
// No content has been loaded yet.
|
|
// Return an IAnimatedVisual that produces nothing.
|
|
diagnostics = null;
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
// Some content was loaded. Ask the factory to produce an
|
|
// IAnimatedVisual. If it returns null, the player will treat it
|
|
// as an error.
|
|
return _animatedVisualFactory.TryCreateAnimatedVisual(compositor, out diagnostics);
|
|
}
|
|
}
|
|
|
|
void NotifyListenersThatCompositionChanged()
|
|
{
|
|
#if WINAPPSDK
|
|
foreach (var v in _compositionInvalidatedEventTokenTable)
|
|
{
|
|
v.Invoke(this, null);
|
|
}
|
|
#else
|
|
EventRegistrationTokenTable<TypedEventHandler<IDynamicAnimatedVisualSource?, object?>>
|
|
.GetOrCreateEventRegistrationTokenTable(ref _compositionInvalidatedEventTokenTable)
|
|
.InvocationList?.Invoke(this, null);
|
|
#endif
|
|
}
|
|
|
|
// Called when the UriSource property is updated.
|
|
void HandleUriSourcePropertyChanged(Uri oldValue, Uri newValue)
|
|
{
|
|
if (newValue == _uriSource)
|
|
{
|
|
// Ignore if setting to the current value. This can't happen if the value
|
|
// is being set via the DependencyProperty, but it will happen if the value
|
|
// is set via SetSourceAsync, as _uriSource will have been set before this
|
|
// is called.
|
|
return;
|
|
}
|
|
|
|
_uriSource = newValue;
|
|
|
|
var ignoredTask = StartLoadingAndIgnoreErrorsAsync();
|
|
|
|
async Task StartLoadingAndIgnoreErrorsAsync()
|
|
{
|
|
try
|
|
{
|
|
await LoadAsync(UriLoader.LoadAsync(_imageAssetHandler, UriSource, Options));
|
|
}
|
|
catch
|
|
{
|
|
// Swallow any errors - nobody is listening.
|
|
}
|
|
}
|
|
}
|
|
|
|
// Starts loading. Completes the returned task when the load completes or is replaced by another load.
|
|
async Task LoadAsync(Task<AnimatedVisualFactory?> loader)
|
|
{
|
|
var loadVersion = ++_loadVersion;
|
|
|
|
var oldFactory = _animatedVisualFactory;
|
|
_animatedVisualFactory = null;
|
|
|
|
if (oldFactory is not null)
|
|
{
|
|
// Notify all listeners that their existing content is no longer valid.
|
|
// They should stop showing the content. We will notify them again when the
|
|
// content changes.
|
|
NotifyListenersThatCompositionChanged();
|
|
}
|
|
|
|
// Disable the warning about the task possibly having being started in
|
|
// another context. There is no other context here.
|
|
#pragma warning disable VSTHRD003
|
|
|
|
// Wait for the loader to finish.
|
|
var factory = await loader;
|
|
#pragma warning restore VSTHRD003
|
|
|
|
if (loadVersion != _loadVersion)
|
|
{
|
|
// Another load request came in before this one completed.
|
|
return;
|
|
}
|
|
|
|
if (factory is null)
|
|
{
|
|
// Load didn't produce anything.
|
|
return;
|
|
}
|
|
|
|
// We are the the most recent load. Save the result.
|
|
_animatedVisualFactory = factory;
|
|
|
|
// Notify all listeners that they should try to create their instance of the content again.
|
|
NotifyListenersThatCompositionChanged();
|
|
|
|
if (!factory.CanInstantiate)
|
|
{
|
|
// The load did not produce any content. Throw an exception so the caller knows.
|
|
throw new ArgumentException("Failed to load animated visual.");
|
|
}
|
|
}
|
|
|
|
#if !WINAPPSDK
|
|
/// <summary>
|
|
/// Returns a string representation of the <see cref="LottieVisualSource"/> for debugging purposes.
|
|
/// </summary>
|
|
/// <returns>A string representation of the <see cref="LottieVisualSource"/> for debugging purposes.</returns>
|
|
public override string ToString()
|
|
{
|
|
var identity = _uriSource?.ToString() ?? string.Empty;
|
|
return $"LottieVisualSource({identity})";
|
|
}
|
|
#endif
|
|
}
|
|
} |