SkyTimelineWindow.cs 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using UnityEngine;
  5. using UnityEditor;
  6. namespace Funly.SkyStudio
  7. {
  8. // The timeline window is an animation editor for SkyProfiles.
  9. public class SkyTimelineWindow : EditorWindow
  10. {
  11. private const float TIME_HEADER_HEIGHT = 50.0f;
  12. private const float CONTENT_INSET = 20.0f;
  13. private const float PLAYHEAD_WIDTH = 32.0f;
  14. private const float CURSOR_LINE_WIDTH = 8.0f;
  15. private const float NAME_COLUMN_WIDTH = 225.0f;
  16. private const float COLOR_ROW_HEIGHT = 30.0f;
  17. private const float SPHERE_POINT_ROW_HEIGHT = 30.0f;
  18. private const float NUMBER_ROW_HEIGHT = 45.0f;
  19. private const float ROW_PADDING = 22.0f;
  20. private const float MIN_WINDOW_WIDTH = 580.0f;
  21. private const float MIN_WINDOW_HEIGHT = 250.0f;
  22. private const float VALUE_COLUMN_INSET = 1.0f;
  23. private const float HORIZONTAL_DIVIDER_HEIGHT = 2.0f;
  24. private const float VERTICAL_DIVIDER_WIDTH = 2.0f;
  25. private const float MAX_TIME_VALUE = .9999f;
  26. private const float EMPTY_MESSAGE_Y_OFFSET = 50.0f;
  27. private const float EMPTY_ADD_BUTTON_Y_OFFSET = 20.0f;
  28. private const float ICON_BUTTON_SIZE = 20.0f;
  29. private const float LEFT_INSET = 5.0f;
  30. private Vector2 m_ScrollPosition = Vector2.zero;
  31. private Texture2D m_PlayheadTexture;
  32. private List<ProfileGroupDefinition> m_TimelineDefinitions;
  33. private SkyProfile m_ActiveSkyProfile;
  34. private TimeOfDayController m_ActiveTimeController;
  35. private GUIStyle m_ButtonStyle;
  36. private GUIStyle m_GroupTitleStyle;
  37. private GUIStyle m_EmptyTitleStyle;
  38. private GUIStyle m_TimeLabelStyle;
  39. private const int MAX_DEBUG_POINTS = 100;
  40. private Vector4[] m_DebugPoints = new Vector4[MAX_DEBUG_POINTS];
  41. [MenuItem("Window/Sky Studio/Sky Timeline")]
  42. public static void ShowWindow()
  43. {
  44. TimelineSelection.Clear();
  45. SkyTimelineWindow window = EditorWindow.GetWindow<SkyTimelineWindow>();
  46. window.ConfigureWindow();
  47. window.minSize = new Vector2(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT);
  48. window.Show();
  49. }
  50. public void ConfigureWindow() {
  51. this.name = "Sky Timeline";
  52. this.titleContent = new GUIContent("Sky Timeline");
  53. this.wantsMouseMove = true;
  54. this.wantsMouseEnterLeaveWindow = true;
  55. }
  56. private void OnEnable()
  57. {
  58. ConfigureWindow();
  59. }
  60. private void OnDisable()
  61. {
  62. HideDebugPoints();
  63. }
  64. private void OnDestroy()
  65. {
  66. TimelineSelection.Clear();
  67. }
  68. private void OnInspectorUpdate()
  69. {
  70. Repaint();
  71. }
  72. private void OnGUI()
  73. {
  74. LoadStyles();
  75. TimeOfDayController timeController = FindObjectOfType<TimeOfDayController>() as TimeOfDayController;
  76. // Render a setup helper UI.
  77. if (timeController == null) {
  78. RenderNeedsSkySetupLayout();
  79. return;
  80. }
  81. // Render a profile help message UI.
  82. if (timeController.skyProfile == null) {
  83. RenderNeedsProfileLayout();
  84. return;
  85. }
  86. m_ActiveTimeController = timeController;
  87. m_ActiveSkyProfile = timeController ? timeController.skyProfile : null;
  88. RebuildTimelineDefinitions(timeController.skyProfile);
  89. float contentHeight = CalculateWindowContentHeight(timeController.skyProfile);
  90. float scrollbarInset = 0;
  91. // Select the first colorGroup if one isn't selected.
  92. if (TimelineSelection.selectedGroupUUID == null &&
  93. timeController.skyProfile.timelineManagedKeys.Count > 0) {
  94. IKeyframeGroup group = timeController.skyProfile.GetGroup(timeController.skyProfile.timelineManagedKeys[0]);
  95. if (group != null) {
  96. TimelineSelection.selectedGroupUUID = group.id;
  97. }
  98. }
  99. // Inset content on the right to make room for scroll bar.
  100. if (contentHeight > position.height) {
  101. scrollbarInset = CONTENT_INSET;
  102. }
  103. // Timeline rect.
  104. Rect contentRect = new Rect(
  105. 0,
  106. 0,
  107. position.width - scrollbarInset,
  108. position.height);
  109. // Check if mouse left the window, and cancel drag operations.
  110. if (Event.current.type == EventType.MouseLeaveWindow ||
  111. contentRect.Contains(Event.current.mousePosition) == false) {
  112. SkyEditorUtility.CancelTimelineDrags();
  113. }
  114. // Loads the list of timeline groups to render.
  115. RenderTimelineEditor(contentRect, timeController, contentHeight);
  116. // Save the edits to the profile object.
  117. if (timeController != null) {
  118. EditorUtility.SetDirty(timeController.skyProfile);
  119. // Keep the scene view rendering in sync for live editing.
  120. timeController.UpdateSkyForCurrentTime();
  121. }
  122. }
  123. private void LoadStyles() {
  124. m_ButtonStyle = new GUIStyle(GUI.skin.button);
  125. m_ButtonStyle.padding = new RectOffset(2, 2, 2, 2);
  126. m_GroupTitleStyle = new GUIStyle(GUI.skin.label);
  127. m_GroupTitleStyle.normal.textColor = Color.white;
  128. m_EmptyTitleStyle = new GUIStyle(GUI.skin.label);
  129. m_EmptyTitleStyle.normal.textColor = Color.white;
  130. m_TimeLabelStyle = new GUIStyle();
  131. m_TimeLabelStyle.fontStyle = FontStyle.Normal;
  132. m_TimeLabelStyle.fontSize = 24;
  133. m_TimeLabelStyle.normal.textColor = GUI.color;
  134. }
  135. private float CalculateWindowContentHeight(SkyProfile profile)
  136. {
  137. if (profile == null)
  138. {
  139. return 0;
  140. }
  141. int colorRowCount = 0;
  142. int numericRowCount = 0;
  143. int spherePointRowCount = 0;
  144. foreach (ProfileGroupDefinition groupInfo in m_TimelineDefinitions) {
  145. if (profile.IsManagedByTimeline(groupInfo.propertyKey) == false) {
  146. continue;
  147. }
  148. if (groupInfo.type == ProfileGroupDefinition.GroupType.Number) {
  149. numericRowCount += 1;
  150. } else if (groupInfo.type == ProfileGroupDefinition.GroupType.Color) {
  151. colorRowCount += 1;
  152. } else if (groupInfo.type == ProfileGroupDefinition.GroupType.SpherePoint)
  153. {
  154. spherePointRowCount += 1;
  155. }
  156. }
  157. float colorsHeight = colorRowCount * (COLOR_ROW_HEIGHT + ROW_PADDING);
  158. float numbersHeight = numericRowCount * (NUMBER_ROW_HEIGHT + ROW_PADDING);
  159. float spherePointHeight = spherePointRowCount * (SPHERE_POINT_ROW_HEIGHT + ROW_PADDING);
  160. float contentHeight = colorsHeight + numbersHeight + spherePointHeight + TIME_HEADER_HEIGHT;
  161. return contentHeight;
  162. }
  163. private void RenderTimelineEditor(Rect rect, TimeOfDayController timeController, float contentHeight)
  164. {
  165. float nameColMinX = rect.x;
  166. float valueColMinX = nameColMinX + NAME_COLUMN_WIDTH;
  167. float valueColMaxX = rect.xMax;
  168. float valueColWidth = rect.width - NAME_COLUMN_WIDTH;
  169. // Check for the end of a timeline drag.
  170. if (TimelineSelection.isDraggingTimeline && Event.current.type == EventType.MouseUp) {
  171. TimelineSelection.isDraggingTimeline = false;
  172. }
  173. // If we're busy dragging the timeline, consume the events so child views don't see them.
  174. if (Event.current.type == EventType.MouseDrag && TimelineSelection.isDraggingTimeline)
  175. {
  176. Event.current.Use();
  177. }
  178. // Check if user dragged in the time ruler so we don't click things behind it.
  179. if (DidDragTimeRuler()) {
  180. SkyEditorUtility.CancelTimelineDrags();
  181. TimelineSelection.isDraggingTimeline = true;
  182. Event.current.Use();
  183. }
  184. float fullContentHeight = Mathf.Max(position.height, contentHeight);
  185. // Background style.
  186. RenderBackground();
  187. // Render timeline buttons at header.
  188. Rect toolbarRect = new Rect(nameColMinX,
  189. rect.y, NAME_COLUMN_WIDTH, TIME_HEADER_HEIGHT);
  190. RenderHeaderButtons(toolbarRect, timeController);
  191. // Render Scrubber.
  192. Rect timeScrubberRect = new Rect(valueColMinX, rect.y,
  193. valueColWidth - VALUE_COLUMN_INSET, TIME_HEADER_HEIGHT);
  194. RenderTimeRuler(timeScrubberRect, timeController.timeOfDay);
  195. // Show an empty content help message.
  196. if (timeController.skyProfile.timelineManagedKeys.Count == 0) {
  197. RenderEmptyTimelineMessage();
  198. }
  199. Rect innerScrollViewContent = new Rect(
  200. 0,
  201. 0,
  202. rect.width,
  203. contentHeight - TIME_HEADER_HEIGHT);
  204. Rect scrollWindowPosition = new Rect(0, TIME_HEADER_HEIGHT, position.width, rect.height - TIME_HEADER_HEIGHT);
  205. m_ScrollPosition = GUI.BeginScrollView(scrollWindowPosition, m_ScrollPosition, innerScrollViewContent, false, false);
  206. // Render all the content rows.
  207. Rect rowsRect = new Rect(rect.x, 0, rect.width, 0);
  208. RenderAllRows(rowsRect, timeController.skyProfile);
  209. RenderSpherePointGroupDebugPointsIfSelected();
  210. GUI.EndScrollView();
  211. // Draw the cursor, which overlaps the other content.
  212. Rect cursorRect = new Rect(
  213. valueColMinX, rect.y, valueColWidth - VALUE_COLUMN_INSET, fullContentHeight);
  214. RenderTimelineCursor(cursorRect, timeController);
  215. }
  216. // If sphere point group is selected, we render dots in the skybox to help user position things.
  217. private void RenderSpherePointGroupDebugPointsIfSelected()
  218. {
  219. if (m_ActiveSkyProfile == null || TimelineSelection.selectedGroupUUID == null)
  220. {
  221. return;
  222. }
  223. IKeyframeGroup group = m_ActiveSkyProfile.GetGroupWithId(TimelineSelection.selectedGroupUUID);
  224. if (group is SpherePointKeyframeGroup)
  225. {
  226. ShowSpherePointKeyframesInSkybox(group as SpherePointKeyframeGroup);
  227. }
  228. else
  229. {
  230. HideDebugPoints();
  231. }
  232. }
  233. private void RenderNeedsSkySetupLayout() {
  234. RenderNonInteractiveBaseLayout();
  235. if (RenderCenteredTimelineMessage("Your scene needs an active sky system.", "Create Sky System...")) {
  236. EditorWindow.GetWindow<SkySetupWindow>().Show();
  237. }
  238. }
  239. private void RenderNeedsProfileLayout() {
  240. RenderNonInteractiveBaseLayout();
  241. RenderCenteredTimelineMessage("The time controller in the scene has no profile assigned.", null);
  242. }
  243. private void RenderNonInteractiveBaseLayout() {
  244. // Background style.
  245. RenderBackground();
  246. // Time ruler.
  247. Rect timeScrubberRect = new Rect(
  248. NAME_COLUMN_WIDTH,
  249. 0,
  250. position.size.x - NAME_COLUMN_WIDTH,
  251. TIME_HEADER_HEIGHT);
  252. RenderTimeRuler(timeScrubberRect, 0.0f);
  253. // Divider.
  254. Rect dividerRect = new Rect(
  255. 0,
  256. TIME_HEADER_HEIGHT - HORIZONTAL_DIVIDER_HEIGHT,
  257. position.width,
  258. HORIZONTAL_DIVIDER_HEIGHT);
  259. RenderHorizontalDivider(dividerRect);
  260. RenderTimeLabel(0.0f);
  261. }
  262. private bool DidDragTimeRuler() {
  263. if (Event.current.isMouse == false || Event.current.type != EventType.MouseDrag) {
  264. return false;
  265. }
  266. Rect rulerRect = new Rect(
  267. NAME_COLUMN_WIDTH,
  268. 0,
  269. position.width - NAME_COLUMN_WIDTH,
  270. TIME_HEADER_HEIGHT);
  271. return rulerRect.Contains(Event.current.mousePosition);
  272. }
  273. private void RenderBackground()
  274. {
  275. Rect fullWindowRect = new Rect(
  276. 0,
  277. 0,
  278. position.width,
  279. position.height);
  280. float windowBg = .20f;
  281. EditorGUI.DrawRect(fullWindowRect, new Color(windowBg, windowBg, windowBg, 1));
  282. Rect nameColRect = new Rect(
  283. 0,
  284. 0,
  285. NAME_COLUMN_WIDTH,
  286. position.height);
  287. float nameBg = .38f;
  288. EditorGUI.DrawRect(nameColRect, new Color(nameBg, nameBg, nameBg, 1));
  289. Rect vertDivider = new Rect(
  290. NAME_COLUMN_WIDTH - VERTICAL_DIVIDER_WIDTH,
  291. TIME_HEADER_HEIGHT,
  292. VERTICAL_DIVIDER_WIDTH,
  293. fullWindowRect.height);
  294. RenderVerticalDivider(vertDivider);
  295. }
  296. private void RenderHeaderButtons(Rect rect, TimeOfDayController tc)
  297. {
  298. RenderTimeLabel(tc.timeOfDay);
  299. Rect buttonsRect = new Rect(
  300. rect.x, rect.y + ICON_BUTTON_SIZE,
  301. rect.width, rect.height - ICON_BUTTON_SIZE);
  302. RenderGlobalTimelineButtons(buttonsRect, tc);
  303. // Divider.
  304. Rect dividerRect = new Rect(
  305. rect.x,
  306. rect.y + TIME_HEADER_HEIGHT - HORIZONTAL_DIVIDER_HEIGHT,
  307. rect.width,
  308. HORIZONTAL_DIVIDER_HEIGHT);
  309. RenderHorizontalDivider(dividerRect);
  310. }
  311. private void RenderTimeLabel(float time) {
  312. GUIContent labelContent = new GUIContent(TimeStringFromPercent(time));
  313. Vector2 labelSize = m_TimeLabelStyle.CalcSize(labelContent);
  314. Rect timeRect = new Rect(
  315. 5.0f,
  316. TIME_HEADER_HEIGHT - labelSize.y - 5.0f,
  317. labelSize.x,
  318. labelSize.y);
  319. EditorGUI.LabelField(timeRect, labelContent, m_TimeLabelStyle);
  320. }
  321. private static string TimeStringFromPercent(float percent)
  322. {
  323. float hoursFract = percent * 24.0f;
  324. int hours = (int) hoursFract;
  325. int minutes = (int)((hoursFract - hours) * 60.0f);
  326. string hourStr = hours < 10 ? "0" + hours : hours.ToString();
  327. string minuteStr = minutes < 10 ? "0" + minutes : minutes.ToString();
  328. return hourStr + ":" + minuteStr;
  329. }
  330. // These are the buttons next to the current time of day at the top.
  331. private void RenderGlobalTimelineButtons(Rect rect, TimeOfDayController tc)
  332. {
  333. GUILayout.BeginArea(rect);
  334. GUILayout.BeginHorizontal();
  335. GUILayout.FlexibleSpace();
  336. float buttonSize = 20.0f;
  337. Color originalContentColor = GUI.contentColor;
  338. GUI.contentColor = GUI.skin.label.normal.textColor;
  339. GUIStyle buttonStyle = new GUIStyle(GUI.skin.button);
  340. buttonStyle.padding = new RectOffset(2, 2, 2, 2);
  341. Texture2D addTexture = SkyEditorUtility.LoadEditorResourceTexture("AddIcon");
  342. bool didClickAddButton = GUILayout.Button(
  343. new GUIContent(addTexture, "Add a sky property to the timeline."),
  344. buttonStyle, GUILayout.Width(buttonSize), GUILayout.Height(buttonSize));
  345. GUILayout.Space(LEFT_INSET);
  346. if (didClickAddButton)
  347. {
  348. SkyGUITimelineMenu.ShowAddTimelinePropertyMenu(m_ActiveSkyProfile);
  349. }
  350. GUI.contentColor = originalContentColor;
  351. GUILayout.EndHorizontal();
  352. GUILayout.EndArea();
  353. }
  354. private void DidClickAddNewKeyframe(TimeOfDayController tc)
  355. {
  356. IKeyframeGroup selectedGroup = tc.skyProfile.GetGroupWithId(TimelineSelection.selectedGroupUUID);
  357. if (selectedGroup == null)
  358. {
  359. Debug.LogError("Can't insert keyframe since no group was fould for selected UUID.");
  360. return;
  361. }
  362. Undo.RecordObject(tc.skyProfile, "Keyframe inserted into group.");
  363. if (selectedGroup is ColorKeyframeGroup)
  364. {
  365. InsertKeyframeInColorGroup(tc.timeOfDay, selectedGroup as ColorKeyframeGroup);
  366. }
  367. else if (selectedGroup is NumberKeyframeGroup)
  368. {
  369. InsertKeyframeInNumericGroup(tc.timeOfDay, selectedGroup as NumberKeyframeGroup);
  370. }
  371. else if (selectedGroup is SpherePointKeyframeGroup)
  372. {
  373. InsertKeyframeInSpherePointGroup(tc.timeOfDay, selectedGroup as SpherePointKeyframeGroup);
  374. }
  375. EditorUtility.SetDirty(tc.skyProfile);
  376. Repaint();
  377. }
  378. private void InsertKeyframeInColorGroup(float time, ColorKeyframeGroup group)
  379. {
  380. ColorKeyframe previousKeyFrame = group.GetPreviousKeyFrame(time);
  381. //Color keyColor = previousKeyFrame != null ? previousKeyFrame.color : Color.white;
  382. ColorKeyframe newKeyFrame = new ColorKeyframe(previousKeyFrame);
  383. newKeyFrame.time = time;
  384. group.AddKeyFrame(newKeyFrame);
  385. KeyframeInspectorWindow.SetKeyframeData(
  386. newKeyFrame, group, KeyframeInspectorWindow.KeyType.Color, m_ActiveSkyProfile);
  387. }
  388. private void InsertKeyframeInNumericGroup(float time, NumberKeyframeGroup group)
  389. {
  390. NumberKeyframe previousKeyframe = group.GetPreviousKeyFrame(time);
  391. NumberKeyframe newKeyFrame = new NumberKeyframe(previousKeyframe);
  392. newKeyFrame.time = time;
  393. group.AddKeyFrame(newKeyFrame);
  394. KeyframeInspectorWindow.SetKeyframeData(
  395. newKeyFrame, group, KeyframeInspectorWindow.KeyType.Numeric, m_ActiveSkyProfile);
  396. }
  397. private void InsertKeyframeInSpherePointGroup(float time, SpherePointKeyframeGroup group)
  398. {
  399. SpherePointKeyframe previousKeyFrame = group.GetPreviousKeyFrame(time);
  400. SpherePointKeyframe newKeyFrame = new SpherePointKeyframe(previousKeyFrame);
  401. newKeyFrame.time = time;
  402. group.AddKeyFrame(newKeyFrame);
  403. KeyframeInspectorWindow.SetKeyframeData(
  404. newKeyFrame, group, KeyframeInspectorWindow.KeyType.SpherePoint, m_ActiveSkyProfile);
  405. }
  406. private void RenderTimeRuler(Rect rect, float currentTime)
  407. {
  408. // 1 notch for every hour of the day.
  409. const int lineIncrements = 24;
  410. const float notchWidth = 1.0f;
  411. const float notchLabelYOffset = 10.0f;
  412. const int largeNotchPerHour = 3;
  413. float smallNotchHeight = 10.0f;
  414. float largeNotchHeight = 20.0f;
  415. float notchSectionWidth = rect.width / lineIncrements;
  416. Rect backgroundRect = new Rect(NAME_COLUMN_WIDTH, rect.y, position.width - NAME_COLUMN_WIDTH, TIME_HEADER_HEIGHT);
  417. EditorGUI.DrawRect(backgroundRect, Color.black);
  418. GUIStyle labelStyle = new GUIStyle(GUI.skin.label);
  419. labelStyle.normal.textColor = GUI.color;
  420. // Draw the notches on the timeline.
  421. for (int i = 0; i < lineIncrements + 1; i++)
  422. {
  423. float notchHeight = smallNotchHeight;
  424. // Draw large notch every 6 hours.
  425. if (i % largeNotchPerHour == 0)
  426. {
  427. notchHeight = largeNotchHeight;
  428. float notchLabelXOffset = 0;
  429. string hourString = i.ToString();
  430. if (i == 0)
  431. {
  432. notchLabelXOffset = 0;
  433. } else if (i == lineIncrements)
  434. {
  435. notchLabelXOffset = -15.0f;
  436. }
  437. else
  438. {
  439. if (hourString.Length == 1)
  440. {
  441. notchLabelXOffset = -5.0f;
  442. }
  443. else
  444. {
  445. notchLabelXOffset = -8.0f;
  446. }
  447. }
  448. Rect timeLabelRect = new Rect(
  449. rect.x + i * notchSectionWidth + notchLabelXOffset,
  450. rect.y + notchLabelYOffset,
  451. 20.0f,
  452. 30.0f);
  453. EditorGUI.LabelField(timeLabelRect, new GUIContent(i.ToString()), labelStyle);
  454. }
  455. Rect finalNotchRect = new Rect(
  456. rect.x + i * notchSectionWidth - (notchWidth / 2.0f),
  457. rect.y + TIME_HEADER_HEIGHT - notchHeight - HORIZONTAL_DIVIDER_HEIGHT,
  458. notchWidth,
  459. notchHeight);
  460. EditorGUI.DrawRect(finalNotchRect, Color.white);
  461. }
  462. // Divider.
  463. Rect dividerRect = new Rect(
  464. rect.x,
  465. rect.y + TIME_HEADER_HEIGHT - HORIZONTAL_DIVIDER_HEIGHT,
  466. rect.width,
  467. HORIZONTAL_DIVIDER_HEIGHT);
  468. RenderHorizontalDivider(dividerRect);
  469. }
  470. private void RenderTimelineCursor(Rect rect, TimeOfDayController timeController)
  471. {
  472. // Flag the start of a timeline drag.
  473. if (TimelineSelection.isDraggingTimeline == false &&
  474. (Event.current.type == EventType.MouseDrag) &&
  475. rect.Contains(Event.current.mousePosition))
  476. {
  477. TimelineSelection.isDraggingTimeline = true;
  478. }
  479. if (TimelineSelection.isDraggingTimeline)
  480. {
  481. float percent = Mathf.Clamp((Event.current.mousePosition.x - rect.x) / rect.width, 0, MAX_TIME_VALUE);
  482. timeController.skyTime = percent;
  483. EditorUtility.SetDirty(timeController);
  484. }
  485. if (Event.current.type != EventType.Repaint)
  486. {
  487. return;
  488. }
  489. float playHeadHeight = PLAYHEAD_WIDTH / 2.0f;
  490. float xCursorPos = SkyEditorUtility.GetXPositionForPercent(rect, timeController.timeOfDay);
  491. // Draw the line that overlaps all the content rows.
  492. const float extensionSize = 5.0f;
  493. Rect lineRect = new Rect(
  494. xCursorPos - (CURSOR_LINE_WIDTH / 2.0f),
  495. rect.y + TIME_HEADER_HEIGHT - extensionSize,
  496. CURSOR_LINE_WIDTH,
  497. rect.height - TIME_HEADER_HEIGHT + extensionSize);
  498. GUI.DrawTexture(lineRect, SkyEditorUtility.LoadEditorResourceTexture("CursorLine"));
  499. // Draw the playhead arrow.
  500. if (m_PlayheadTexture == null)
  501. {
  502. m_PlayheadTexture = SkyEditorUtility.LoadEditorResourceTexture("PlayheadArrow");
  503. }
  504. Rect headRect = new Rect(
  505. xCursorPos - (PLAYHEAD_WIDTH / 2.0f),
  506. rect.y + TIME_HEADER_HEIGHT - playHeadHeight,
  507. PLAYHEAD_WIDTH,
  508. playHeadHeight);
  509. GUI.DrawTexture(headRect, m_PlayheadTexture, ScaleMode.StretchToFill, true);
  510. }
  511. private bool RenderCenteredTimelineMessage(string emptyMessage, string buttonText) {
  512. Rect rect = new Rect(0, TIME_HEADER_HEIGHT, position.width, position.height);
  513. GUIContent emptyMessageContent = new GUIContent(emptyMessage);
  514. Vector2 labelWidth = m_EmptyTitleStyle.CalcSize(emptyMessageContent);
  515. float valueColumnWidth = rect.width - NAME_COLUMN_WIDTH;
  516. // Label message.
  517. Rect msgRect = new Rect(NAME_COLUMN_WIDTH + (valueColumnWidth - labelWidth.x) / 2.0f,
  518. rect.y + EMPTY_MESSAGE_Y_OFFSET, labelWidth.x, labelWidth.y);
  519. EditorGUI.LabelField(msgRect, new GUIContent(emptyMessage), m_EmptyTitleStyle);
  520. // Button to add first item to timeline.
  521. if (buttonText != null) {
  522. GUIContent addButtonContent = new GUIContent(buttonText);
  523. Vector2 addButtonSize = GUI.skin.button.CalcSize(addButtonContent);
  524. Rect addButtonRect = new Rect(NAME_COLUMN_WIDTH + (valueColumnWidth - addButtonSize.x) / 2.0f,
  525. msgRect.y + labelWidth.y + EMPTY_ADD_BUTTON_Y_OFFSET, addButtonSize.x, addButtonSize.y);
  526. return GUI.Button(addButtonRect, addButtonContent);
  527. }
  528. return false;
  529. }
  530. private void RenderEmptyTimelineMessage() {
  531. bool didClickAddProperty = RenderCenteredTimelineMessage(
  532. "Animate sky properties by adding them to the timeline.",
  533. "Add to Timeline");
  534. if (didClickAddProperty)
  535. {
  536. SkyGUITimelineMenu.ShowAddTimelinePropertyMenu(m_ActiveSkyProfile);
  537. }
  538. return;
  539. }
  540. private void RenderAllRows(Rect rect, SkyProfile profile)
  541. {
  542. Rect rowRect = new Rect(rect.x, rect.y + ROW_PADDING / 2.0f, rect.width, COLOR_ROW_HEIGHT);
  543. // Render all rows that are managed by the timeline.
  544. foreach (ProfileGroupDefinition groupInfo in m_TimelineDefinitions) {
  545. if (profile.IsManagedByTimeline(groupInfo.propertyKey) == false) {
  546. continue;
  547. }
  548. if (groupInfo.type == ProfileGroupDefinition.GroupType.Number) {
  549. RenderNumericRowAndAdvance(
  550. ref rowRect,
  551. profile,
  552. profile.GetGroup<NumberKeyframeGroup>(groupInfo.propertyKey),
  553. groupInfo);
  554. } else if (groupInfo.type == ProfileGroupDefinition.GroupType.Color) {
  555. RenderGradientRowAndAdvance(
  556. ref rowRect,
  557. profile,
  558. profile.GetGroup<ColorKeyframeGroup>(groupInfo.propertyKey),
  559. groupInfo);
  560. } else if (groupInfo.type == ProfileGroupDefinition.GroupType.SpherePoint)
  561. {
  562. RenderSpherePointRowAndAdvance(
  563. ref rowRect,
  564. profile,
  565. profile.GetGroup<SpherePointKeyframeGroup>(groupInfo.propertyKey),
  566. groupInfo);
  567. }
  568. }
  569. }
  570. private void RebuildTimelineDefinitions(SkyProfile profile)
  571. {
  572. if (m_TimelineDefinitions == null)
  573. {
  574. m_TimelineDefinitions = new List<ProfileGroupDefinition>();
  575. }
  576. m_TimelineDefinitions.Clear();
  577. foreach (string groupKey in profile.timelineManagedKeys)
  578. {
  579. ProfileGroupDefinition groupDefinition = profile.GetGroupDefinitionForKey(groupKey);
  580. if (groupDefinition == null)
  581. {
  582. //Debug.LogError("Failed to get group definition for key: " + groupKey);
  583. continue;
  584. }
  585. m_TimelineDefinitions.Add(groupDefinition);
  586. }
  587. }
  588. // Load the rects to use for this row colorGroup.
  589. private void LoadRowInformation(ref Rect rect, string groupUUID, float rowHeight, out Rect valueRowRect, out Rect nameRowRect, out bool isActive)
  590. {
  591. valueRowRect = new Rect(rect.x + NAME_COLUMN_WIDTH, rect.y, rect.width - NAME_COLUMN_WIDTH - VALUE_COLUMN_INSET, rowHeight);
  592. nameRowRect = new Rect(rect.x, rect.y, NAME_COLUMN_WIDTH, rowHeight);
  593. isActive = TimelineSelection.selectedGroupUUID != null && TimelineSelection.selectedGroupUUID == groupUUID;
  594. rect.y += rowHeight + ROW_PADDING;
  595. }
  596. // Render a timeline of gradient keyframes.
  597. private void RenderGradientRowAndAdvance(ref Rect rect, SkyProfile profile, ColorKeyframeGroup group, ProfileGroupDefinition groupDefinition)
  598. {
  599. rect.height = COLOR_ROW_HEIGHT;
  600. UpdateActiveSelectedRow(rect, group.id, groupDefinition.propertyKey);
  601. Rect valueRowRect;
  602. Rect nameRowRect;
  603. bool isActive;
  604. LoadRowInformation(ref rect, group.id, COLOR_ROW_HEIGHT, out valueRowRect, out nameRowRect, out isActive);
  605. RenderRowTitle(nameRowRect, group.name, isActive, groupDefinition);
  606. ColorTimelineRow.RenderColorGroupRow(valueRowRect, profile, group);
  607. }
  608. // Render a timeline of sphere point keyframes.
  609. private void RenderSpherePointRowAndAdvance(ref Rect rect, SkyProfile profile, SpherePointKeyframeGroup group, ProfileGroupDefinition groupDefinition)
  610. {
  611. rect.height = SPHERE_POINT_ROW_HEIGHT;
  612. UpdateActiveSelectedRow(rect, group.id, groupDefinition.propertyKey);
  613. Rect valueRowRect;
  614. Rect nameRowRect;
  615. bool isActive;
  616. LoadRowInformation(ref rect, group.id, SPHERE_POINT_ROW_HEIGHT, out valueRowRect, out nameRowRect, out isActive);
  617. // Render debug points if this is active.
  618. if (isActive)
  619. {
  620. ShowSpherePointKeyframesInSkybox(group);
  621. }
  622. RenderRowTitle(nameRowRect, group.name, isActive, groupDefinition);
  623. SpherePointTimelineRow.RenderSpherePointRow(valueRowRect, profile, group);
  624. }
  625. // Render a timeline of numeric keyframe positions.
  626. private void RenderNumericRowAndAdvance(ref Rect rect, SkyProfile profile, NumberKeyframeGroup group, ProfileGroupDefinition groupDefinition)
  627. {
  628. rect.height = NUMBER_ROW_HEIGHT;
  629. UpdateActiveSelectedRow(rect, group.id, groupDefinition.propertyKey);
  630. Rect valueRowRect;
  631. Rect nameRowRect;
  632. bool isActive;
  633. LoadRowInformation(ref rect, group.id, NUMBER_ROW_HEIGHT, out valueRowRect, out nameRowRect, out isActive);
  634. RenderRowTitle(nameRowRect, group.name, isActive, groupDefinition);
  635. NumberTimelineRow.RenderNumberGroup(valueRowRect, profile, group);
  636. }
  637. private void UpdateActiveSelectedRow(Rect rect, string groupUUID, string propertyKey)
  638. {
  639. Rect clickableRowRect = new Rect(rect.x, rect.y, rect.width + VALUE_COLUMN_INSET, rect.height + ROW_PADDING);
  640. if (clickableRowRect.Contains(Event.current.mousePosition) &&
  641. Event.current.type == EventType.MouseDown)
  642. {
  643. TimelineSelection.selectedGroupUUID = groupUUID;
  644. UpdateExternalWindowsWithActiveGroupSelection(propertyKey);
  645. }
  646. }
  647. private bool RenderGroupButton(Rect rowRect, int buttonIndex, string iconName, string tooltip)
  648. {
  649. Texture2D texIcon = SkyEditorUtility.LoadEditorResourceTexture(iconName);
  650. if (texIcon == null)
  651. {
  652. Debug.LogError("Failed to load icon for group button.");
  653. return false;
  654. }
  655. Color originalColor = GUI.color;
  656. GUI.contentColor = GUI.skin.label.normal.textColor;
  657. const float buttonPadding = 2.0f;
  658. const float buttonEndPadding = 5.0f;
  659. Rect btnRect = new Rect(
  660. NAME_COLUMN_WIDTH - buttonEndPadding - ((buttonIndex + 1) * ICON_BUTTON_SIZE) - ((buttonIndex) * buttonPadding),
  661. rowRect.y + (rowRect.height - ICON_BUTTON_SIZE) / 2.0f,
  662. ICON_BUTTON_SIZE,
  663. ICON_BUTTON_SIZE
  664. );
  665. bool didClickButton = GUI.Button(
  666. btnRect,
  667. new GUIContent(null, texIcon, tooltip),
  668. m_ButtonStyle);
  669. GUI.contentColor = originalColor;
  670. return didClickButton;
  671. }
  672. private void RenderRowTitle(Rect rect, string rowTitle, bool isActive, ProfileGroupDefinition groupDefinition)
  673. {
  674. GUILayout.Space(LEFT_INSET);
  675. m_GroupTitleStyle.fontStyle = isActive ? FontStyle.Bold : FontStyle.Normal;
  676. const float labelHeight = 18;
  677. Rect labelRect = new Rect(
  678. 5, rect.y + ((rect.height - labelHeight) / 2.0f), NAME_COLUMN_WIDTH, labelHeight);
  679. EditorGUI.LabelField(labelRect, new GUIContent(rowTitle, groupDefinition.tooltip), m_GroupTitleStyle);
  680. // Render buttons over the active row.
  681. if (isActive) {
  682. if (RenderGroupButton(rect, 1, "AddIcon", "Add a keyframe at the current cursor position.")) {
  683. DidClickAddNewKeyframe(m_ActiveTimeController);
  684. }
  685. if (RenderGroupButton(rect, 0, "HelpIcon", "Show help information about this group property.")) {
  686. GroupHelpWindow.SetHelpItem(m_ActiveSkyProfile, groupDefinition.propertyKey, true);
  687. }
  688. }
  689. // Draw a divider between rows.
  690. float dividerYPosition = rect.y + rect.height + (ROW_PADDING / 2.0f) - HORIZONTAL_DIVIDER_HEIGHT;
  691. RenderHorizontalDivider(new Rect(rect.x, dividerYPosition, rect.width, HORIZONTAL_DIVIDER_HEIGHT));
  692. }
  693. private void RenderHorizontalDivider(Rect dividerRect, string imageName = "HorizontalDividerOverlay")
  694. {
  695. Texture2D dividerTexture = SkyEditorUtility.LoadEditorResourceTexture(imageName);
  696. GUI.DrawTexture(dividerRect, dividerTexture);
  697. }
  698. private void RenderVerticalDivider(Rect dividerRect, string imageName = "VerticalDividerOverlay")
  699. {
  700. Texture2D dividerTexture = SkyEditorUtility.LoadEditorResourceTexture(imageName);
  701. GUI.DrawTexture(dividerRect, dividerTexture);
  702. }
  703. private void UpdateExternalWindowsWithActiveGroupSelection(string propertyKey)
  704. {
  705. GroupHelpWindow.SetHelpItem(m_ActiveSkyProfile, propertyKey, false);
  706. }
  707. private void ShowSpherePointKeyframesInSkybox(SpherePointKeyframeGroup group)
  708. {
  709. int debugPoints = 0;
  710. for (int i = 0; i < group.keyframes.Count; i++)
  711. {
  712. SpherePointKeyframe keyframe = group.keyframes[i];
  713. Vector3 direction = keyframe.spherePoint.GetWorldDirection();
  714. float isActiveKeyframe = SkyEditorUtility.IsKeyframeActiveInInspector(keyframe) ? 1.0f : 0.0f;
  715. Vector4 pointData = new Vector4(direction.x, direction.y, direction.z, isActiveKeyframe);
  716. if (i < MAX_DEBUG_POINTS) {
  717. m_DebugPoints[i] = pointData;
  718. debugPoints += 1;
  719. }
  720. }
  721. ShowDebugPoints(m_DebugPoints, debugPoints);
  722. }
  723. private void ShowDebugPoints(Vector4[] points, int count)
  724. {
  725. if (m_ActiveSkyProfile == null || m_ActiveSkyProfile.skyboxMaterial == null || points == null)
  726. {
  727. return;
  728. }
  729. m_ActiveSkyProfile.skyboxMaterial.EnableKeyword(ShaderKeywords.RenderDebugPoints);
  730. m_ActiveSkyProfile.skyboxMaterial.SetInt("_DebugPointsCount", count);
  731. m_ActiveSkyProfile.skyboxMaterial.SetVectorArray("_DebugPoints", points);
  732. }
  733. private void HideDebugPoints()
  734. {
  735. if (m_ActiveSkyProfile == null || m_ActiveSkyProfile.skyboxMaterial == null)
  736. {
  737. return;
  738. }
  739. m_ActiveSkyProfile.skyboxMaterial.SetInt("_DebugPointsCount", 0);
  740. m_ActiveSkyProfile.skyboxMaterial.DisableKeyword(ShaderKeywords.RenderDebugPoints);
  741. }
  742. }
  743. }