SW 중심대학 OSS GIT 서버 박건태, 이승준, 고기완, 이준호 새로운 배포
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

537 lines
18 KiB

4 years ago
  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. //Case 1201405 : Check if the lock state is valid with the lock tracker state
  265. if (locked != m_LockTracker.isLocked)
  266. m_LockTracker.isLocked = locked;
  267. if (!locked && m_LastFrameHadSequence)
  268. {
  269. // 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
  270. var selectedGameObject = Selection.activeObject != null ? Selection.activeObject as GameObject : null;
  271. var selectedDirector = selectedGameObject != null ? selectedGameObject.GetComponent<PlayableDirector>() : null;
  272. if (selectedDirector != null)
  273. {
  274. SetCurrentTimeline(selectedDirector);
  275. }
  276. }
  277. }
  278. else
  279. {
  280. // the user may have changed the timeline associated with the current director
  281. if (state.editSequence.asset != state.editSequence.director.playableAsset)
  282. {
  283. if (!locked)
  284. {
  285. SetCurrentTimeline(state.editSequence.director);
  286. }
  287. else
  288. {
  289. // Keep locked on the current timeline but set the current director to null since it's not the timeline owner anymore
  290. SetCurrentTimeline(state.editSequence.asset);
  291. }
  292. }
  293. }
  294. }
  295. }
  296. void Initialize()
  297. {
  298. if (!m_HasBeenInitialized)
  299. {
  300. InitializeStateChange();
  301. InitializeEditorCallbacks();
  302. m_HasBeenInitialized = true;
  303. }
  304. }
  305. void RefreshLastSelectionIfRequired()
  306. {
  307. // case 1088918 - workaround for the instanceID to object cache being update during Awake.
  308. // This corrects any playableDirector ptrs with the correct cached version
  309. // This can happen when going from edit to playmode
  310. if (m_ForceRefreshLastSelection)
  311. {
  312. m_ForceRefreshLastSelection = false;
  313. RestoreLastSelection(true);
  314. }
  315. }
  316. void InitializeGUIIfRequired()
  317. {
  318. RefreshLastSelectionIfRequired();
  319. InitializeTimeArea();
  320. if (treeView == null && state.editSequence.asset != null)
  321. {
  322. treeView = new TimelineTreeViewGUI(this, state.editSequence.asset, position);
  323. }
  324. }
  325. void UpdateGUIConstants()
  326. {
  327. m_HorizontalScrollBarSize =
  328. GUI.skin.horizontalScrollbar.fixedHeight + GUI.skin.horizontalScrollbar.margin.top;
  329. m_VerticalScrollBarSize = (treeView != null && treeView.showingVerticalScrollBar)
  330. ? GUI.skin.verticalScrollbar.fixedWidth + GUI.skin.verticalScrollbar.margin.left
  331. : 0;
  332. }
  333. void UpdateViewStateHash()
  334. {
  335. if (Event.current.type == EventType.Layout)
  336. state.UpdateViewStateHash();
  337. }
  338. static bool PerformUndo()
  339. {
  340. if (!Event.current.isKey)
  341. return false;
  342. if (Event.current.keyCode != KeyCode.Z)
  343. return false;
  344. if (!EditorGUI.actionKey)
  345. return false;
  346. return true;
  347. }
  348. public void RebuildGraphIfNecessary(bool evaluate = true)
  349. {
  350. if (state == null || state.editSequence.director == null || state.editSequence.asset == null)
  351. return;
  352. if (state.rebuildGraph)
  353. {
  354. // rebuilding the graph resets the time
  355. double time = state.editSequence.time;
  356. var wasPlaying = false;
  357. // disable preview mode,
  358. if (!EditorApplication.isPlaying)
  359. {
  360. wasPlaying = state.playing;
  361. state.previewMode = false;
  362. state.GatherProperties(state.masterSequence.director);
  363. }
  364. state.RebuildPlayableGraph();
  365. state.editSequence.time = time;
  366. if (wasPlaying)
  367. state.Play();
  368. if (evaluate)
  369. {
  370. // put the scene back in the correct state
  371. state.EvaluateImmediate();
  372. // this is necessary to see accurate results when inspector refreshes
  373. // case 1154802 - this will property re-force time on the director, so
  374. // the play head won't snap back to the timeline duration on rebuilds
  375. if (!state.playing)
  376. state.Evaluate();
  377. }
  378. Repaint();
  379. }
  380. state.rebuildGraph = false;
  381. }
  382. // for tests
  383. public new void RepaintImmediately()
  384. {
  385. base.RepaintImmediately();
  386. }
  387. internal static bool IsEditingTimelineAsset(TimelineAsset timelineAsset)
  388. {
  389. return instance != null && instance.state != null && instance.state.editSequence.asset == timelineAsset;
  390. }
  391. internal static void RepaintIfEditingTimelineAsset(TimelineAsset timelineAsset)
  392. {
  393. if (IsEditingTimelineAsset(timelineAsset))
  394. instance.Repaint();
  395. }
  396. internal class DoCreateTimeline : ProjectWindowCallback.EndNameEditAction
  397. {
  398. public override void Action(int instanceId, string pathName, string resourceFile)
  399. {
  400. var timeline = ScriptableObject.CreateInstance<TimelineAsset>();
  401. AssetDatabase.CreateAsset(timeline, pathName);
  402. ProjectWindowUtil.ShowCreatedAsset(timeline);
  403. }
  404. }
  405. [MenuItem("Assets/Create/Timeline", false, 450)]
  406. public static void CreateNewTimeline()
  407. {
  408. var icon = EditorGUIUtility.IconContent("TimelineAsset Icon").image as Texture2D;
  409. ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance<DoCreateTimeline>(), "New Timeline.playable", icon, null);
  410. }
  411. [MenuItem("Window/Sequencing/Timeline", false, 1)]
  412. public static void ShowWindow()
  413. {
  414. GetWindow<TimelineWindow>(typeof(SceneView));
  415. instance.Focus();
  416. }
  417. [OnOpenAsset(1)]
  418. public static bool OnDoubleClick(int instanceID, int line)
  419. {
  420. var assetDoubleClicked = EditorUtility.InstanceIDToObject(instanceID) as TimelineAsset;
  421. if (assetDoubleClicked == null)
  422. return false;
  423. ShowWindow();
  424. instance.SetCurrentTimeline(assetDoubleClicked);
  425. return true;
  426. }
  427. public virtual void AddItemsToMenu(GenericMenu menu)
  428. {
  429. bool disabled = state == null || state.editSequence.asset == null;
  430. m_LockTracker.AddItemsToMenu(menu, disabled);
  431. }
  432. protected virtual void ShowButton(Rect r)
  433. {
  434. bool disabled = state == null || state.editSequence.asset == null;
  435. m_LockTracker.ShowButton(r, DirectorStyles.Instance.lockButton, disabled);
  436. }
  437. internal void TreeViewKeyboardCallback()
  438. {
  439. if (Event.current.type != EventType.KeyDown)
  440. return;
  441. if (TimelineAction.HandleShortcut(state, Event.current))
  442. {
  443. Event.current.Use();
  444. }
  445. }
  446. }
  447. }