TimelineWindow.cs 18 KB

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