161 lines
6.6 KiB
C#
161 lines
6.6 KiB
C#
|
using System;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Timeline;
|
||
|
|
||
|
namespace UnityEditor.Timeline
|
||
|
{
|
||
|
partial class TimelineWindow
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// The public Breadcrumb navigation controller, accessible through TimelineEditorWindow
|
||
|
/// </summary>
|
||
|
public override TimelineNavigator navigator => new TimelineNavigator(this);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Implementation of TimelineNavigator
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Always use TimelineNavigator, not this class.
|
||
|
///
|
||
|
/// The class acts as a handle on the TimelineWindow, and lets users navigate the breadcrumbs and dive into subtimelines
|
||
|
/// </remarks>
|
||
|
internal class TimelineNavigatorImpl
|
||
|
{
|
||
|
/// <summary>
|
||
|
///
|
||
|
/// </summary>
|
||
|
/// <param name="window"></param>
|
||
|
public TimelineNavigatorImpl(IWindowStateProvider window)
|
||
|
{
|
||
|
if (window == null)
|
||
|
throw new ArgumentNullException(nameof(window),
|
||
|
"TimelineNavigator cannot be used with a null window");
|
||
|
m_Window = window;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a SequenceContext from the top of the breadcrumb stack
|
||
|
/// </summary>
|
||
|
/// <returns></returns>
|
||
|
public SequenceContext GetCurrentContext()
|
||
|
{
|
||
|
return GetBreadcrumbs().LastOrDefault();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a SequenceContext from the second to last breadcrumb in the list
|
||
|
/// </summary>
|
||
|
/// <returns>Valid context if there is a parent, Invalid context otherwise</returns>
|
||
|
public SequenceContext GetParentContext()
|
||
|
{
|
||
|
//If the edit sequence is the master sequence, there is no parent context
|
||
|
if (windowState.editSequence == windowState.masterSequence)
|
||
|
return SequenceContext.Invalid;
|
||
|
var contexts = GetBreadcrumbs();
|
||
|
var length = contexts.Count();
|
||
|
return contexts.ElementAtOrDefault(length - 2);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a SequenceContext from the top of the breadcrumb stack
|
||
|
/// </summary>
|
||
|
/// <returns>Always returns a valid SequenceContext</returns>
|
||
|
public SequenceContext GetRootContext()
|
||
|
{
|
||
|
return GetBreadcrumbs().FirstOrDefault();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates SequenceContexts for all the child Timelines
|
||
|
/// </summary>
|
||
|
/// <returns>Collection of SequenceContexts. Can be empty if there are no valid child contexts</returns>
|
||
|
public IEnumerable<SequenceContext> GetChildContexts()
|
||
|
{
|
||
|
return windowState.GetSubSequences();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates SequenceContexts from the breadcrumb stack, from top to bottom
|
||
|
/// </summary>
|
||
|
/// <returns>Collection of SequenceContexts. Should never be empty</returns>
|
||
|
public IEnumerable<SequenceContext> GetBreadcrumbs()
|
||
|
{
|
||
|
return CollectBreadcrumbContexts();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Changes the current Timeline shown in the TimelineWindow to a new SequenceContext (if different and valid)
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Should only ever accept SequenceContexts that are in the breadcrumbs. SetTimeline is the proper
|
||
|
/// method to use to switch root Timelines.
|
||
|
/// </remarks>
|
||
|
/// <param name="context">A valid SequenceContext. <paramref name="context"/> should always be found in the breadcrumbs</param>
|
||
|
/// <exception cref="System.ArgumentException"> The context is not valid</exception>
|
||
|
/// <exception cref="System.InvalidOperationException"> The context is not a valid navigation destination.</exception>
|
||
|
public void NavigateTo(SequenceContext context)
|
||
|
{
|
||
|
if (!context.IsValid())
|
||
|
throw new ArgumentException(
|
||
|
$"Argument {nameof(context)} is not valid. Check validity with SequenceContext.IsValid.");
|
||
|
|
||
|
//If the provided context is the current context
|
||
|
if (windowState.editSequence.hostClip == context.clip &&
|
||
|
windowState.editSequence.director == context.director &&
|
||
|
windowState.editSequence.asset == context.director.playableAsset)
|
||
|
{
|
||
|
return; // Nothing to do
|
||
|
}
|
||
|
|
||
|
if (context.clip == null)
|
||
|
{
|
||
|
if (context.director != windowState.masterSequence.director)
|
||
|
throw new InvalidOperationException($"{nameof(context)} is not a valid destination in this context. " +
|
||
|
$"To change the root context, use TimelineEditorWindow.SetTimeline instead.");
|
||
|
}
|
||
|
|
||
|
var children = GetChildContexts().ToArray();
|
||
|
var breadcrumbs = CollectBreadcrumbContexts().ToArray();
|
||
|
|
||
|
if (!children.Contains(context) && !breadcrumbs.Contains(context))
|
||
|
{
|
||
|
throw new InvalidOperationException(
|
||
|
"The provided SequenceContext is not a valid destination. " +
|
||
|
"Use GetChildContexts or GetBreadcrumbs to acquire valid destination contexts.");
|
||
|
}
|
||
|
|
||
|
if (children.Contains(context))
|
||
|
{
|
||
|
windowState.SetCurrentSequence(context.director.playableAsset as TimelineAsset, context.director, context.clip);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var idx = Array.IndexOf(breadcrumbs, context);
|
||
|
if (idx != -1)
|
||
|
{
|
||
|
windowState.PopSequencesUntilCount(idx + 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private IWindowState windowState
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
if (m_Window == null || m_Window.windowState == null)
|
||
|
throw new InvalidOperationException("The Window associated to this instance has been destroyed");
|
||
|
return m_Window.windowState;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private IEnumerable<SequenceContext> CollectBreadcrumbContexts()
|
||
|
{
|
||
|
return windowState.allSequences?.Select(s => new SequenceContext(s.director, s.hostClip));
|
||
|
}
|
||
|
|
||
|
private readonly IWindowStateProvider m_Window;
|
||
|
}
|
||
|
}
|
||
|
}
|