Editor/MovieMaker/Session/TrackListView.cs
using Sandbox.MovieMaker;
using System.Linq;
namespace Editor.MovieMaker;
#nullable enable
/// <summary>
/// Describes which tracks should be shown in the track list / dope sheet.
/// </summary>
public sealed class TrackListView
{
public Session Session { get; }
public int StateHash { get; private set; }
public MovieTime PreviewOffset { get; set; }
public float Height { get; private set; }
private readonly SynchronizedSet<IProjectTrack, TrackView> _rootTracks;
private readonly Dictionary<IProjectTrack, TrackView> _trackDict = new();
public IReadOnlyList<TrackView> RootTracks => _rootTracks;
public event Action<TrackListView>? Changed;
public IEnumerable<TrackView> AllTracks => RootTracks.SelectMany( EnumerateDescendants );
public IEnumerable<TrackView> VisibleTracks => RootTracks.SelectMany( EnumerateVisibleDescendants );
public IEnumerable<TrackView> SelectedTracks => AllTracks.Where( x => x.IsSelected );
public IEnumerable<TrackView> EditableTracks =>
AllTracks.Where( x => x is { IsLocked: false, Target: ITrackProperty { CanWrite: true } } );
public TrackView? Find( IProjectTrack track ) => _trackDict.GetValueOrDefault( track );
public TrackView? Find( GameObject go ) => AllTracks.FirstOrDefault( x =>
x.Target is ITrackReference<GameObject> { IsBound: true } target && target.Value == go );
public TrackView? Find( Component cmp ) => AllTracks.FirstOrDefault( x =>
x.Target is ITrackReference { IsBound: true } target && target.Value == cmp );
public TrackView? Find( MovieResource resource ) => AllTracks.FirstOrDefault( x =>
x.Track is ProjectSequenceTrack );
private static IEnumerable<TrackView> EnumerateDescendants( TrackView track ) =>
[track, .. track.Children.SelectMany( EnumerateDescendants )];
private static IEnumerable<TrackView> EnumerateVisibleDescendants( TrackView track ) =>
track.IsExpanded
? [track, .. track.Children.SelectMany( EnumerateVisibleDescendants )]
: [track];
public TrackListView( Session session )
{
Session = session;
_rootTracks = new SynchronizedSet<IProjectTrack, TrackView>(
AddRootTrack, RemoveRootTrack, UpdateRootTrack );
Update();
}
private TrackView AddRootTrack( IProjectTrack source ) =>
new( this, null, source, Session.Binder.Get( source ) );
private void RemoveRootTrack( TrackView item ) => item.OnRemoved();
private bool UpdateRootTrack( IProjectTrack source, TrackView item ) => item.Update();
public void Update()
{
if ( !_rootTracks.Update( Session.Project.RootTracks.Order() ) ) return;
_trackDict.Clear();
foreach ( var trackView in AllTracks )
{
_trackDict[trackView.Track] = trackView;
}
var position = 0f;
var hashCode = new HashCode();
foreach ( var track in _rootTracks )
{
track.UpdatePosition( ref position );
hashCode.Add( track.StateHash );
position += 8f;
}
StateHash = hashCode.ToHashCode();
Height = position - 8f;
Changed?.Invoke( this );
}
public void Frame()
{
foreach ( var track in _rootTracks )
{
track.Frame();
}
}
public void DeselectAll()
{
foreach ( var view in SelectedTracks.ToArray() )
{
view.IsSelected = false;
}
}
}