561 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			561 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using UnityEditor.Callbacks;
 | |
| using UnityEngine;
 | |
| using UnityEngine.Events;
 | |
| using UnityEngine.Playables;
 | |
| using UnityEngine.SceneManagement;
 | |
| using UnityEngine.Timeline;
 | |
| using Object = UnityEngine.Object;
 | |
| 
 | |
| namespace UnityEditor.Timeline
 | |
| {
 | |
|     internal interface IWindowStateProvider
 | |
|     {
 | |
|         IWindowState windowState { get; }
 | |
|     }
 | |
| 
 | |
|     [EditorWindowTitle(title = "Timeline", useTypeNameAsIconName = true)]
 | |
|     partial class TimelineWindow : TimelineEditorWindow, IHasCustomMenu, IWindowStateProvider
 | |
|     {
 | |
|         [Serializable]
 | |
|         public class TimelineWindowPreferences
 | |
|         {
 | |
|             public EditMode.EditType editType = EditMode.EditType.Mix;
 | |
|             public TimeReferenceMode timeReferenceMode = TimeReferenceMode.Local;
 | |
|         }
 | |
| 
 | |
|         [SerializeField] TimelineWindowPreferences m_Preferences = new TimelineWindowPreferences();
 | |
|         public TimelineWindowPreferences preferences { get { return m_Preferences; } }
 | |
| 
 | |
|         [SerializeField]
 | |
|         EditorGUIUtility.EditorLockTracker m_LockTracker = new EditorGUIUtility.EditorLockTracker();
 | |
| 
 | |
|         readonly PreviewResizer m_PreviewResizer = new PreviewResizer();
 | |
|         bool m_LastFrameHadSequence;
 | |
|         bool m_ForceRefreshLastSelection;
 | |
|         int m_CurrentSceneHashCode = -1;
 | |
| 
 | |
|         [NonSerialized]
 | |
|         bool m_HasBeenInitialized;
 | |
| 
 | |
|         [SerializeField]
 | |
|         SequenceHierarchy m_SequenceHierarchy;
 | |
|         static SequenceHierarchy s_LastHierarchy;
 | |
| 
 | |
|         public static TimelineWindow instance { get; private set; }
 | |
|         public Rect clientArea { get; set; }
 | |
|         public bool isDragging { get; set; }
 | |
|         public static DirectorStyles styles { get { return DirectorStyles.Instance; } }
 | |
|         public List<TimelineTrackBaseGUI> allTracks
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 return treeView != null ? treeView.allTrackGuis : new List<TimelineTrackBaseGUI>();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public WindowState state { get; private set; }
 | |
| 
 | |
|         IWindowState IWindowStateProvider.windowState => state;
 | |
| 
 | |
|         public override bool locked
 | |
|         {
 | |
|             get
 | |
|             {
 | |
|                 // we can never be in a locked state if there is no timeline asset
 | |
|                 if (state.editSequence.asset == null)
 | |
|                     return false;
 | |
| 
 | |
|                 return m_LockTracker.isLocked;
 | |
|             }
 | |
|             set { m_LockTracker.isLocked = value; }
 | |
|         }
 | |
| 
 | |
|         public bool hierarchyChangedThisFrame { get; private set; }
 | |
| 
 | |
|         public TimelineWindow()
 | |
|         {
 | |
|             InitializeManipulators();
 | |
|             m_LockTracker.lockStateChanged.AddPersistentListener(OnLockStateChanged, UnityEventCallState.EditorAndRuntime);
 | |
|         }
 | |
| 
 | |
|         void OnLockStateChanged(bool locked)
 | |
|         {
 | |
|             // Make sure that upon unlocking, any selection change is updated
 | |
|             // Case 1123119 -- only force rebuild if not recording
 | |
|             if (!locked)
 | |
|                 RefreshSelection(state != null && !state.recording);
 | |
|         }
 | |
| 
 | |
|         void OnEnable()
 | |
|         {
 | |
|             if (m_SequencePath == null)
 | |
|                 m_SequencePath = new SequencePath();
 | |
| 
 | |
|             if (m_SequenceHierarchy == null)
 | |
|             {
 | |
|                 // The sequence hierarchy will become null if maximize on play is used for in/out of playmode
 | |
|                 // a static var will hang on to the reference
 | |
|                 if (s_LastHierarchy != null)
 | |
|                     m_SequenceHierarchy = s_LastHierarchy;
 | |
|                 else
 | |
|                     m_SequenceHierarchy = SequenceHierarchy.CreateInstance();
 | |
| 
 | |
|                 state = null;
 | |
|             }
 | |
|             s_LastHierarchy = m_SequenceHierarchy;
 | |
| 
 | |
|             titleContent = GetLocalizedTitleContent();
 | |
| 
 | |
|             UpdateTitle();
 | |
| 
 | |
|             m_PreviewResizer.Init("TimelineWindow");
 | |
| 
 | |
|             // Unmaximize fix : when unmaximizing, a new window is enabled and disabled. Prevent it from overriding the instance pointer.
 | |
|             if (instance == null)
 | |
|                 instance = this;
 | |
| 
 | |
|             AnimationClipCurveCache.Instance.OnEnable();
 | |
|             TrackAsset.OnClipPlayableCreate += m_PlayableLookup.UpdatePlayableLookup;
 | |
|             TrackAsset.OnTrackAnimationPlayableCreate += m_PlayableLookup.UpdatePlayableLookup;
 | |
| 
 | |
|             if (state == null)
 | |
|             {
 | |
|                 state = new WindowState(this, s_LastHierarchy);
 | |
|                 Initialize();
 | |
|                 RefreshSelection(true);
 | |
|                 m_ForceRefreshLastSelection = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void OnDisable()
 | |
|         {
 | |
|             if (instance == this)
 | |
|                 instance = null;
 | |
| 
 | |
|             if (state != null)
 | |
|                 state.Reset();
 | |
| 
 | |
|             if (instance == null)
 | |
|                 SelectionManager.RemoveTimelineSelection();
 | |
| 
 | |
|             AnimationClipCurveCache.Instance.OnDisable();
 | |
|             TrackAsset.OnClipPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup;
 | |
|             TrackAsset.OnTrackAnimationPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup;
 | |
|             TimelineWindowViewPrefs.SaveAll();
 | |
|             TimelineWindowViewPrefs.UnloadAllViewModels();
 | |
|         }
 | |
| 
 | |
|         void OnDestroy()
 | |
|         {
 | |
|             if (state != null)
 | |
|             {
 | |
|                 state.OnDestroy(instance == this);
 | |
|             }
 | |
|             m_HasBeenInitialized = false;
 | |
|             RemoveEditorCallbacks();
 | |
|             AnimationClipCurveCache.Instance.Clear();
 | |
|             TimelineAnimationUtilities.UnlinkAnimationWindow();
 | |
|         }
 | |
| 
 | |
|         void OnLostFocus()
 | |
|         {
 | |
|             isDragging = false;
 | |
| 
 | |
|             if (state != null)
 | |
|                 state.captured.Clear();
 | |
| 
 | |
|             Repaint();
 | |
|         }
 | |
| 
 | |
|         void OnHierarchyChange()
 | |
|         {
 | |
|             hierarchyChangedThisFrame = true;
 | |
|             Repaint();
 | |
|         }
 | |
| 
 | |
|         void OnStateChange()
 | |
|         {
 | |
|             state.UpdateRecordingState();
 | |
|             state.editSequence.InvalidateChildAssetCache();
 | |
|             if (treeView != null && state.editSequence.asset != null)
 | |
|                 treeView.Reload();
 | |
|             if (m_MarkerHeaderGUI != null)
 | |
|                 m_MarkerHeaderGUI.Rebuild();
 | |
|         }
 | |
| 
 | |
|         void OnGUI()
 | |
|         {
 | |
|             InitializeGUIIfRequired();
 | |
|             UpdateGUIConstants();
 | |
|             UpdateViewStateHash();
 | |
| 
 | |
|             EditMode.HandleModeClutch(); // TODO We Want that here?
 | |
| 
 | |
|             DetectStylesChange();
 | |
|             DetectActiveSceneChanges();
 | |
|             DetectStateChanges();
 | |
| 
 | |
|             state.ProcessStartFramePendingUpdates();
 | |
| 
 | |
|             var clipRect = new Rect(0.0f, 0.0f, position.width, position.height);
 | |
| 
 | |
|             using (new GUIViewportScope(clipRect))
 | |
|                 state.InvokeWindowOnGuiStarted(Event.current);
 | |
| 
 | |
|             if (Event.current.type == EventType.MouseDrag && state != null && state.mouseDragLag > 0.0f)
 | |
|             {
 | |
|                 state.mouseDragLag -= Time.deltaTime;
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (PerformUndo())
 | |
|                 return;
 | |
| 
 | |
|             if (state != null && state.ignorePreview && state.playing)
 | |
|             {
 | |
|                 if (state.recording)
 | |
|                     state.recording = false;
 | |
|                 Repaint();
 | |
|             }
 | |
| 
 | |
|             clientArea = position;
 | |
| 
 | |
|             PlaybackScroller.AutoScroll(state);
 | |
|             DoLayout();
 | |
| 
 | |
|             // overlays
 | |
|             if (state.captured.Count > 0)
 | |
|             {
 | |
|                 using (new GUIViewportScope(clipRect))
 | |
|                 {
 | |
|                     foreach (var o in state.captured)
 | |
|                     {
 | |
|                         o.Overlay(Event.current, state);
 | |
|                     }
 | |
|                     Repaint();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             if (state.showQuadTree)
 | |
|             {
 | |
|                 var fillColor = new Color(1.0f, 1.0f, 1.0f, 0.1f);
 | |
|                 state.spacePartitioner.DebugDraw(fillColor, Color.yellow);
 | |
|                 state.headerSpacePartitioner.DebugDraw(fillColor, Color.green);
 | |
|             }
 | |
| 
 | |
|             // attempt another rebuild -- this will avoid 1 frame flashes
 | |
|             if (Event.current.type == EventType.Repaint)
 | |
|             {
 | |
|                 RebuildGraphIfNecessary();
 | |
|                 state.ProcessEndFramePendingUpdates();
 | |
|             }
 | |
| 
 | |
|             using (new GUIViewportScope(clipRect))
 | |
|             {
 | |
|                 if (Event.current.type == EventType.Repaint)
 | |
|                     EditMode.inputHandler.OnGUI(state, Event.current);
 | |
|             }
 | |
| 
 | |
|             if (Event.current.type == EventType.Repaint)
 | |
|             {
 | |
|                 hierarchyChangedThisFrame = false;
 | |
|             }
 | |
| 
 | |
|             if (Event.current.type == EventType.Layout)
 | |
|             {
 | |
|                 UpdateTitle();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void UpdateTitle()
 | |
|         {
 | |
| #if UNITY_2020_2_OR_NEWER
 | |
|             bool dirty = false;
 | |
|             List<Object> children = state?.editSequence.cachedChildAssets;
 | |
|             if (children != null)
 | |
|             {
 | |
|                 foreach (var child in children)
 | |
|                 {
 | |
|                     dirty = EditorUtility.IsDirty(child);
 | |
|                     if (dirty)
 | |
|                     {
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             hasUnsavedChanges = dirty;
 | |
| #endif
 | |
|         }
 | |
| 
 | |
|         static void DetectStylesChange()
 | |
|         {
 | |
|             DirectorStyles.ReloadStylesIfNeeded();
 | |
|         }
 | |
| 
 | |
|         void DetectActiveSceneChanges()
 | |
|         {
 | |
|             if (m_CurrentSceneHashCode == -1)
 | |
|             {
 | |
|                 m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode();
 | |
|             }
 | |
| 
 | |
|             if (m_CurrentSceneHashCode != SceneManager.GetActiveScene().GetHashCode())
 | |
|             {
 | |
|                 bool isSceneStillLoaded = false;
 | |
|                 for (int a = 0; a < SceneManager.sceneCount; a++)
 | |
|                 {
 | |
|                     var scene = SceneManager.GetSceneAt(a);
 | |
|                     if (scene.GetHashCode() == m_CurrentSceneHashCode && scene.isLoaded)
 | |
|                     {
 | |
|                         isSceneStillLoaded = true;
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 if (!isSceneStillLoaded)
 | |
|                 {
 | |
|                     if (!locked)
 | |
|                         ClearTimeline();
 | |
|                     m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void DetectStateChanges()
 | |
|         {
 | |
|             if (state != null)
 | |
|             {
 | |
|                 foreach (var sequenceState in state.allSequences)
 | |
|                 {
 | |
|                     sequenceState.ResetIsReadOnly();
 | |
|                 }
 | |
|                 // detect if the sequence was removed under our feet
 | |
|                 if (m_LastFrameHadSequence && state.editSequence.asset == null)
 | |
|                 {
 | |
|                     ClearTimeline();
 | |
|                 }
 | |
|                 m_LastFrameHadSequence = state.editSequence.asset != null;
 | |
| 
 | |
|                 // the currentDirector can get set to null by a deletion or scene unloading so polling is required
 | |
|                 if (state.editSequence.director == null)
 | |
|                 {
 | |
|                     state.recording = false;
 | |
|                     state.previewMode = false;
 | |
| 
 | |
|                     if (locked)
 | |
|                     {
 | |
|                         //revert lock if the original context was not asset mode
 | |
|                         if (!state.masterSequence.isAssetOnly)
 | |
|                             locked = false;
 | |
|                     }
 | |
| 
 | |
|                     if (!locked && m_LastFrameHadSequence)
 | |
|                     {
 | |
|                         // the user may be adding a new PlayableDirector to a selected GameObject, make sure the timeline editor is shows the proper director if none is already showing
 | |
|                         var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null;
 | |
|                         var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null;
 | |
|                         if (selectedDirector != null)
 | |
|                         {
 | |
|                             SetTimeline(selectedDirector);
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             state.masterSequence.isAssetOnly = true;
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     // the user may have changed the timeline associated with the current director
 | |
|                     if (state.editSequence.asset != state.editSequence.director.playableAsset)
 | |
|                     {
 | |
|                         if (!locked)
 | |
|                         {
 | |
|                             SetTimeline(state.editSequence.director);
 | |
|                         }
 | |
|                         else
 | |
|                         {
 | |
|                             // Keep locked on the current timeline but set the current director to null since it's not the timeline owner anymore
 | |
|                             SetTimeline(state.editSequence.asset);
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void Initialize()
 | |
|         {
 | |
|             if (!m_HasBeenInitialized)
 | |
|             {
 | |
|                 InitializeStateChange();
 | |
|                 InitializeEditorCallbacks();
 | |
|                 m_HasBeenInitialized = true;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void RefreshLastSelectionIfRequired()
 | |
|         {
 | |
|             // case 1088918 - workaround for the instanceID to object cache being update during Awake.
 | |
|             // This corrects any playableDirector ptrs with the correct cached version
 | |
|             // This can happen when going from edit to playmode
 | |
|             if (m_ForceRefreshLastSelection)
 | |
|             {
 | |
|                 m_ForceRefreshLastSelection = false;
 | |
|                 RestoreLastSelection(true);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void InitializeGUIIfRequired()
 | |
|         {
 | |
|             RefreshLastSelectionIfRequired();
 | |
|             InitializeTimeArea();
 | |
|             if (treeView == null && state.editSequence.asset != null)
 | |
|             {
 | |
|                 treeView = new TimelineTreeViewGUI(this, state.editSequence.asset, position);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void UpdateGUIConstants()
 | |
|         {
 | |
|             m_HorizontalScrollBarSize =
 | |
|                 GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top;
 | |
|             m_VerticalScrollBarSize = (treeView != null && treeView.showingVerticalScrollBar)
 | |
|                 ? GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left
 | |
|                 : 0;
 | |
|         }
 | |
| 
 | |
|         void UpdateViewStateHash()
 | |
|         {
 | |
|             if (Event.current.type == EventType.Layout)
 | |
|                 state.UpdateViewStateHash();
 | |
|         }
 | |
| 
 | |
|         static bool PerformUndo()
 | |
|         {
 | |
|             if (!Event.current.isKey)
 | |
|                 return false;
 | |
| 
 | |
|             if (Event.current.keyCode != KeyCode.Z)
 | |
|                 return false;
 | |
| 
 | |
|             if (!EditorGUI.actionKey)
 | |
|                 return false;
 | |
| 
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         public void RebuildGraphIfNecessary(bool evaluate = true)
 | |
|         {
 | |
|             if (state == null || currentMode.mode != TimelineModes.Active || state.editSequence.director == null || state.editSequence.asset == null)
 | |
|                 return;
 | |
| 
 | |
|             if (state.rebuildGraph)
 | |
|             {
 | |
|                 // rebuilding the graph resets the time
 | |
|                 double time = state.editSequence.time;
 | |
| 
 | |
|                 var wasPlaying = false;
 | |
| 
 | |
|                 // disable preview mode,
 | |
|                 if (!state.ignorePreview)
 | |
|                 {
 | |
|                     wasPlaying = state.playing;
 | |
| 
 | |
|                     state.previewMode = false;
 | |
|                     state.GatherProperties(state.masterSequence.director);
 | |
|                 }
 | |
|                 state.RebuildPlayableGraph();
 | |
|                 state.editSequence.time = time;
 | |
| 
 | |
|                 if (wasPlaying)
 | |
|                     state.Play();
 | |
| 
 | |
|                 if (evaluate)
 | |
|                 {
 | |
|                     // put the scene back in the correct state
 | |
|                     state.EvaluateImmediate();
 | |
| 
 | |
|                     // this is necessary to see accurate results when inspector refreshes
 | |
|                     // case 1154802 - this will property re-force time on the director, so
 | |
|                     //  the play head won't snap back to the timeline duration on rebuilds
 | |
|                     if (!state.playing)
 | |
|                         state.Evaluate();
 | |
|                 }
 | |
|                 Repaint();
 | |
|             }
 | |
| 
 | |
|             state.rebuildGraph = false;
 | |
|         }
 | |
| 
 | |
|         // for tests
 | |
|         public new void RepaintImmediately()
 | |
|         {
 | |
|             base.RepaintImmediately();
 | |
|         }
 | |
| 
 | |
|         internal static bool IsEditingTimelineAsset(TimelineAsset timelineAsset)
 | |
|         {
 | |
|             return instance != null && instance.state != null && instance.state.editSequence.asset == timelineAsset;
 | |
|         }
 | |
| 
 | |
|         internal static void RepaintIfEditingTimelineAsset(TimelineAsset timelineAsset)
 | |
|         {
 | |
|             if (IsEditingTimelineAsset(timelineAsset))
 | |
|                 instance.Repaint();
 | |
|         }
 | |
| 
 | |
|         internal class DoCreateTimeline : ProjectWindowCallback.EndNameEditAction
 | |
|         {
 | |
|             public override void Action(int instanceId, string pathName, string resourceFile)
 | |
|             {
 | |
|                 var timeline = TimelineUtility.CreateAndSaveTimelineAsset(pathName);
 | |
|                 ProjectWindowUtil.ShowCreatedAsset(timeline);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         [MenuItem("Assets/Create/Timeline/Timeline", false, -124)]
 | |
|         public static void CreateNewTimeline()
 | |
|         {
 | |
|             var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D;
 | |
|             ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, CreateInstance<DoCreateTimeline>(), "New Timeline.playable", icon, null);
 | |
|         }
 | |
| 
 | |
|         [MenuItem("Window/Sequencing/Timeline", false, 1)]
 | |
|         public static void ShowWindow()
 | |
|         {
 | |
|             GetWindow<TimelineWindow>(typeof(SceneView));
 | |
|             instance.Focus();
 | |
|         }
 | |
| 
 | |
|         [OnOpenAsset(1)]
 | |
|         public static bool OnDoubleClick(int instanceID, int line)
 | |
|         {
 | |
|             var assetDoubleClicked = SelectionUtility.IdToObject(instanceID) as TimelineAsset;
 | |
|             if (assetDoubleClicked == null)
 | |
|                 return false;
 | |
| 
 | |
|             ShowWindow();
 | |
|             instance.SetTimeline(assetDoubleClicked);
 | |
| 
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         public virtual void AddItemsToMenu(GenericMenu menu)
 | |
|         {
 | |
|             bool disabled = state == null || state.editSequence.asset == null;
 | |
| 
 | |
|             m_LockTracker.AddItemsToMenu(menu, disabled);
 | |
|         }
 | |
| 
 | |
|         protected virtual void ShowButton(Rect r)
 | |
|         {
 | |
|             bool disabled = state == null || state.editSequence.asset == null;
 | |
| 
 | |
|             m_LockTracker.ShowButton(r, DirectorStyles.Instance.timelineLockButton, disabled);
 | |
|         }
 | |
|     }
 | |
| }
 |