Code/Clips/Extensions.cs
using System;
using Sandbox.MovieMaker.Compiled;
namespace Sandbox.MovieMaker;
#nullable enable
/// <summary>
/// Helper methods for working with <see cref="IClip"/> and <see cref="ITrack"/>.
/// </summary>
public static class ClipExtensions
{
/// <summary>
/// How deeply are we nested? Root tracks have depth <c>0</c>.
/// </summary>
public static int GetDepth( this ITrack track ) => track.Parent is null ? 0 : track.Parent.GetDepth() + 1;
public static (IReferenceTrack ReferenceTrack, IReadOnlyList<string> PropertyNames) GetPath( this ITrack track )
{
var names = new List<string>();
while ( track is not IReferenceTrack )
{
names.Add( track.Name );
track = track.Parent ?? throw new Exception( "Expected root tracks to be reference tracks." );
}
names.Reverse();
return ((IReferenceTrack)track, names);
}
/// <summary>
/// Searches <paramref name="clip"/> for a track with the given <paramref name="path"/>,
/// starting from the root level of the clip.
/// </summary>
public static ITrack? GetTrack( this IClip clip, params string[] path )
{
return clip.Tracks.FirstOrDefault( x => x.HasMatchingFullPath( path ) );
}
/// <inheritdoc cref="GetTrack(IClip,string[])"/>
public static ICompiledTrack? GetTrack( this MovieClip clip, params string[] path )
{
return (ICompiledTrack)((IClip)clip).GetTrack( path )!;
}
/// <summary>
/// Searches <paramref name="clip"/> for a track with the given <paramref name="path"/>,
/// starting from the root level of the clip.
/// </summary>
public static IReferenceTrack<T>? GetReference<T>( this IClip clip, params string[] path )
where T : class, IValid
{
return clip.Tracks
.OfType<IReferenceTrack<T>>()
.FirstOrDefault( x => x.HasMatchingFullPath( path ) );
}
/// <inheritdoc cref="GetReference{T}(IClip,string[])"/>
public static CompiledReferenceTrack<T>? GetReference<T>( this MovieClip clip, params string[] path )
where T : class, IValid
{
return (CompiledReferenceTrack<T>)((IClip)clip).GetReference<T>( path )!;
}
/// <summary>
/// Searches <paramref name="clip"/> for a property track with the given <paramref name="path"/>,
/// starting from the root level of the clip.
/// </summary>
/// <typeparam name="T">Property value type.</typeparam>
public static IPropertyTrack<T>? GetProperty<T>( this IClip clip, params string[] path )
{
return clip.Tracks
.OfType<IPropertyTrack<T>>()
.FirstOrDefault( x => x.HasMatchingFullPath( path ) );
}
public static IPropertyTrack<T>? GetProperty<T>( this IClip clip, Guid refTrackId, IReadOnlyList<string> path )
{
if ( clip.GetTrack( refTrackId ) is not { } refTrack ) return null;
return clip.Tracks
.OfType<IPropertyTrack<T>>()
.FirstOrDefault( x => x.HasMatchingFullPath( refTrack, path ) );
}
/// <inheritdoc cref="GetProperty{T}(IClip,string[])"/>
public static CompiledPropertyTrack<T>? GetProperty<T>( this MovieClip clip, params string[] path )
{
return (CompiledPropertyTrack<T>?)((IClip)clip).GetProperty<T>( path );
}
public static CompiledPropertyTrack<T>? GetProperty<T>( this MovieClip clip, Guid refTrackId, IReadOnlyList<string> path )
{
return (CompiledPropertyTrack<T>?)((IClip)clip).GetProperty<T>( refTrackId, path );
}
private static bool HasMatchingFullPath( this ITrack track, IReadOnlyList<string> path )
{
if ( track.GetDepth() != path.Count - 1 ) return false;
var parent = track;
for ( var i = path.Count - 1; i >= 0 && parent is not null; --i )
{
var name = path[i];
if ( parent.Name != name ) return false;
parent = parent.Parent;
}
return true;
}
private static bool HasMatchingFullPath( this ITrack track, IReferenceTrack refTrack, IReadOnlyList<string> propertyPath )
{
if ( track.GetDepth() - refTrack.GetDepth() != propertyPath.Count ) return false;
var parent = track;
for ( var i = propertyPath.Count - 1; i >= 0 && parent is not null; --i )
{
var name = propertyPath[i];
if ( parent.Name != name ) return false;
parent = parent.Parent;
}
return parent == refTrack;
}
/// <summary>
/// For each track in the given <paramref name="clip"/> that we have a mapped property for,
/// set the property value to whatever value is stored in that track at the given <paramref name="time"/>.
/// </summary>
public static bool Update( this IClip clip, MovieTime time, TrackBinder? binder = null )
{
var anyChanges = false;
foreach ( var track in clip.GetTracks( time ).OfType<IPropertyTrack>() )
{
anyChanges |= track.Update( time, binder );
}
return anyChanges;
}
/// <summary>
/// If we have a mapped property for <paramref name="track"/>, set the property value to whatever value
/// is stored in the track at the given <paramref name="time"/>.
/// </summary>
public static bool Update( this IPropertyTrack track, MovieTime time, TrackBinder? binder = null )
{
binder ??= TrackBinder.Default;
return binder.Get( track ).Update( track, time );
}
/// <inheritdoc cref="Update(IPropertyTrack,MovieTime,TrackBinder?)"/>
public static bool Update<T>( this IPropertyTrack<T> track, MovieTime time, TrackBinder? binder = null )
{
binder ??= TrackBinder.Default;
binder.Get( track ).Update( track, time );
return true;
}
}