A game about forced loneliness, made by TACStudios
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}