Lottie-Windows/source/WinCompData/CompositionObject.cs

268 строки
10 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.Diagnostics;
using System.Linq;
namespace CommunityToolkit.WinUI.Lottie.WinCompData
{
[MetaData.UapVersion(2)]
#if PUBLIC_WinCompData
public
#endif
abstract class CompositionObject : IDisposable, IDescribable
{
// Keys used to identify the short description and long description metadata.
static readonly Guid s_shortDescriptionMetadataKey = new Guid("AF01D303-5572-4540-A4AC-E48F1394E1D1");
static readonly Guid s_longDescriptionMetadataKey = new Guid("63514254-2B3E-4794-B01D-9F67D5946A7E");
static readonly Guid s_nameMetadataKey = new Guid("6EB18A31-FA33-43B9-8EE1-57B489DC3404");
readonly List<Animator> _animators = new List<Animator>();
// Null until the first metadata is set.
SortedDictionary<Guid, object>? _metadata;
private protected CompositionObject()
{
if (Type == CompositionObjectType.CompositionPropertySet)
{
// The property set on a property set is itself.
Properties = (CompositionPropertySet)this;
}
else
{
Properties = new CompositionPropertySet(this);
}
}
/// <summary>
/// Associates metadata with the object. The type of metadata is indicated by the <paramref name="key"/>
/// <see cref="Guid"/>. If metadata for the given <paramref name="key"/> is already associated with the
/// object, the meatadata will be replaced with the new <paramref name="value"/>.
/// </summary>
/// <param name="key">A <see cref="Guid"/> that identifies that type of metadata.</param>
/// <param name="value">The value of the metadata.</param>
public void SetMetadata(in Guid key, object? value)
{
if (_metadata is null)
{
_metadata = new SortedDictionary<Guid, object>();
}
if (value is null)
{
_metadata.Remove(key);
}
else
{
_metadata[key] = value;
}
}
/// <summary>
/// Returns the metadata associated with this object that is identified by the given
/// <paramref name="key"/>, or null if no such metadata has been associated with this
/// object yet.
/// </summary>
/// <param name="key">A <see cref="Guid"/> that identifies that type of metadata.</param>
/// <returns>The metadata, or null.</returns>
public object? TryGetMetadata(in Guid key)
{
if (_metadata is object && _metadata.TryGetValue(key, out var result))
{
return result;
}
return null;
}
public string? Comment { get; set; }
#pragma warning disable CA1033 // Interface methods should be callable by child types
/// <summary>
/// Gets or sets a description of the object. This may be used to add comments to generated code.
/// Cf. the <see cref="Comment"/> property which is a property on real composition
/// objects that is used for debugging.
/// </summary>
string? IDescribable.LongDescription
{
get => (string?)TryGetMetadata(in s_longDescriptionMetadataKey);
set => SetMetadata(in s_longDescriptionMetadataKey, value);
}
/// <summary>
/// Gets or sets a description of the object. This may be used to add comments to generated code.
/// Cf. the <see cref="Comment"/> property which is a property on real composition
/// objects that is used for debugging.
/// </summary>
string? IDescribable.ShortDescription
{
get => (string?)TryGetMetadata(in s_shortDescriptionMetadataKey);
set => SetMetadata(in s_shortDescriptionMetadataKey, value);
}
/// <summary>
/// Gets or sets a name for the object. This may be used for variable names in generated code.
/// </summary>
string? IDescribable.Name
{
get => (string?)TryGetMetadata(in s_nameMetadataKey);
set => SetMetadata(in s_nameMetadataKey, value);
}
#pragma warning restore CA1033 // Interface methods should be callable by child types
public CompositionPropertySet Properties { get; }
/// <summary>
/// Binds an animation to a property with a given custom controller.
/// </summary>
/// <param name="target">The name of the property.</param>
/// <param name="animation">The animation.</param>
/// <param name="customController">Custom controller.</param>
/// <returns>New animator.</returns>
public Animator StartAnimation(string target, CompositionAnimation animation, AnimationController? customController)
{
// Remove any existing animation.
StopAnimation(target);
// Clone the animation so that the existing animation object can be reconfigured.
// If the animation is frozen, it is safe to not do the clone.
var clone = animation.IsFrozen ? animation : animation.Clone();
var controller = animation is ExpressionAnimation
? null
: new AnimationController(this, target);
if (customController is not null)
{
Debug.Assert(customController.IsCustom, "Should be custom!");
Debug.Assert(animation is not ExpressionAnimation, "Should not be ExpressionAnimation!");
controller = customController;
}
var animator = new Animator(
animatedProperty: target,
animatedObject: this,
animation: clone,
controller: controller);
_animators.Add(animator);
return animator;
}
/// <summary>
/// Binds an animation to a property.
/// </summary>
/// <param name="target">The name of the property.</param>
/// <param name="animation">The animation.</param>
/// <returns>New animator.</returns>
public Animator StartAnimation(string target, CompositionAnimation animation) => StartAnimation(target, animation, null);
/// <summary>
/// Stops an animation that was previously started.
/// </summary>
/// <param name="propertyName">The name of the property.</param>
public void StopAnimation(string propertyName)
{
// We also need to stop animations on any sub-channels and the root property.
// Examples:
// Sub-channels: stopping Offset must also stop
// Offset.X, Offset.Y, etc..
// Root property: stopping Offset.X must also
// stop Offset.
// If there's a dot in the name it is a sub-channel name.
var subChannelPrefix = $"{propertyName}.";
var firstDotIndex = propertyName.IndexOf('.');
var rootPropertyName = $"{(firstDotIndex >= 0 ? propertyName.Substring(0, firstDotIndex) : string.Empty)}.";
for (var i = 0; i < _animators.Count; i++)
{
var animatorPropertyName = _animators[i].AnimatedProperty;
if (animatorPropertyName == propertyName ||
animatorPropertyName == rootPropertyName ||
animatorPropertyName.StartsWith(subChannelPrefix))
{
_animators.RemoveAt(i);
// Adjust the iteration variable to ensure we don't miss the
// animator just after the one we just removed.
i--;
}
}
}
/// <summary>
/// Gets the animators that are bound to this object.
/// </summary>
public IReadOnlyList<Animator> Animators => _animators;
public AnimationController? TryGetAnimationController(string target) =>
_animators.Where(a => a.AnimatedProperty == target).SingleOrDefault()?.Controller;
public abstract CompositionObjectType Type { get; }
/// <inheritdoc/>
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
}
public override string ToString() => Type.ToString();
/// <summary>
/// An animation bound to a property on this object.
/// </summary>
public sealed class Animator
{
internal Animator(
string animatedProperty,
CompositionObject animatedObject,
CompositionAnimation animation,
AnimationController? controller)
{
AnimatedProperty = animatedProperty;
AnimatedObject = animatedObject;
Animation = animation;
Controller = controller;
}
/// <summary>
/// Gets the property being animated by this animator.
/// This could be the name of a property on the object, the name
/// of a property in the <see cref="CompositionPropertySet"/> of the
/// object, or a dotted path to a property of a property on the object
/// or in the <see cref="CompositionPropertySet"/> of the object.
/// </summary>
public string AnimatedProperty { get; }
/// <summary>
/// Gets the object whose property is being animated by this animator.
/// </summary>
public CompositionObject AnimatedObject { get; }
public CompositionAnimation Animation { get; }
/// <summary>
/// The controller for this <see cref="Animator"/> or null
/// if the animation is an <see cref="ExpressionAnimation"/>.
/// </summary>
public AnimationController? Controller { get; private set; }
/// <inheritdoc/>
public override string ToString() => AnimatedProperty;
}
}
}