File size: 6,805 Bytes
18a519f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
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;
        }
    }
}