using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.Timeline; namespace UnityEditor.Timeline { partial class TimelineWindow { /// /// The public Breadcrumb navigation controller, accessible through TimelineEditorWindow /// public override TimelineNavigator navigator => new TimelineNavigator(this); /// /// Implementation of TimelineNavigator /// /// /// 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 /// internal class TimelineNavigatorImpl { /// /// /// /// public TimelineNavigatorImpl(IWindowStateProvider window) { if (window == null) throw new ArgumentNullException(nameof(window), "TimelineNavigator cannot be used with a null window"); m_Window = window; } /// /// Creates a SequenceContext from the top of the breadcrumb stack /// /// public SequenceContext GetCurrentContext() { return GetBreadcrumbs().LastOrDefault(); } /// /// Creates a SequenceContext from the second to last breadcrumb in the list /// /// Valid context if there is a parent, Invalid context otherwise 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); } /// /// Creates a SequenceContext from the top of the breadcrumb stack /// /// Always returns a valid SequenceContext public SequenceContext GetRootContext() { return GetBreadcrumbs().FirstOrDefault(); } /// /// Creates SequenceContexts for all the child Timelines /// /// Collection of SequenceContexts. Can be empty if there are no valid child contexts public IEnumerable GetChildContexts() { return windowState.GetSubSequences(); } /// /// Creates SequenceContexts from the breadcrumb stack, from top to bottom /// /// Collection of SequenceContexts. Should never be empty public IEnumerable GetBreadcrumbs() { return CollectBreadcrumbContexts(); } /// /// Changes the current Timeline shown in the TimelineWindow to a new SequenceContext (if different and valid) /// /// /// Should only ever accept SequenceContexts that are in the breadcrumbs. SetTimeline is the proper /// method to use to switch root Timelines. /// /// A valid SequenceContext. should always be found in the breadcrumbs /// The context is not valid /// The context is not a valid navigation destination. 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 CollectBreadcrumbContexts() { return windowState.allSequences?.Select(s => new SequenceContext(s.director, s.hostClip)); } private readonly IWindowStateProvider m_Window; } } }