TimelineWindow.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  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. TimelineAnimationUtilities.UnlinkAnimationWindow();
  128. }
  129. void OnLostFocus()
  130. {
  131. isDragging = false;
  132. if (state != null)
  133. state.captured.Clear();
  134. Repaint();
  135. }
  136. void OnFocus()
  137. {
  138. if (state == null) return;
  139. // selection may have changed while Timeline Editor was looking away
  140. RefreshSelection(false);
  141. // Inline curves may have become out of sync
  142. RefreshInlineCurves();
  143. }
  144. void OnHierarchyChange()
  145. {
  146. hierarchyChangedThisFrame = true;
  147. Repaint();
  148. }
  149. void OnStateChange()
  150. {
  151. state.UpdateRecordingState();
  152. if (treeView != null && state.editSequence.asset != null)
  153. treeView.Reload();
  154. if (m_MarkerHeaderGUI != null)
  155. m_MarkerHeaderGUI.Rebuild();
  156. }
  157. void OnGUI()
  158. {
  159. InitializeGUIIfRequired();
  160. UpdateGUIConstants();
  161. UpdateViewStateHash();
  162. EditMode.HandleModeClutch(); // TODO We Want that here?
  163. DetectStylesChange();
  164. DetectActiveSceneChanges();
  165. DetectStateChanges();
  166. state.ProcessStartFramePendingUpdates();
  167. var clipRect = new Rect(0.0f, 0.0f, position.width, position.height);
  168. clipRect.xMin += state.sequencerHeaderWidth;
  169. using (new GUIViewportScope(clipRect))
  170. state.InvokeWindowOnGuiStarted(Event.current);
  171. if (Event.current.type == EventType.MouseDrag && state != null && state.mouseDragLag > 0.0f)
  172. {
  173. state.mouseDragLag -= Time.deltaTime;
  174. return;
  175. }
  176. if (PerformUndo())
  177. return;
  178. if (EditorApplication.isPlaying)
  179. {
  180. if (state != null)
  181. {
  182. if (state.recording)
  183. state.recording = false;
  184. }
  185. Repaint();
  186. }
  187. clientArea = position;
  188. PlaybackScroller.AutoScroll(state);
  189. DoLayout();
  190. // overlays
  191. if (state.captured.Count > 0)
  192. {
  193. using (new GUIViewportScope(clipRect))
  194. {
  195. foreach (var o in state.captured)
  196. {
  197. o.Overlay(Event.current, state);
  198. }
  199. Repaint();
  200. }
  201. }
  202. if (state.showQuadTree)
  203. state.spacePartitioner.DebugDraw();
  204. // attempt another rebuild -- this will avoid 1 frame flashes
  205. if (Event.current.type == EventType.Repaint)
  206. {
  207. RebuildGraphIfNecessary();
  208. state.ProcessEndFramePendingUpdates();
  209. }
  210. using (new GUIViewportScope(clipRect))
  211. {
  212. if (Event.current.type == EventType.Repaint)
  213. EditMode.inputHandler.OnGUI(state, Event.current);
  214. }
  215. if (Event.current.type == EventType.Repaint)
  216. hierarchyChangedThisFrame = false;
  217. }
  218. static void DetectStylesChange()
  219. {
  220. DirectorStyles.ReloadStylesIfNeeded();
  221. }
  222. void DetectActiveSceneChanges()
  223. {
  224. if (m_CurrentSceneHashCode == -1)
  225. {
  226. m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode();
  227. }
  228. if (m_CurrentSceneHashCode != SceneManager.GetActiveScene().GetHashCode())
  229. {
  230. bool isSceneStillLoaded = false;
  231. for (int a = 0; a < SceneManager.sceneCount; a++)
  232. {
  233. var scene = SceneManager.GetSceneAt(a);
  234. if (scene.GetHashCode() == m_CurrentSceneHashCode && scene.isLoaded)
  235. {
  236. isSceneStillLoaded = true;
  237. break;
  238. }
  239. }
  240. if (!isSceneStillLoaded)
  241. {
  242. if (!locked)
  243. ClearCurrentTimeline();
  244. m_CurrentSceneHashCode = SceneManager.GetActiveScene().GetHashCode();
  245. }
  246. }
  247. }
  248. void DetectStateChanges()
  249. {
  250. if (state != null)
  251. {
  252. state.editSequence.ResetIsReadOnly(); //Force reset readonly for asset flag for each frame.
  253. // detect if the sequence was removed under our feet
  254. if (m_LastFrameHadSequence && state.editSequence.asset == null)
  255. {
  256. ClearCurrentTimeline();
  257. }
  258. m_LastFrameHadSequence = state.editSequence.asset != null;
  259. // the currentDirector can get set to null by a deletion or scene unloading so polling is required
  260. if (state.editSequence.director == null)
  261. {
  262. state.recording = false;
  263. state.previewMode = false;
  264. if (!locked && m_LastFrameHadSequence)
  265. {
  266. // 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
  267. var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null;
  268. var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null;
  269. if (selectedDirector != null)
  270. {
  271. SetCurrentTimeline(selectedDirector);
  272. }
  273. }
  274. }
  275. else
  276. {
  277. // the user may have changed the timeline associated with the current director
  278. if (state.editSequence.asset != state.editSequence.director.playableAsset)
  279. {
  280. if (!locked)
  281. {
  282. SetCurrentTimeline(state.editSequence.director);
  283. }
  284. else
  285. {
  286. // Keep locked on the current timeline but set the current director to null since it's not the timeline owner anymore
  287. SetCurrentTimeline(state.editSequence.asset);
  288. }
  289. }
  290. }
  291. }
  292. }
  293. void Initialize()
  294. {
  295. if (!m_HasBeenInitialized)
  296. {
  297. InitializeStateChange();
  298. InitializeEditorCallbacks();
  299. m_HasBeenInitialized = true;
  300. }
  301. }
  302. void RefreshLastSelectionIfRequired()
  303. {
  304. // case 1088918 - workaround for the instanceID to object cache being update during Awake.
  305. // This corrects any playableDirector ptrs with the correct cached version
  306. // This can happen when going from edit to playmode
  307. if (m_ForceRefreshLastSelection)
  308. {
  309. m_ForceRefreshLastSelection = false;
  310. RestoreLastSelection(true);
  311. }
  312. }
  313. void InitializeGUIIfRequired()
  314. {
  315. RefreshLastSelectionIfRequired();
  316. InitializeTimeArea();
  317. if (treeView == null && state.editSequence.asset != null)
  318. {
  319. treeView = new TimelineTreeViewGUI(this, state.editSequence.asset, position);
  320. }
  321. }
  322. void UpdateGUIConstants()
  323. {
  324. m_HorizontalScrollBarSize =
  325. GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top;
  326. m_VerticalScrollBarSize = (treeView != null && treeView.showingVerticalScrollBar)
  327. ? GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left
  328. : 0;
  329. }
  330. void UpdateViewStateHash()
  331. {
  332. if (Event.current.type == EventType.Layout)
  333. state.UpdateViewStateHash();
  334. }
  335. static bool PerformUndo()
  336. {
  337. if (!Event.current.isKey)
  338. return false;
  339. if (Event.current.keyCode != KeyCode.Z)
  340. return false;
  341. if (!EditorGUI.actionKey)
  342. return false;
  343. return true;
  344. }
  345. public void RebuildGraphIfNecessary(bool evaluate = true)
  346. {
  347. if (state == null || state.editSequence.director == null || state.editSequence.asset == null)
  348. return;
  349. if (state.rebuildGraph)
  350. {
  351. // rebuilding the graph resets the time
  352. double time = state.editSequence.time;
  353. var wasPlaying = false;
  354. // disable preview mode,
  355. if (!EditorApplication.isPlaying)
  356. {
  357. wasPlaying = state.playing;
  358. state.previewMode = false;
  359. state.GatherProperties(state.masterSequence.director);
  360. }
  361. state.RebuildPlayableGraph();
  362. state.editSequence.time = time;
  363. if (wasPlaying)
  364. state.Play();
  365. if (evaluate)
  366. {
  367. // put the scene back in the correct state
  368. state.EvaluateImmediate();
  369. // this is necessary to see accurate results when inspector refreshes
  370. // case 1154802 - this will property re-force time on the director, so
  371. // the play head won't snap back to the timeline duration on rebuilds
  372. if (!state.playing)
  373. state.Evaluate();
  374. }
  375. Repaint();
  376. }
  377. state.rebuildGraph = false;
  378. }
  379. // for tests
  380. public new void RepaintImmediately()
  381. {
  382. base.RepaintImmediately();
  383. }
  384. internal static bool IsEditingTimelineAsset(TimelineAsset timelineAsset)
  385. {
  386. return instance != null && instance.state != null && instance.state.editSequence.asset == timelineAsset;
  387. }
  388. internal static void RepaintIfEditingTimelineAsset(TimelineAsset timelineAsset)
  389. {
  390. if (IsEditingTimelineAsset(timelineAsset))
  391. instance.Repaint();
  392. }
  393. internal class DoCreateTimeline : ProjectWindowCallback.EndNameEditAction
  394. {
  395. public override void Action(int instanceId, string pathName, string resourceFile)
  396. {
  397. var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
  398. AssetDatabase.CreateAsset(timeline, pathName);
  399. ProjectWindowUtil.ShowCreatedAsset(timeline);
  400. }
  401. }
  402. [MenuItem("Assets/Create/Timeline", false, 450)]
  403. public static void CreateNewTimeline()
  404. {
  405. var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D;
  406. ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance<DoCreateTimeline>(), "New Timeline.playable", icon, null);
  407. }
  408. [MenuItem("Window/Sequencing/Timeline", false, 1)]
  409. public static void ShowWindow()
  410. {
  411. GetWindow<TimelineWindow>(typeof(SceneView));
  412. instance.Focus();
  413. }
  414. [OnOpenAsset(1)]
  415. public static bool OnDoubleClick(int instanceID, int line)
  416. {
  417. var assetDoubleClicked = EditorUtility.InstanceIDToObject(instanceID) as TimelineAsset;
  418. if (assetDoubleClicked == null)
  419. return false;
  420. ShowWindow();
  421. instance.SetCurrentTimeline(assetDoubleClicked);
  422. return true;
  423. }
  424. public virtual void AddItemsToMenu(GenericMenu menu)
  425. {
  426. bool disabled = state == null || state.editSequence.asset == null;
  427. m_LockTracker.AddItemsToMenu(menu, disabled);
  428. }
  429. protected virtual void ShowButton(Rect r)
  430. {
  431. bool disabled = state == null || state.editSequence.asset == null;
  432. m_LockTracker.ShowButton(r, DirectorStyles.Instance.lockButton, disabled);
  433. }
  434. internal void TreeViewKeyboardCallback()
  435. {
  436. if (Event.current.type != EventType.KeyDown)
  437. return;
  438. if (TimelineAction.HandleShortcut(state, Event.current))
  439. {
  440. Event.current.Use();
  441. }
  442. }
  443. }
  444. }