TimelineWindow.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEditor.Callbacks;
  4. using UnityEngine;
  5. using UnityEngine.Events;
  6. using UnityEngine.Playables;
  7. using UnityEngine.SceneManagement;
  8. using UnityEngine.Timeline;
  9. namespace UnityEditor.Timeline
  10. {
  11. [EditorWindowTitle(title = "Timeline", useTypeNameAsIconName = true)]
  12. partial class TimelineWindow : EditorWindow, IHasCustomMenu
  13. {
  14. [Serializable]
  15. public class TimelineWindowPreferences
  16. {
  17. public bool playRangeLoopMode = true;
  18. public EditMode.EditType editType = EditMode.EditType.Mix;
  19. public TimeReferenceMode timeReferenceMode = TimeReferenceMode.Local;
  20. }
  21. [SerializeField] TimelineWindowPreferences m_Preferences = new TimelineWindowPreferences();
  22. public TimelineWindowPreferences preferences { get { return m_Preferences; } }
  23. [SerializeField]
  24. EditorGUIUtility.EditorLockTracker m_LockTracker = new EditorGUIUtility.EditorLockTracker();
  25. readonly PreviewResizer m_PreviewResizer = new PreviewResizer();
  26. bool m_LastFrameHadSequence;
  27. bool m_ForceRefreshLastSelection;
  28. int m_CurrentSceneHashCode = -1;
  29. [NonSerialized]
  30. bool m_HasBeenInitialized;
  31. [SerializeField]
  32. SequenceHierarchy m_SequenceHierarchy;
  33. static SequenceHierarchy s_LastHierarchy;
  34. public static TimelineWindow instance { get; private set; }
  35. public Rect clientArea { get; set; }
  36. public bool isDragging { get; set; }
  37. public static DirectorStyles styles { get { return DirectorStyles.Instance; } }
  38. public List<TimelineTrackBaseGUI> allTracks
  39. {
  40. get
  41. {
  42. return treeView != null ? treeView.allTrackGuis : new List<TimelineTrackBaseGUI>();
  43. }
  44. }
  45. public WindowState state { get; private set; }
  46. public bool locked
  47. {
  48. get
  49. {
  50. // we can never be in a locked state if there is no timeline asset
  51. if (state.editSequence.asset == null)
  52. return false;
  53. return m_LockTracker.isLocked;
  54. }
  55. set { m_LockTracker.isLocked = value; }
  56. }
  57. public bool hierarchyChangedThisFrame { get; private set; }
  58. public TimelineWindow()
  59. {
  60. InitializeManipulators();
  61. m_LockTracker.lockStateChanged.AddPersistentListener(OnLockStateChanged, UnityEventCallState.EditorAndRuntime);
  62. }
  63. void OnLockStateChanged(bool locked)
  64. {
  65. // Make sure that upon unlocking, any selection change is updated
  66. // Case 1123119 -- only force rebuild if not recording
  67. if (!locked)
  68. RefreshSelection(state != null && !state.recording);
  69. }
  70. void OnEnable()
  71. {
  72. if (m_SequencePath == null)
  73. m_SequencePath = new SequencePath();
  74. if (m_SequenceHierarchy == null)
  75. {
  76. // The sequence hierarchy will become null if maximize on play is used for in/out of playmode
  77. // a static var will hang on to the reference
  78. if (s_LastHierarchy != null)
  79. m_SequenceHierarchy = s_LastHierarchy;
  80. else
  81. m_SequenceHierarchy = SequenceHierarchy.CreateInstance();
  82. state = null;
  83. }
  84. s_LastHierarchy = m_SequenceHierarchy;
  85. titleContent = GetLocalizedTitleContent();
  86. m_PreviewResizer.Init("TimelineWindow");
  87. // Unmaximize fix : when unmaximizing, a new window is enabled and disabled. Prevent it from overriding the instance pointer.
  88. if (instance == null)
  89. instance = this;
  90. AnimationClipCurveCache.Instance.OnEnable();
  91. TrackAsset.OnClipPlayableCreate += m_PlayableLookup.UpdatePlayableLookup;
  92. TrackAsset.OnTrackAnimationPlayableCreate += m_PlayableLookup.UpdatePlayableLookup;
  93. if (state == null)
  94. {
  95. state = new WindowState(this, s_LastHierarchy);
  96. Initialize();
  97. RefreshSelection(true);
  98. m_ForceRefreshLastSelection = true;
  99. }
  100. }
  101. void OnDisable()
  102. {
  103. if (instance == this)
  104. instance = null;
  105. if (state != null)
  106. state.Reset();
  107. if (instance == null)
  108. SelectionManager.RemoveTimelineSelection();
  109. AnimationClipCurveCache.Instance.OnDisable();
  110. TrackAsset.OnClipPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup;
  111. TrackAsset.OnTrackAnimationPlayableCreate -= m_PlayableLookup.UpdatePlayableLookup;
  112. TimelineWindowViewPrefs.SaveAll();
  113. TimelineWindowViewPrefs.UnloadAllViewModels();
  114. }
  115. void OnDestroy()
  116. {
  117. if (state != null)
  118. {
  119. state.OnDestroy();
  120. }
  121. m_HasBeenInitialized = false;
  122. RemoveEditorCallbacks();
  123. AnimationClipCurveCache.Instance.Clear();
  124. TimelineAnimationUtilities.UnlinkAnimationWindow();
  125. }
  126. void OnLostFocus()
  127. {
  128. isDragging = false;
  129. if (state != null)
  130. state.captured.Clear();
  131. Repaint();
  132. }
  133. void OnFocus()
  134. {
  135. if (state == null) return;
  136. if (lastSelectedGO != Selection.activeObject)
  137. {
  138. // selection may have changed while Timeline Editor was looking away
  139. RefreshSelection(false);
  140. }
  141. }
  142. void OnHierarchyChange()
  143. {
  144. hierarchyChangedThisFrame = true;
  145. Repaint();
  146. }
  147. void OnStateChange()
  148. {
  149. state.UpdateRecordingState();
  150. if (treeView != null && state.editSequence.asset != null)
  151. treeView.Reload();
  152. if (m_MarkerHeaderGUI != null)
  153. m_MarkerHeaderGUI.Rebuild();
  154. }
  155. void OnGUI()
  156. {
  157. InitializeGUIIfRequired();
  158. UpdateGUIConstants();
  159. UpdateViewStateHash();
  160. EditMode.HandleModeClutch(); // TODO We Want that here?
  161. DetectStylesChange();
  162. DetectActiveSceneChanges();
  163. DetectStateChanges();
  164. state.ProcessStartFramePendingUpdates();
  165. var clipRect = new Rect(0.0f, 0.0f, position.width, position.height);
  166. using (new GUIViewportScope(clipRect))
  167. state.InvokeWindowOnGuiStarted(Event.current);
  168. if (Event.current.type == EventType.MouseDrag && state != null && state.mouseDragLag > 0.0f)
  169. {
  170. state.mouseDragLag -= Time.deltaTime;
  171. return;
  172. }
  173. if (PerformUndo())
  174. return;
  175. if (state != null && state.ignorePreview && state.playing)
  176. {
  177. if (state.recording)
  178. state.recording = false;
  179. Repaint();
  180. }
  181. clientArea = position;
  182. PlaybackScroller.AutoScroll(state);
  183. DoLayout();
  184. // overlays
  185. if (state.captured.Count > 0)
  186. {
  187. using (new GUIViewportScope(clipRect))
  188. {
  189. foreach (var o in state.captured)
  190. {
  191. o.Overlay(Event.current, state);
  192. }
  193. Repaint();
  194. }
  195. }
  196. if (state.showQuadTree)
  197. {
  198. var fillColor = new Color(1.0f, 1.0f, 1.0f, 0.1f);
  199. state.spacePartitioner.DebugDraw(fillColor, Color.yellow);
  200. state.headerSpacePartitioner.DebugDraw(fillColor, Color.green);
  201. }
  202. // attempt another rebuild -- this will avoid 1 frame flashes
  203. if (Event.current.type == EventType.Repaint)
  204. {
  205. RebuildGraphIfNecessary();
  206. state.ProcessEndFramePendingUpdates();
  207. }
  208. using (new GUIViewportScope(clipRect))
  209. {
  210. if (Event.current.type == EventType.Repaint)
  211. EditMode.inputHandler.OnGUI(state, Event.current);
  212. }
  213. if (Event.current.type == EventType.Repaint)
  214. hierarchyChangedThisFrame = false;
  215. }
  216. static void DetectStylesChange()
  217. {
  218. DirectorStyles.ReloadStylesIfNeeded();
  219. }
  220. void DetectActiveSceneChanges()
  221. {
  222. if (m_CurrentSceneHashCode == -1)
  223. {
  224. m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode();
  225. }
  226. if (m_CurrentSceneHashCode != SceneManager.GetActiveScene().GetHashCode())
  227. {
  228. bool isSceneStillLoaded = false;
  229. for (int a = 0; a < SceneManager.sceneCount; a++)
  230. {
  231. var scene = SceneManager.GetSceneAt(a);
  232. if (scene.GetHashCode() == m_CurrentSceneHashCode && scene.isLoaded)
  233. {
  234. isSceneStillLoaded = true;
  235. break;
  236. }
  237. }
  238. if (!isSceneStillLoaded)
  239. {
  240. if (!locked)
  241. ClearCurrentTimeline();
  242. m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode();
  243. }
  244. }
  245. }
  246. void DetectStateChanges()
  247. {
  248. if (state != null)
  249. {
  250. state.editSequence.ResetIsReadOnly(); //Force reset readonly for asset flag for each frame.
  251. // detect if the sequence was removed under our feet
  252. if (m_LastFrameHadSequence && state.editSequence.asset == null)
  253. {
  254. ClearCurrentTimeline();
  255. }
  256. m_LastFrameHadSequence = state.editSequence.asset != null;
  257. // the currentDirector can get set to null by a deletion or scene unloading so polling is required
  258. if (state.editSequence.director == null)
  259. {
  260. state.recording = false;
  261. state.previewMode = false;
  262. if (!locked && m_LastFrameHadSequence)
  263. {
  264. // 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
  265. var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null;
  266. var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null;
  267. if (selectedDirector != null)
  268. {
  269. SetCurrentTimeline(selectedDirector);
  270. }
  271. }
  272. }
  273. else
  274. {
  275. // the user may have changed the timeline associated with the current director
  276. if (state.editSequence.asset != state.editSequence.director.playableAsset)
  277. {
  278. if (!locked)
  279. {
  280. SetCurrentTimeline(state.editSequence.director);
  281. }
  282. else
  283. {
  284. // Keep locked on the current timeline but set the current director to null since it's not the timeline owner anymore
  285. SetCurrentTimeline(state.editSequence.asset);
  286. }
  287. }
  288. }
  289. }
  290. }
  291. void Initialize()
  292. {
  293. if (!m_HasBeenInitialized)
  294. {
  295. InitializeStateChange();
  296. InitializeEditorCallbacks();
  297. m_HasBeenInitialized = true;
  298. }
  299. }
  300. void RefreshLastSelectionIfRequired()
  301. {
  302. // case 1088918 - workaround for the instanceID to object cache being update during Awake.
  303. // This corrects any playableDirector ptrs with the correct cached version
  304. // This can happen when going from edit to playmode
  305. if (m_ForceRefreshLastSelection)
  306. {
  307. m_ForceRefreshLastSelection = false;
  308. RestoreLastSelection(true);
  309. }
  310. }
  311. void InitializeGUIIfRequired()
  312. {
  313. RefreshLastSelectionIfRequired();
  314. InitializeTimeArea();
  315. if (treeView == null && state.editSequence.asset != null)
  316. {
  317. treeView = new TimelineTreeViewGUI(this, state.editSequence.asset, position);
  318. }
  319. }
  320. void UpdateGUIConstants()
  321. {
  322. m_HorizontalScrollBarSize =
  323. GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top;
  324. m_VerticalScrollBarSize = (treeView != null && treeView.showingVerticalScrollBar)
  325. ? GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left
  326. : 0;
  327. }
  328. void UpdateViewStateHash()
  329. {
  330. if (Event.current.type == EventType.Layout)
  331. state.UpdateViewStateHash();
  332. }
  333. static bool PerformUndo()
  334. {
  335. if (!Event.current.isKey)
  336. return false;
  337. if (Event.current.keyCode != KeyCode.Z)
  338. return false;
  339. if (!EditorGUI.actionKey)
  340. return false;
  341. return true;
  342. }
  343. public void RebuildGraphIfNecessary(bool evaluate = true)
  344. {
  345. if (state == null || state.editSequence.director == null || state.editSequence.asset == null)
  346. return;
  347. if (state.rebuildGraph)
  348. {
  349. // rebuilding the graph resets the time
  350. double time = state.editSequence.time;
  351. var wasPlaying = false;
  352. // disable preview mode,
  353. if (!state.ignorePreview)
  354. {
  355. wasPlaying = state.playing;
  356. state.previewMode = false;
  357. state.GatherProperties(state.masterSequence.director);
  358. }
  359. state.RebuildPlayableGraph();
  360. state.editSequence.time = time;
  361. if (wasPlaying)
  362. state.Play();
  363. if (evaluate)
  364. {
  365. // put the scene back in the correct state
  366. state.EvaluateImmediate();
  367. // this is necessary to see accurate results when inspector refreshes
  368. // case 1154802 - this will property re-force time on the director, so
  369. // the play head won't snap back to the timeline duration on rebuilds
  370. if (!state.playing)
  371. state.Evaluate();
  372. }
  373. Repaint();
  374. }
  375. state.rebuildGraph = false;
  376. }
  377. // for tests
  378. public new void RepaintImmediately()
  379. {
  380. base.RepaintImmediately();
  381. }
  382. internal static bool IsEditingTimelineAsset(TimelineAsset timelineAsset)
  383. {
  384. return instance != null && instance.state != null && instance.state.editSequence.asset == timelineAsset;
  385. }
  386. internal static void RepaintIfEditingTimelineAsset(TimelineAsset timelineAsset)
  387. {
  388. if (IsEditingTimelineAsset(timelineAsset))
  389. instance.Repaint();
  390. }
  391. internal class DoCreateTimeline : ProjectWindowCallback.EndNameEditAction
  392. {
  393. public override void Action(int instanceId, string pathName, string resourceFile)
  394. {
  395. var timeline = TimelineUtility.CreateAndSaveTimelineAsset(pathName);
  396. ProjectWindowUtil.ShowCreatedAsset(timeline);
  397. }
  398. }
  399. [MenuItem("Assets/Create/Timeline", false, 450)]
  400. public static void CreateNewTimeline()
  401. {
  402. var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D;
  403. ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance<DoCreateTimeline>(), "New Timeline.playable", icon, null);
  404. }
  405. [MenuItem("Window/Sequencing/Timeline", false, 1)]
  406. public static void ShowWindow()
  407. {
  408. GetWindow<TimelineWindow>(typeof(SceneView));
  409. instance.Focus();
  410. }
  411. [OnOpenAsset(1)]
  412. public static bool OnDoubleClick(int instanceID, int line)
  413. {
  414. var assetDoubleClicked = EditorUtility.InstanceIDToObject(instanceID) as TimelineAsset;
  415. if (assetDoubleClicked == null)
  416. return false;
  417. ShowWindow();
  418. instance.SetCurrentTimeline(assetDoubleClicked);
  419. return true;
  420. }
  421. public virtual void AddItemsToMenu(GenericMenu menu)
  422. {
  423. bool disabled = state == null || state.editSequence.asset == null;
  424. m_LockTracker.AddItemsToMenu(menu, disabled);
  425. }
  426. protected virtual void ShowButton(Rect r)
  427. {
  428. bool disabled = state == null || state.editSequence.asset == null;
  429. m_LockTracker.ShowButton(r, DirectorStyles.Instance.timelineLockButton, disabled);
  430. }
  431. }
  432. }