ClipCurveEditor.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEditor.Timeline;
  5. using UnityEngine;
  6. using UnityEngine.Timeline;
  7. namespace UnityEditor
  8. {
  9. class ClipCurveEditor
  10. {
  11. static readonly GUIContent s_RemoveCurveContent = new GUIContent(L10n.Tr("Remove Curve"));
  12. static readonly GUIContent s_RemoveCurvesContent = new GUIContent(L10n.Tr("Remove Curves"));
  13. internal readonly CurveEditor m_CurveEditor;
  14. static readonly CurveEditorSettings s_CurveEditorSettings = new CurveEditorSettings
  15. {
  16. hSlider = false,
  17. vSlider = false,
  18. hRangeLocked = false,
  19. vRangeLocked = false,
  20. scaleWithWindow = true,
  21. hRangeMin = 0.0f,
  22. showAxisLabels = true,
  23. allowDeleteLastKeyInCurve = true,
  24. rectangleToolFlags = CurveEditorSettings.RectangleToolFlags.MiniRectangleTool
  25. };
  26. static readonly float s_GridLabelWidth = 40.0f;
  27. readonly BindingSelector m_BindingHierarchy;
  28. public BindingSelector bindingHierarchy
  29. {
  30. get { return m_BindingHierarchy; }
  31. }
  32. public Rect shownAreaInsideMargins
  33. {
  34. get { return m_CurveEditor != null ? m_CurveEditor.shownAreaInsideMargins : new Rect(1, 1, 1, 1); }
  35. }
  36. Vector2 m_ScrollPosition = Vector2.zero;
  37. readonly CurveDataSource m_DataSource;
  38. float m_LastFrameRate = 30.0f;
  39. UInt64 m_LastClipVersion = UInt64.MaxValue;
  40. TrackViewModelData m_ViewModel;
  41. bool m_ShouldRestoreShownArea;
  42. bool isNewSelection
  43. {
  44. get
  45. {
  46. if (m_ViewModel == null || m_DataSource == null)
  47. return true;
  48. return m_ViewModel.lastInlineCurveDataID != m_DataSource.id;
  49. }
  50. }
  51. internal CurveEditor curveEditor
  52. {
  53. get { return m_CurveEditor; }
  54. }
  55. public ClipCurveEditor(CurveDataSource dataSource, TimelineWindow parentWindow, TrackAsset hostTrack)
  56. {
  57. m_DataSource = dataSource;
  58. m_CurveEditor = new CurveEditor(new Rect(0, 0, 1000, 100), new CurveWrapper[0], false);
  59. s_CurveEditorSettings.vTickStyle = new TickStyle
  60. {
  61. tickColor = { color = DirectorStyles.Instance.customSkin.colorInlineCurveVerticalLines },
  62. distLabel = 20,
  63. stubs = true
  64. };
  65. s_CurveEditorSettings.hTickStyle = new TickStyle
  66. {
  67. // hide horizontal lines by giving them a transparent color
  68. tickColor = { color = new Color(0.0f, 0.0f, 0.0f, 0.0f) },
  69. distLabel = 0
  70. };
  71. m_CurveEditor.settings = s_CurveEditorSettings;
  72. m_ViewModel = TimelineWindowViewPrefs.GetTrackViewModelData(hostTrack);
  73. m_ShouldRestoreShownArea = true;
  74. m_CurveEditor.ignoreScrollWheelUntilClicked = true;
  75. m_CurveEditor.curvesUpdated = OnCurvesUpdated;
  76. m_BindingHierarchy = new BindingSelector(parentWindow, m_CurveEditor, m_ViewModel.inlineCurvesState);
  77. }
  78. public void SelectAllKeys()
  79. {
  80. m_CurveEditor.SelectAll();
  81. }
  82. public void FrameClip()
  83. {
  84. m_CurveEditor.InvalidateBounds();
  85. m_CurveEditor.FrameClip(false, true);
  86. }
  87. public CurveDataSource dataSource
  88. {
  89. get { return m_DataSource; }
  90. }
  91. // called when curves are edited
  92. internal void OnCurvesUpdated()
  93. {
  94. if (m_DataSource == null)
  95. return;
  96. if (m_CurveEditor == null)
  97. return;
  98. if (m_CurveEditor.animationCurves.Length == 0)
  99. return;
  100. List<CurveWrapper> curvesToUpdate = m_CurveEditor.animationCurves.Where(c => c.changed).ToList();
  101. // nothing changed, return.
  102. if (curvesToUpdate.Count == 0)
  103. return;
  104. // something changed, manage the undo properly.
  105. m_DataSource.ApplyCurveChanges(curvesToUpdate);
  106. m_LastClipVersion = m_DataSource.GetClipVersion();
  107. }
  108. public void DrawHeader(Rect headerRect)
  109. {
  110. m_BindingHierarchy.InitIfNeeded(headerRect, m_DataSource, isNewSelection);
  111. try
  112. {
  113. GUILayout.BeginArea(headerRect);
  114. m_ScrollPosition = GUILayout.BeginScrollView(m_ScrollPosition, GUIStyle.none, GUI.skin.verticalScrollbar);
  115. m_BindingHierarchy.OnGUI(new Rect(0, 0, headerRect.width, headerRect.height));
  116. if (m_BindingHierarchy.treeViewController != null)
  117. m_BindingHierarchy.treeViewController.contextClickItemCallback = ContextClickItemCallback;
  118. GUILayout.EndScrollView();
  119. GUILayout.EndArea();
  120. }
  121. catch (Exception e)
  122. {
  123. Debug.LogException(e);
  124. }
  125. }
  126. void ContextClickItemCallback(int obj)
  127. {
  128. GenerateContextMenu(obj);
  129. }
  130. void GenerateContextMenu(int obj = -1)
  131. {
  132. if (Event.current.type != EventType.ContextClick)
  133. return;
  134. var selectedCurves = GetSelectedProperties().ToArray();
  135. if (selectedCurves.Length > 0)
  136. {
  137. var menu = new GenericMenu();
  138. var content = selectedCurves.Length == 1 ? s_RemoveCurveContent : s_RemoveCurvesContent;
  139. menu.AddItem(content,
  140. false,
  141. () => RemoveCurves(selectedCurves)
  142. );
  143. menu.ShowAsContext();
  144. }
  145. }
  146. public IEnumerable<EditorCurveBinding> GetSelectedProperties(bool useForcedGroups = false)
  147. {
  148. var bindings = new HashSet<EditorCurveBinding>();
  149. var bindingTree = m_BindingHierarchy.treeViewController.data as BindingTreeViewDataSource;
  150. foreach (var selectedId in m_BindingHierarchy.treeViewController.GetSelection())
  151. {
  152. var node = bindingTree.FindItem(selectedId) as CurveTreeViewNode;
  153. if (node == null)
  154. continue;
  155. var curveNodeParent = node.parent as CurveTreeViewNode;
  156. if (useForcedGroups && node.forceGroup && curveNodeParent != null)
  157. bindings.UnionWith(curveNodeParent.bindings);
  158. else
  159. bindings.UnionWith(node.bindings);
  160. }
  161. return bindings;
  162. }
  163. public void RemoveCurves(IEnumerable<EditorCurveBinding> bindings)
  164. {
  165. m_DataSource.RemoveCurves(bindings);
  166. m_BindingHierarchy.RefreshTree();
  167. TimelineWindow.instance.state.CalculateRowRects();
  168. m_LastClipVersion = m_DataSource.GetClipVersion();
  169. }
  170. class FrameFormatCurveEditorState : ICurveEditorState
  171. {
  172. public TimeArea.TimeFormat timeFormat
  173. {
  174. get { return TimeArea.TimeFormat.Frame; }
  175. }
  176. public Vector2 timeRange { get { return new Vector2(0, 1); } }
  177. public bool rippleTime { get { return false; } }
  178. }
  179. class UnformattedCurveEditorState : ICurveEditorState
  180. {
  181. public TimeArea.TimeFormat timeFormat
  182. {
  183. get { return TimeArea.TimeFormat.None; }
  184. }
  185. public Vector2 timeRange { get { return new Vector2(0, 1); } }
  186. public bool rippleTime { get { return false; } }
  187. }
  188. void UpdateCurveEditorIfNeeded(WindowState state)
  189. {
  190. if ((Event.current.type != EventType.Layout) || (m_DataSource == null) || (m_BindingHierarchy == null))
  191. return;
  192. // check if the curves have changed externally
  193. var curveChange = m_DataSource.UpdateExternalChanges(ref m_LastClipVersion);
  194. if (curveChange == CurveChangeType.None)
  195. return;
  196. if (curveChange == CurveChangeType.CurveAddedOrRemoved)
  197. m_BindingHierarchy.RefreshTree();
  198. else // curve modified
  199. m_BindingHierarchy.RefreshCurves();
  200. m_CurveEditor.InvalidateSelectionBounds();
  201. if (state.timeInFrames)
  202. m_CurveEditor.state = new FrameFormatCurveEditorState();
  203. else
  204. m_CurveEditor.state = new UnformattedCurveEditorState();
  205. m_CurveEditor.invSnap = state.referenceSequence.frameRate;
  206. }
  207. public void DrawCurveEditor(Rect rect, WindowState state, Vector2 clipRange, bool loop, bool selected)
  208. {
  209. SetupMarginsAndRect(rect, state);
  210. UpdateCurveEditorIfNeeded(state);
  211. if (m_ShouldRestoreShownArea)
  212. RestoreShownArea();
  213. var curveVisibleTimeRange = CalculateCurveVisibleTimeRange(state.timeAreaShownRange, m_DataSource);
  214. m_CurveEditor.SetShownHRangeInsideMargins(curveVisibleTimeRange.x, curveVisibleTimeRange.y); //align the curve with the clip.
  215. if (m_LastFrameRate != state.referenceSequence.frameRate)
  216. {
  217. m_CurveEditor.hTicks.SetTickModulosForFrameRate(state.referenceSequence.frameRate);
  218. m_LastFrameRate = state.referenceSequence.frameRate;
  219. }
  220. foreach (var cw in m_CurveEditor.animationCurves)
  221. cw.renderer.SetWrap(WrapMode.Default, loop ? WrapMode.Loop : WrapMode.Default);
  222. using (new GUIGroupScope(rect))
  223. {
  224. var localRect = new Rect(0.0f, 0.0f, rect.width, rect.height);
  225. var localClipRange = new Vector2(Mathf.Floor(clipRange.x - rect.xMin), Mathf.Ceil(clipRange.y - rect.xMin));
  226. var curveStartPosX = Mathf.Floor(state.TimeToPixel(m_DataSource.start) - rect.xMin);
  227. EditorGUI.DrawRect(new Rect(curveStartPosX, 0.0f, 1.0f, rect.height), new Color(1.0f, 1.0f, 1.0f, 0.5f));
  228. DrawCurveEditorBackground(localRect);
  229. if (selected)
  230. {
  231. var selectionRect = new Rect(localClipRange.x, 0.0f, localClipRange.y - localClipRange.x, localRect.height);
  232. DrawOutline(selectionRect);
  233. }
  234. EditorGUI.BeginChangeCheck();
  235. {
  236. var evt = Event.current;
  237. if (evt.type == EventType.Layout || evt.type == EventType.Repaint || selected)
  238. m_CurveEditor.CurveGUI();
  239. }
  240. if (EditorGUI.EndChangeCheck())
  241. OnCurvesUpdated();
  242. DrawOverlay(localRect, localClipRange, DirectorStyles.Instance.customSkin.colorInlineCurveOutOfRangeOverlay);
  243. DrawGrid(localRect, curveStartPosX);
  244. }
  245. }
  246. static Vector2 CalculateCurveVisibleTimeRange(Vector2 timeAreaShownRange, CurveDataSource curve)
  247. {
  248. var curveVisibleTimeRange = new Vector2
  249. {
  250. x = Math.Max(0.0f, timeAreaShownRange.x - curve.start),
  251. y = timeAreaShownRange.y - curve.start
  252. };
  253. return curveVisibleTimeRange * curve.timeScale;
  254. }
  255. void SetupMarginsAndRect(Rect rect, WindowState state)
  256. {
  257. var startX = state.TimeToPixel(m_DataSource.start) - rect.x;
  258. var timelineWidth = state.timeAreaRect.width;
  259. m_CurveEditor.rect = new Rect(0.0f, 0.0f, timelineWidth, rect.height);
  260. m_CurveEditor.leftmargin = Math.Max(startX, 0.0f);
  261. m_CurveEditor.rightmargin = 0.0f;
  262. m_CurveEditor.topmargin = m_CurveEditor.bottommargin = CalculateTopMargin(rect.height);
  263. }
  264. void RestoreShownArea()
  265. {
  266. if (isNewSelection)
  267. FrameClip();
  268. else
  269. m_CurveEditor.shownAreaInsideMargins = m_ViewModel.inlineCurvesShownAreaInsideMargins;
  270. m_ShouldRestoreShownArea = false;
  271. }
  272. static void DrawCurveEditorBackground(Rect rect)
  273. {
  274. if (EditorGUIUtility.isProSkin)
  275. return;
  276. var animEditorBackgroundRect = Rect.MinMaxRect(0.0f, rect.yMin, rect.xMax, rect.yMax);
  277. // Curves are not legible in Personal Skin so we need to darken the background a bit.
  278. EditorGUI.DrawRect(animEditorBackgroundRect, DirectorStyles.Instance.customSkin.colorInlineCurvesBackground);
  279. }
  280. static float CalculateTopMargin(float height)
  281. {
  282. return Mathf.Clamp(0.15f * height, 10.0f, 40.0f);
  283. }
  284. static void DrawOutline(Rect rect, float thickness = 2.0f)
  285. {
  286. // Draw top selected lines.
  287. EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMin, rect.width, thickness), Color.white);
  288. // Draw bottom selected lines.
  289. EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMax - thickness, rect.width, thickness), Color.white);
  290. // Draw Left Selected Lines
  291. EditorGUI.DrawRect(new Rect(rect.xMin, rect.yMin, thickness, rect.height), Color.white);
  292. // Draw Right Selected Lines
  293. EditorGUI.DrawRect(new Rect(rect.xMax - thickness, rect.yMin, thickness, rect.height), Color.white);
  294. }
  295. static void DrawOverlay(Rect rect, Vector2 clipRange, Color color)
  296. {
  297. var leftSide = new Rect(rect.xMin, rect.yMin, clipRange.x - rect.xMin, rect.height);
  298. EditorGUI.DrawRect(leftSide, color);
  299. var rightSide = new Rect(Mathf.Max(0.0f, clipRange.y), rect.yMin, rect.xMax, rect.height);
  300. EditorGUI.DrawRect(rightSide, color);
  301. }
  302. void DrawGrid(Rect rect, float curveXPosition)
  303. {
  304. var gridXPos = Mathf.Max(curveXPosition - s_GridLabelWidth, rect.xMin);
  305. var gridRect = new Rect(gridXPos, rect.y, s_GridLabelWidth, rect.height);
  306. var originalRect = m_CurveEditor.rect;
  307. m_CurveEditor.rect = new Rect(0.0f, 0.0f, rect.width, rect.height);
  308. using (new GUIGroupScope(gridRect))
  309. m_CurveEditor.GridGUI();
  310. m_CurveEditor.rect = originalRect;
  311. }
  312. }
  313. }