A game about forced loneliness, made by TACStudios
at master 160 lines 6.8 kB view raw
1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5using UnityEngine.Timeline; 6 7namespace UnityEditor.Timeline 8{ 9 partial class TimelineWindow 10 { 11 /// <summary> 12 /// The public Breadcrumb navigation controller, accessible through TimelineEditorWindow 13 /// </summary> 14 public override TimelineNavigator navigator => new TimelineNavigator(this); 15 16 /// <summary> 17 /// Implementation of TimelineNavigator 18 /// </summary> 19 /// <remarks> 20 /// Always use TimelineNavigator, not this class. 21 /// 22 /// The class acts as a handle on the TimelineWindow, and lets users navigate the breadcrumbs and dive into subtimelines 23 /// </remarks> 24 internal class TimelineNavigatorImpl 25 { 26 /// <summary> 27 /// 28 /// </summary> 29 /// <param name="window"></param> 30 public TimelineNavigatorImpl(IWindowStateProvider window) 31 { 32 if (window == null) 33 throw new ArgumentNullException(nameof(window), 34 "TimelineNavigator cannot be used with a null window"); 35 m_Window = window; 36 } 37 38 /// <summary> 39 /// Creates a SequenceContext from the top of the breadcrumb stack 40 /// </summary> 41 /// <returns></returns> 42 public SequenceContext GetCurrentContext() 43 { 44 return GetBreadcrumbs().LastOrDefault(); 45 } 46 47 /// <summary> 48 /// Creates a SequenceContext from the second to last breadcrumb in the list 49 /// </summary> 50 /// <returns>Valid context if there is a parent, Invalid context otherwise</returns> 51 public SequenceContext GetParentContext() 52 { 53 //If the edit sequence is the master sequence, there is no parent context 54 if (windowState.editSequence == windowState.masterSequence) 55 return SequenceContext.Invalid; 56 var contexts = GetBreadcrumbs(); 57 var length = contexts.Count(); 58 return contexts.ElementAtOrDefault(length - 2); 59 } 60 61 /// <summary> 62 /// Creates a SequenceContext from the top of the breadcrumb stack 63 /// </summary> 64 /// <returns>Always returns a valid SequenceContext</returns> 65 public SequenceContext GetRootContext() 66 { 67 return GetBreadcrumbs().FirstOrDefault(); 68 } 69 70 /// <summary> 71 /// Creates SequenceContexts for all the child Timelines 72 /// </summary> 73 /// <returns>Collection of SequenceContexts. Can be empty if there are no valid child contexts</returns> 74 public IEnumerable<SequenceContext> GetChildContexts() 75 { 76 return windowState.GetSubSequences(); 77 } 78 79 /// <summary> 80 /// Creates SequenceContexts from the breadcrumb stack, from top to bottom 81 /// </summary> 82 /// <returns>Collection of SequenceContexts. Should never be empty</returns> 83 public IEnumerable<SequenceContext> GetBreadcrumbs() 84 { 85 return CollectBreadcrumbContexts(); 86 } 87 88 /// <summary> 89 /// Changes the current Timeline shown in the TimelineWindow to a new SequenceContext (if different and valid) 90 /// </summary> 91 /// <remarks> 92 /// Should only ever accept SequenceContexts that are in the breadcrumbs. SetTimeline is the proper 93 /// method to use to switch root Timelines. 94 /// </remarks> 95 /// <param name="context">A valid SequenceContext. <paramref name="context"/> should always be found in the breadcrumbs</param> 96 /// <exception cref="System.ArgumentException"> The context is not valid</exception> 97 /// <exception cref="System.InvalidOperationException"> The context is not a valid navigation destination.</exception> 98 public void NavigateTo(SequenceContext context) 99 { 100 if (!context.IsValid()) 101 throw new ArgumentException( 102 $"Argument {nameof(context)} is not valid. Check validity with SequenceContext.IsValid."); 103 104 //If the provided context is the current context 105 if (windowState.editSequence.hostClip == context.clip && 106 windowState.editSequence.director == context.director && 107 windowState.editSequence.asset == context.director.playableAsset) 108 { 109 return; // Nothing to do 110 } 111 112 if (context.clip == null) 113 { 114 if (context.director != windowState.masterSequence.director) 115 throw new InvalidOperationException($"{nameof(context)} is not a valid destination in this context. " + 116 $"To change the root context, use TimelineEditorWindow.SetTimeline instead."); 117 } 118 119 var children = GetChildContexts().ToArray(); 120 var breadcrumbs = CollectBreadcrumbContexts().ToArray(); 121 122 if (!children.Contains(context) && !breadcrumbs.Contains(context)) 123 { 124 throw new InvalidOperationException( 125 "The provided SequenceContext is not a valid destination. " + 126 "Use GetChildContexts or GetBreadcrumbs to acquire valid destination contexts."); 127 } 128 129 if (children.Contains(context)) 130 { 131 windowState.SetCurrentSequence(context.director.playableAsset as TimelineAsset, context.director, context.clip); 132 return; 133 } 134 135 var idx = Array.IndexOf(breadcrumbs, context); 136 if (idx != -1) 137 { 138 windowState.PopSequencesUntilCount(idx + 1); 139 } 140 } 141 142 private IWindowState windowState 143 { 144 get 145 { 146 if (m_Window == null || m_Window.windowState == null) 147 throw new InvalidOperationException("The Window associated to this instance has been destroyed"); 148 return m_Window.windowState; 149 } 150 } 151 152 private IEnumerable<SequenceContext> CollectBreadcrumbContexts() 153 { 154 return windowState.allSequences?.Select(s => new SequenceContext(s.director, s.hostClip)); 155 } 156 157 private readonly IWindowStateProvider m_Window; 158 } 159 } 160}