GUICurveEditor.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.Collections.Generic;
  5. using BansheeEngine;
  6. namespace BansheeEditor
  7. {
  8. /** @addtogroup Windows
  9. * @{
  10. */
  11. internal class GUICurveEditor
  12. {
  13. public struct KeyframeRef
  14. {
  15. public KeyframeRef(int curveIdx, int keyIdx)
  16. {
  17. this.curveIdx = curveIdx;
  18. this.keyIdx = keyIdx;
  19. }
  20. public int curveIdx;
  21. public int keyIdx;
  22. }
  23. private const int TIMELINE_HEIGHT = 20;
  24. private const int SIDEBAR_WIDTH = 30;
  25. private EditorWindow window;
  26. private GUILayout gui;
  27. private GUIPanel drawingPanel;
  28. private GUIGraphTime guiTimeline;
  29. private GUICurveDrawing guiCurveDrawing;
  30. private GUIGraphValues guiSidebar;
  31. private ContextMenu blankContextMenu;
  32. private ContextMenu keyframeContextMenu;
  33. private Vector2I contextClickPosition;
  34. private EdAnimationCurve[] curves = new EdAnimationCurve[0];
  35. private int markedFrameIdx;
  36. private List<KeyframeRef> selectedKeyframes = new List<KeyframeRef>();
  37. private bool isPointerHeld;
  38. private bool isMousePressedOverKey;
  39. private KeyFrame[] draggedKeyframes;
  40. private Vector2 dragStart;
  41. public GUICurveEditor(EditorWindow window, GUILayout gui, int width, int height)
  42. {
  43. this.window = window;
  44. this.gui = gui;
  45. blankContextMenu = new ContextMenu();
  46. blankContextMenu.AddItem("Add keyframe", AddKeyframeAtPosition);
  47. blankContextMenu.AddItem("Add event", AddEventAtPosition);
  48. keyframeContextMenu = new ContextMenu();
  49. keyframeContextMenu.AddItem("Delete", DeleteSelectedKeyframes);
  50. keyframeContextMenu.AddItem("Tangents/Auto", () => { ChangeSelectionTangentMode(TangentMode.Auto); });
  51. keyframeContextMenu.AddItem("Tangents/Free", () => { ChangeSelectionTangentMode(TangentMode.Free); });
  52. keyframeContextMenu.AddItem("Tangents/In/Auto", () => { ChangeSelectionTangentMode(TangentMode.InAuto); });
  53. keyframeContextMenu.AddItem("Tangents/In/Free", () => { ChangeSelectionTangentMode(TangentMode.InFree); });
  54. keyframeContextMenu.AddItem("Tangents/In/Linear", () => { ChangeSelectionTangentMode(TangentMode.InLinear); });
  55. keyframeContextMenu.AddItem("Tangents/In/Step", () => { ChangeSelectionTangentMode(TangentMode.InStep); });
  56. keyframeContextMenu.AddItem("Tangents/Out/Auto", () => { ChangeSelectionTangentMode(TangentMode.OutAuto); });
  57. keyframeContextMenu.AddItem("Tangents/Out/Free", () => { ChangeSelectionTangentMode(TangentMode.OutFree); });
  58. keyframeContextMenu.AddItem("Tangents/Out/Linear", () => { ChangeSelectionTangentMode(TangentMode.OutLinear); });
  59. keyframeContextMenu.AddItem("Tangents/Out/Step", () => { ChangeSelectionTangentMode(TangentMode.OutStep); });
  60. guiTimeline = new GUIGraphTime(gui, width, TIMELINE_HEIGHT);
  61. drawingPanel = gui.AddPanel();
  62. drawingPanel.SetPosition(0, TIMELINE_HEIGHT);
  63. guiCurveDrawing = new GUICurveDrawing(drawingPanel, width, height - TIMELINE_HEIGHT, curves);
  64. guiCurveDrawing.SetRange(60.0f, 20.0f);
  65. GUIPanel sidebarPanel = gui.AddPanel(-10);
  66. sidebarPanel.SetPosition(0, TIMELINE_HEIGHT);
  67. guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT);
  68. guiSidebar.SetRange(-10.0f, 10.0f);
  69. EditorInput.OnPointerPressed += OnPointerPressed;
  70. EditorInput.OnPointerMoved += OnPointerMoved;
  71. EditorInput.OnPointerReleased += OnPointerReleased;
  72. EditorInput.OnButtonUp += OnButtonUp;
  73. }
  74. private void OnPointerPressed(PointerEvent ev)
  75. {
  76. if (ev.IsUsed)
  77. return;
  78. Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
  79. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  80. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  81. Rect2I drawingBounds = drawingPanel.Bounds;
  82. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  83. if (ev.Button == PointerButton.Left)
  84. {
  85. Vector2 curveCoord;
  86. int curveIdx;
  87. int keyIdx;
  88. if (guiCurveDrawing.GetCoordInfo(drawingPos, out curveCoord, out curveIdx, out keyIdx))
  89. {
  90. if (keyIdx == -1)
  91. ClearSelection();
  92. else
  93. {
  94. if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
  95. ClearSelection();
  96. SelectKeyframe(curveIdx, keyIdx);
  97. isMousePressedOverKey = true;
  98. dragStart = curveCoord;
  99. }
  100. guiCurveDrawing.Rebuild();
  101. }
  102. else
  103. {
  104. int frameIdx = guiTimeline.GetFrame(pointerPos);
  105. if (frameIdx != -1)
  106. SetMarkedFrame(frameIdx);
  107. }
  108. isPointerHeld = true;
  109. }
  110. else if (ev.Button == PointerButton.Right)
  111. {
  112. Vector2 curveCoord;
  113. int curveIdx;
  114. int keyIdx;
  115. if (guiCurveDrawing.GetCoordInfo(drawingPos, out curveCoord, out curveIdx, out keyIdx))
  116. {
  117. contextClickPosition = pointerPos;
  118. if (keyIdx == -1)
  119. {
  120. ClearSelection();
  121. blankContextMenu.Open(contextClickPosition, gui);
  122. }
  123. else
  124. {
  125. // If clicked outside of current selection, just select the one keyframe
  126. if (!IsSelected(curveIdx, keyIdx))
  127. {
  128. ClearSelection();
  129. SelectKeyframe(curveIdx, keyIdx);
  130. guiCurveDrawing.Rebuild();
  131. }
  132. keyframeContextMenu.Open(contextClickPosition, gui);
  133. }
  134. }
  135. }
  136. }
  137. private void OnPointerMoved(PointerEvent ev)
  138. {
  139. if (ev.Button != PointerButton.Left)
  140. return;
  141. if (isPointerHeld)
  142. {
  143. if (isMousePressedOverKey)
  144. {
  145. // TODO - Check if pointer moves some minimal amount
  146. // - If so, start drag. Record all current positions
  147. // - Calculate offset in curve space and apply to all keyframes
  148. }
  149. else
  150. {
  151. Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
  152. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  153. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  154. int frameIdx = guiTimeline.GetFrame(pointerPos);
  155. if (frameIdx != -1)
  156. SetMarkedFrame(frameIdx);
  157. }
  158. }
  159. }
  160. private void OnPointerReleased(PointerEvent ev)
  161. {
  162. isPointerHeld = false;
  163. isMousePressedOverKey = false;
  164. }
  165. private void OnButtonUp(ButtonEvent ev)
  166. {
  167. if(ev.Button == ButtonCode.Delete)
  168. DeleteSelectedKeyframes();
  169. }
  170. /// <summary>
  171. /// Change the set of curves to display.
  172. /// </summary>
  173. /// <param name="curves">New set of curves to draw on the GUI element.</param>
  174. public void SetCurves(EdAnimationCurve[] curves)
  175. {
  176. this.curves = curves;
  177. guiCurveDrawing.SetCurves(curves);
  178. // TODO - Recalculate valid size
  179. Redraw();
  180. }
  181. /// <summary>
  182. /// Change the physical size of the GUI element.
  183. /// </summary>
  184. /// <param name="width">Width of the element in pixels.</param>
  185. /// <param name="height">Height of the element in pixels.</param>
  186. public void SetSize(int width, int height)
  187. {
  188. guiTimeline.SetSize(width, TIMELINE_HEIGHT);
  189. guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT);
  190. guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT);
  191. Redraw();
  192. }
  193. /// <summary>
  194. /// Changes the visible range that the GUI element displays.
  195. /// </summary>
  196. /// <param name="xRange">Range of the horizontal area. Displayed area will range from [0, xRange].</param>
  197. /// <param name="yRange">Range of the vertical area. Displayed area will range from
  198. /// [-yRange * 0.5, yRange * 0.5]</param>
  199. public void SetRange(float xRange, float yRange)
  200. {
  201. guiTimeline.SetRange(xRange);
  202. guiCurveDrawing.SetRange(xRange, yRange);
  203. guiSidebar.SetRange(yRange * -0.5f, yRange * 0.5f);
  204. Redraw();
  205. }
  206. /// <summary>
  207. /// Number of frames per second, used for frame selection and marking.
  208. /// </summary>
  209. /// <param name="fps">Number of prames per second.</param>
  210. public void SetFPS(int fps)
  211. {
  212. guiTimeline.SetFPS(fps);
  213. guiCurveDrawing.SetFPS(fps);
  214. Redraw();
  215. }
  216. /// <summary>
  217. /// Sets the frame at which to display the frame marker.
  218. /// </summary>
  219. /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
  220. public void SetMarkedFrame(int frameIdx)
  221. {
  222. markedFrameIdx = frameIdx;
  223. guiTimeline.SetMarkedFrame(frameIdx);
  224. guiCurveDrawing.SetMarkedFrame(frameIdx);
  225. Redraw();
  226. }
  227. public void AddKeyFrameAtMarker()
  228. {
  229. ClearSelection();
  230. foreach (var curve in curves)
  231. {
  232. float t = guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
  233. float value = curve.Native.Evaluate(t);
  234. curve.AddKeyframe(t, value);
  235. }
  236. // TODO - UNDOREDO
  237. guiCurveDrawing.Rebuild();
  238. }
  239. public void Redraw()
  240. {
  241. guiCurveDrawing.Rebuild();
  242. guiTimeline.Rebuild();
  243. guiSidebar.Rebuild();
  244. }
  245. private void ChangeSelectionTangentMode(TangentMode mode)
  246. {
  247. foreach (var keyframe in selectedKeyframes)
  248. {
  249. EdAnimationCurve curve = curves[keyframe.curveIdx];
  250. if (mode == TangentMode.Auto || mode == TangentMode.Free)
  251. curve.SetTangentMode(keyframe.keyIdx, mode);
  252. else
  253. {
  254. TangentMode newMode = curve.TangentModes[keyframe.keyIdx];
  255. if (mode.HasFlag(TangentTypes.In))
  256. {
  257. // Replace only the in tangent mode, keeping the out tangent as is
  258. TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
  259. TangentMode.InAuto);
  260. newMode &= ~inFlags;
  261. newMode |= (mode & inFlags);
  262. }
  263. else
  264. {
  265. // Replace only the out tangent mode, keeping the in tangent as is
  266. TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
  267. TangentMode.OutAuto);
  268. newMode &= ~outFlags;
  269. newMode |= (mode & outFlags);
  270. }
  271. curve.SetTangentMode(keyframe.keyIdx, newMode);
  272. }
  273. }
  274. }
  275. private void AddKeyframeAtPosition()
  276. {
  277. Vector2 curveCoord;
  278. int curveIdx;
  279. int keyIdx;
  280. if (guiCurveDrawing.GetCoordInfo(contextClickPosition, out curveCoord, out curveIdx, out keyIdx))
  281. {
  282. ClearSelection();
  283. foreach (var curve in curves)
  284. {
  285. float t = curveCoord.x;
  286. float value = curveCoord.y;
  287. curve.AddKeyframe(t, value);
  288. }
  289. // TODO - UNDOREDO
  290. guiCurveDrawing.Rebuild();
  291. }
  292. }
  293. private void AddEventAtPosition()
  294. {
  295. // TODO
  296. }
  297. private void DeleteSelectedKeyframes()
  298. {
  299. // Sort keys from highest to lowest so they can be removed without changing the indices of the keys
  300. // after them
  301. selectedKeyframes.Sort((x, y) =>
  302. {
  303. if (x.curveIdx.Equals(y.curveIdx))
  304. return y.keyIdx.CompareTo(x.keyIdx);
  305. return x.curveIdx.CompareTo(y.curveIdx);
  306. });
  307. foreach (var keyframe in selectedKeyframes)
  308. curves[keyframe.curveIdx].RemoveKeyframe(keyframe.keyIdx);
  309. // TODO - UNDOREDO
  310. ClearSelection();
  311. guiCurveDrawing.Rebuild();
  312. }
  313. private void ClearSelection()
  314. {
  315. guiCurveDrawing.ClearSelectedKeyframes();
  316. selectedKeyframes.Clear();
  317. isMousePressedOverKey = false;
  318. }
  319. private void SelectKeyframe(int curveIdx, int keyIdx)
  320. {
  321. guiCurveDrawing.SelectKeyframe(curveIdx, keyIdx, true);
  322. if (!IsSelected(curveIdx, keyIdx))
  323. selectedKeyframes.Add(new GUICurveEditor.KeyframeRef(curveIdx, keyIdx));
  324. }
  325. private bool IsSelected(int curveIdx, int keyIdx)
  326. {
  327. int existingIdx = selectedKeyframes.FindIndex(x =>
  328. {
  329. return x.curveIdx == curveIdx && x.keyIdx == keyIdx;
  330. });
  331. return (existingIdx != -1);
  332. }
  333. }
  334. /** @} */
  335. }