TimelineWindow_Gui.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEditor.Experimental.SceneManagement;
  4. using UnityEngine;
  5. using UnityEngine.Timeline;
  6. namespace UnityEditor.Timeline
  7. {
  8. partial class TimelineWindow
  9. {
  10. struct MarkerOverlay
  11. {
  12. public IMarker marker;
  13. public Rect rect;
  14. public bool isSelected;
  15. public bool isCollapsed;
  16. public MarkerEditor editor;
  17. }
  18. enum TimelineItemArea
  19. {
  20. Header,
  21. Lines
  22. }
  23. [SerializeField] float m_HierarchySplitterPerc = WindowConstants.hierarchySplitterDefaultPercentage;
  24. static internal readonly TimelineMode s_ActiveMode = new TimelineActiveMode();
  25. static internal readonly TimelineMode s_EditAssetMode = new TimelineAssetEditionMode();
  26. static internal readonly TimelineMode s_InactiveMode = new TimelineInactiveMode();
  27. static internal readonly TimelineMode s_DisabledMode = new TimelineDisabledMode();
  28. static internal readonly TimelineMode s_PrefabOutOfContextMode = new TimelineAssetEditionMode();
  29. static internal readonly TimelineMode s_ReadonlyMode = new TimelineReadOnlyMode();
  30. int m_SplitterCaptured;
  31. float m_VerticalScrollBarSize, m_HorizontalScrollBarSize;
  32. List<MarkerOverlay> m_OverlayQueue = new List<MarkerOverlay>(100);
  33. float headerHeight
  34. {
  35. get
  36. {
  37. return WindowConstants.markerRowYPosition + (state.showMarkerHeader ? WindowConstants.markerRowHeight : 0.0f);
  38. }
  39. }
  40. public Rect markerHeaderRect
  41. {
  42. get { return new Rect(0.0f, WindowConstants.markerRowYPosition, state.sequencerHeaderWidth, WindowConstants.markerRowHeight); }
  43. }
  44. public Rect markerContentRect
  45. {
  46. get { return Rect.MinMaxRect(state.sequencerHeaderWidth, WindowConstants.markerRowYPosition, position.width, WindowConstants.markerRowYPosition + WindowConstants.markerRowHeight); }
  47. }
  48. Rect trackRect
  49. {
  50. get
  51. {
  52. var yMinHeight = headerHeight;
  53. return new Rect(0, yMinHeight, position.width, position.height - yMinHeight - horizontalScrollbarHeight);
  54. }
  55. }
  56. public Rect sequenceRect
  57. {
  58. get { return new Rect(0.0f, WindowConstants.markerRowYPosition, position.width - WindowConstants.sliderWidth, position.height - WindowConstants.timeAreaYPosition); }
  59. }
  60. public Rect sequenceHeaderRect
  61. {
  62. get { return new Rect(0.0f, WindowConstants.markerRowYPosition, state.sequencerHeaderWidth, position.height - WindowConstants.timeAreaYPosition); }
  63. }
  64. public Rect sequenceContentRect
  65. {
  66. get
  67. {
  68. return new Rect(
  69. state.sequencerHeaderWidth,
  70. WindowConstants.markerRowYPosition,
  71. position.width - state.sequencerHeaderWidth - (treeView != null && treeView.showingVerticalScrollBar ? WindowConstants.sliderWidth : 0),
  72. position.height - WindowConstants.markerRowYPosition - horizontalScrollbarHeight);
  73. }
  74. }
  75. public float verticalScrollbarWidth
  76. {
  77. get
  78. {
  79. return m_VerticalScrollBarSize;
  80. }
  81. }
  82. public float horizontalScrollbarHeight
  83. {
  84. get { return m_HorizontalScrollBarSize; }
  85. }
  86. float breadCrumbAreaWidth
  87. {
  88. get
  89. {
  90. return state.timeAreaRect.width - WindowConstants.selectorWidth - WindowConstants.cogButtonWidth - WindowConstants.cogButtonPadding;
  91. }
  92. }
  93. internal TimelineMode currentMode
  94. {
  95. get
  96. {
  97. if (state == null || state.editSequence.asset == null)
  98. return s_InactiveMode;
  99. if (state.editSequence.isReadOnly)
  100. return s_ReadonlyMode;
  101. if (state.editSequence.director == null || state.masterSequence.director == null)
  102. return s_EditAssetMode;
  103. if (PrefabUtility.IsPartOfPrefabAsset(state.editSequence.director))
  104. {
  105. var stage = PrefabStageUtility.GetCurrentPrefabStage();
  106. if (stage == null || !stage.IsPartOfPrefabContents(state.editSequence.director.gameObject))
  107. return s_PrefabOutOfContextMode;
  108. }
  109. if (!state.masterSequence.director.isActiveAndEnabled)
  110. return s_DisabledMode;
  111. return s_ActiveMode;
  112. }
  113. }
  114. void DoLayout()
  115. {
  116. var rawType = Event.current.rawType; // TODO: rawType seems to be broken after calling Use(), use this Hack and remove it once it's fixed.
  117. var mousePosition = Event.current.mousePosition; // mousePosition is also affected by this bug and does not reflect the original position after a Use()
  118. Initialize();
  119. HandleSplitterResize();
  120. var processManipulators = Event.current.type != EventType.Repaint && Event.current.type != EventType.Layout;
  121. if (processManipulators)
  122. {
  123. // Update what's under mouse the cursor
  124. PickerUtils.DoPick(state, mousePosition);
  125. if (state.editSequence.asset != null)
  126. m_PreTreeViewControl.HandleManipulatorsEvents(state);
  127. }
  128. SequencerGUI();
  129. if (processManipulators)
  130. {
  131. if (state.editSequence.asset != null)
  132. m_PostTreeViewControl.HandleManipulatorsEvents(state);
  133. }
  134. m_RectangleSelect.OnGUI(state, rawType, mousePosition);
  135. m_RectangleZoom.OnGUI(state, rawType, mousePosition);
  136. }
  137. void TimelineSectionGUI()
  138. {
  139. GUILayout.BeginVertical();
  140. {
  141. GUILayout.BeginHorizontal(EditorStyles.toolbar, GUILayout.Width(position.width - state.sequencerHeaderWidth));
  142. {
  143. DoSequenceSelectorGUI();
  144. DoBreadcrumbGUI();
  145. OptionsGUI();
  146. }
  147. GUILayout.EndHorizontal();
  148. TimelineGUI();
  149. }
  150. GUILayout.EndVertical();
  151. }
  152. void SplitterGUI()
  153. {
  154. if (!state.IsEditingAnEmptyTimeline())
  155. {
  156. var splitterLineRect = new Rect(state.sequencerHeaderWidth - 1.0f, 0.0f, 2.0f, clientArea.height);
  157. EditorGUI.DrawRect(splitterLineRect, DirectorStyles.Instance.customSkin.colorTopOutline3);
  158. }
  159. }
  160. void TrackViewsGUI()
  161. {
  162. using (new GUIViewportScope(trackRect))
  163. {
  164. TracksGUI(trackRect, state, currentMode.TrackState(state));
  165. }
  166. }
  167. void UserOverlaysGUI()
  168. {
  169. if (Event.current.type != EventType.Repaint)
  170. return;
  171. // the rect containing the time area plus the time ruler
  172. var screenRect = new Rect(
  173. state.sequencerHeaderWidth,
  174. WindowConstants.timeAreaYPosition,
  175. position.width - state.sequencerHeaderWidth - (treeView != null && treeView.showingVerticalScrollBar ? WindowConstants.sliderWidth : 0),
  176. position.height - WindowConstants.timeAreaYPosition - horizontalScrollbarHeight);
  177. var startTime = state.PixelToTime(screenRect.xMin);
  178. var endTime = state.PixelToTime(screenRect.xMax);
  179. using (new GUIViewportScope(screenRect))
  180. {
  181. foreach (var entry in m_OverlayQueue)
  182. {
  183. var uiState = MarkerUIStates.None;
  184. if (entry.isCollapsed)
  185. uiState |= MarkerUIStates.Collapsed;
  186. if (entry.isSelected)
  187. uiState |= MarkerUIStates.Selected;
  188. var region = new MarkerOverlayRegion(GUIClip.Clip(entry.rect), screenRect, startTime, endTime);
  189. try
  190. {
  191. entry.editor.DrawOverlay(entry.marker, uiState, region);
  192. }
  193. catch (Exception e)
  194. {
  195. Debug.LogException(e);
  196. }
  197. }
  198. }
  199. m_OverlayQueue.Clear();
  200. }
  201. void DrawHeaderBackground()
  202. {
  203. var rect = state.timeAreaRect;
  204. rect.xMin = 0.0f;
  205. EditorGUI.DrawRect(rect, DirectorStyles.Instance.customSkin.colorTimelineBackground);
  206. }
  207. void HandleBottomFillerDragAndDrop(Rect rect)
  208. {
  209. if (Event.current.type != EventType.DragUpdated &&
  210. Event.current.type != EventType.DragExited &&
  211. Event.current.type != EventType.DragPerform)
  212. return;
  213. if (instance.treeView == null || instance.treeView.timelineDragging == null)
  214. return;
  215. if (!rect.Contains(Event.current.mousePosition))
  216. return;
  217. instance.treeView.timelineDragging.DragElement(null, new Rect(), -1);
  218. }
  219. void DrawHeaderBackgroundBottomFiller()
  220. {
  221. var rect = sequenceRect;
  222. rect.yMin = rect.yMax;
  223. rect.yMax = rect.yMax + WindowConstants.sliderWidth;
  224. if (state.editSequence.asset != null && !state.IsEditingAnEmptyTimeline())
  225. {
  226. rect.width = state.sequencerHeaderWidth;
  227. }
  228. using (new GUIViewportScope(rect))
  229. {
  230. Graphics.DrawBackgroundRect(state, rect);
  231. }
  232. HandleBottomFillerDragAndDrop(rect);
  233. }
  234. void SequencerGUI()
  235. {
  236. var duration = state.editSequence.duration;
  237. DrawHeaderBackground();
  238. DurationGUI(TimelineItemArea.Header, duration);
  239. GUILayout.BeginHorizontal();
  240. {
  241. SequencerHeaderGUI();
  242. TimelineSectionGUI();
  243. }
  244. GUILayout.EndHorizontal();
  245. TrackViewsGUI();
  246. MarkerHeaderGUI();
  247. UserOverlaysGUI();
  248. DurationGUI(TimelineItemArea.Lines, duration);
  249. PlayRangeGUI(TimelineItemArea.Lines);
  250. TimeCursorGUI(TimelineItemArea.Lines);
  251. DrawHeaderBackgroundBottomFiller();
  252. SubTimelineRangeGUI();
  253. PlayRangeGUI(TimelineItemArea.Header);
  254. TimeCursorGUI(TimelineItemArea.Header);
  255. SplitterGUI();
  256. }
  257. void SubTimelineRangeGUI()
  258. {
  259. if (!state.IsEditingASubTimeline() || state.IsEditingAnEmptyTimeline()) return;
  260. var subTimelineOverlayColor = DirectorStyles.Instance.customSkin.colorSubSequenceOverlay;
  261. var range = state.editSequence.GetEvaluableRange();
  262. var area = new Vector2(state.TimeToPixel(range.start), state.TimeToPixel(range.end));
  263. var fullRect = sequenceContentRect;
  264. fullRect.yMin -= state.timeAreaRect.height;
  265. if (fullRect.xMin < area.x)
  266. {
  267. var before = fullRect;
  268. before.xMin = fullRect.xMin;
  269. before.xMax = Mathf.Min(area.x, fullRect.xMax);
  270. EditorGUI.DrawRect(before, subTimelineOverlayColor);
  271. }
  272. if (fullRect.xMax > area.y)
  273. {
  274. var after = fullRect;
  275. after.xMin = Mathf.Max(area.y, fullRect.xMin);
  276. after.xMax = fullRect.xMax;
  277. EditorGUI.DrawRect(after, subTimelineOverlayColor);
  278. // Space above the vertical scrollbar
  279. after.xMin = after.xMax;
  280. after.width = verticalScrollbarWidth;
  281. after.yMax = state.timeAreaRect.y + state.timeAreaRect.height + (state.showMarkerHeader ? WindowConstants.markerRowHeight : 0.0f);
  282. EditorGUI.DrawRect(after, subTimelineOverlayColor);
  283. }
  284. }
  285. void HandleSplitterResize()
  286. {
  287. state.mainAreaWidth = position.width;
  288. if (state.editSequence.asset == null)
  289. return;
  290. // Sequencer Header Splitter : The splitter has 6 pixels wide,center it around m_State.sequencerHeaderWidth. That's why there's this -3.
  291. Rect sequencerHeaderSplitterRect = new Rect(state.sequencerHeaderWidth - 3.0f, 0.0f, 6.0f, clientArea.height);
  292. EditorGUIUtility.AddCursorRect(sequencerHeaderSplitterRect, MouseCursor.SplitResizeLeftRight);
  293. if (Event.current.type == EventType.MouseDown)
  294. {
  295. if (sequencerHeaderSplitterRect.Contains(Event.current.mousePosition))
  296. m_SplitterCaptured = 1;
  297. }
  298. if (m_SplitterCaptured > 0)
  299. {
  300. if (Event.current.type == EventType.MouseUp)
  301. {
  302. m_SplitterCaptured = 0;
  303. Event.current.Use();
  304. }
  305. if (Event.current.type == EventType.MouseDrag)
  306. {
  307. if (m_SplitterCaptured == 1)
  308. {
  309. var percInc = Event.current.delta.x / position.width;
  310. m_HierarchySplitterPerc = Mathf.Clamp(m_HierarchySplitterPerc + percInc, WindowConstants.minHierarchySplitter, WindowConstants.maxHierarchySplitter);
  311. state.sequencerHeaderWidth += Event.current.delta.x;
  312. }
  313. Event.current.Use();
  314. }
  315. }
  316. }
  317. void OptionsGUI()
  318. {
  319. if (currentMode.headerState.options == TimelineModeGUIState.Hidden || state.editSequence.asset == null)
  320. return;
  321. using (new EditorGUI.DisabledScope(currentMode.headerState.options == TimelineModeGUIState.Disabled))
  322. {
  323. GUILayout.FlexibleSpace();
  324. if (EditorGUILayout.DropdownButton(DirectorStyles.optionsCogIcon, FocusType.Keyboard, EditorStyles.toolbarButton))
  325. {
  326. GenericMenu menu = new GenericMenu();
  327. menu.AddItem(EditorGUIUtility.TrTextContent("Seconds"), !state.timeInFrames, ChangeTimeCode, "seconds");
  328. menu.AddItem(EditorGUIUtility.TrTextContent("Frames"), state.timeInFrames, ChangeTimeCode, "frames");
  329. menu.AddSeparator("");
  330. TimeAreaContextMenu.AddTimeAreaMenuItems(menu, state);
  331. menu.AddSeparator("");
  332. bool standardFrameRate = false;
  333. standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/Film (24)", 24.0f);
  334. standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/PAL (25)", 25.0f);
  335. standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/NTSC (29.97)", 29.97f);
  336. standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/30", 30.0f);
  337. standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/50", 50.0f);
  338. standardFrameRate |= AddStandardFrameRateMenu(menu, "Frame Rate/60", 60.0f);
  339. if (standardFrameRate)
  340. menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Frame Rate/Custom"));
  341. else
  342. menu.AddItem(EditorGUIUtility.TrTextContent("Frame Rate/Custom (" + state.editSequence.frameRate + ")"), true, () => {});
  343. menu.AddSeparator("");
  344. if (state.playRangeEnabled)
  345. {
  346. menu.AddItem(EditorGUIUtility.TrTextContent("Play Range Mode/Loop"), state.playRangeLoopMode, () => state.playRangeLoopMode = true);
  347. menu.AddItem(EditorGUIUtility.TrTextContent("Play Range Mode/Once"), !state.playRangeLoopMode, () => state.playRangeLoopMode = false);
  348. }
  349. else
  350. {
  351. menu.AddDisabledItem(EditorGUIUtility.TrTextContent("Play Range Mode"));
  352. }
  353. menu.AddItem(EditorGUIUtility.TrTextContent("Playback Scrolling mode/None"), state.autoScrollMode == PlaybackScrollMode.None, () => state.autoScrollMode = PlaybackScrollMode.None);
  354. menu.AddItem(EditorGUIUtility.TrTextContent("Playback Scrolling mode/Smooth"), state.autoScrollMode == PlaybackScrollMode.Smooth, () => state.autoScrollMode = PlaybackScrollMode.Smooth);
  355. menu.AddItem(EditorGUIUtility.TrTextContent("Playback Scrolling mode/Pan"), state.autoScrollMode == PlaybackScrollMode.Pan, () => state.autoScrollMode = PlaybackScrollMode.Pan);
  356. menu.AddSeparator("");
  357. menu.AddItem(EditorGUIUtility.TrTextContent("Show Audio Waveforms"), state.showAudioWaveform, () =>
  358. {
  359. state.showAudioWaveform = !state.showAudioWaveform;
  360. });
  361. menu.AddItem(EditorGUIUtility.TrTextContent("Enable Audio Scrubbing"), !state.muteAudioScrubbing, () => state.muteAudioScrubbing = !state.muteAudioScrubbing);
  362. menu.AddSeparator("");
  363. menu.AddItem(EditorGUIUtility.TrTextContent("Snap to Frame"), state.frameSnap, () => state.frameSnap = !state.frameSnap);
  364. menu.AddItem(EditorGUIUtility.TrTextContent("Edge Snap"), state.edgeSnaps, () => state.edgeSnaps = !state.edgeSnaps);
  365. if (Unsupported.IsDeveloperMode())
  366. {
  367. menu.AddItem(EditorGUIUtility.TrTextContent("Show Snapping Debug"), SnapEngine.displayDebugLayout,
  368. () => SnapEngine.displayDebugLayout = !SnapEngine.displayDebugLayout);
  369. menu.AddItem(EditorGUIUtility.TrTextContent("Debug TimeArea"), false,
  370. () =>
  371. Debug.LogFormat("translation: {0} scale: {1} rect: {2} shownRange: {3}", m_TimeArea.translation, m_TimeArea.scale, m_TimeArea.rect, m_TimeArea.shownArea));
  372. menu.AddItem(EditorGUIUtility.TrTextContent("Edit Skin"), false, () => Selection.activeObject = DirectorStyles.Instance.customSkin);
  373. menu.AddItem(EditorGUIUtility.TrTextContent("Show QuadTree Debugger"), state.showQuadTree,
  374. () => state.showQuadTree = !state.showQuadTree);
  375. }
  376. menu.ShowAsContext();
  377. }
  378. }
  379. }
  380. bool AddStandardFrameRateMenu(GenericMenu menu, string name, float value)
  381. {
  382. bool on = state.editSequence.frameRate.Equals(value);
  383. if (state.editSequence.isReadOnly)
  384. {
  385. menu.AddDisabledItem(EditorGUIUtility.TextContent(name), on);
  386. }
  387. else
  388. {
  389. menu.AddItem(EditorGUIUtility.TextContent(name), on, r =>
  390. {
  391. state.editSequence.frameRate = value;
  392. }, value);
  393. }
  394. return on;
  395. }
  396. void ChangeTimeCode(object obj)
  397. {
  398. string format = obj.ToString();
  399. if (format == "frames")
  400. {
  401. state.timeInFrames = true;
  402. }
  403. else
  404. {
  405. state.timeInFrames = false;
  406. }
  407. }
  408. public void AddUserOverlay(IMarker marker, Rect rect, MarkerEditor editor, bool collapsed, bool selected)
  409. {
  410. if (marker == null)
  411. throw new ArgumentNullException("marker");
  412. if (editor == null)
  413. throw new ArgumentNullException("editor");
  414. m_OverlayQueue.Add(new MarkerOverlay()
  415. {
  416. isCollapsed = collapsed,
  417. isSelected = selected,
  418. marker = marker,
  419. rect = rect,
  420. editor = editor
  421. }
  422. );
  423. }
  424. }
  425. }