GUICurveEditor.cs 39 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043
  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 AnimationEditor
  9. * @{
  10. */
  11. /// <summary>
  12. /// Displays a set of animation curves and events. Allows manipulation of both by adding, removing and modifying
  13. /// curve keyframes, and animation events.
  14. /// </summary>
  15. internal class GUICurveEditor
  16. {
  17. /// <summary>
  18. /// Information about currently selected set of keyframes for a specific curve.
  19. /// </summary>
  20. class SelectedKeyframes
  21. {
  22. public int curveIdx;
  23. public List<int> keyIndices = new List<int>();
  24. }
  25. /// <summary>
  26. /// Information about a keyframe that is currently being dragged.
  27. /// </summary>
  28. struct DraggedKeyframe
  29. {
  30. public DraggedKeyframe(int index, KeyFrame original)
  31. {
  32. this.index = index;
  33. this.original = original;
  34. }
  35. public int index;
  36. public KeyFrame original;
  37. }
  38. /// <summary>
  39. /// Information about all keyframes of a specific curve that are currently being dragged.
  40. /// </summary>
  41. class DraggedKeyframes
  42. {
  43. public int curveIdx;
  44. public List<DraggedKeyframe> keys = new List<DraggedKeyframe>();
  45. }
  46. /// <summary>
  47. /// Data about an animation event.
  48. /// </summary>
  49. class EventInfo
  50. {
  51. public AnimationEvent animEvent;
  52. public bool selected;
  53. }
  54. private const int TIMELINE_HEIGHT = 20;
  55. private const int EVENTS_HEIGHT = 10;
  56. private const int SIDEBAR_WIDTH = 30;
  57. private const int DRAG_START_DISTANCE = 3;
  58. private EditorWindow window;
  59. private GUILayout gui;
  60. private GUIPanel drawingPanel;
  61. private GUIPanel eventsPanel;
  62. private GUIGraphTime guiTimeline;
  63. private GUIAnimEvents guiEvents;
  64. private GUICurveDrawing guiCurveDrawing;
  65. private GUIGraphValues guiSidebar;
  66. private ContextMenu blankContextMenu;
  67. private ContextMenu keyframeContextMenu;
  68. private ContextMenu blankEventContextMenu;
  69. private ContextMenu eventContextMenu;
  70. private Vector2I contextClickPosition;
  71. private EdAnimationCurve[] curves = new EdAnimationCurve[0];
  72. private float xRange = 60.0f;
  73. private float yRange = 10.0f;
  74. private Vector2 offset;
  75. private int width;
  76. private int height;
  77. private int markedFrameIdx;
  78. private List<SelectedKeyframes> selectedKeyframes = new List<SelectedKeyframes>();
  79. private List<EventInfo> events = new List<EventInfo>();
  80. private bool isPointerHeld;
  81. private bool isMousePressedOverKey;
  82. private bool isMousePressedOverTangent;
  83. private bool isDragInProgress;
  84. private List<DraggedKeyframes> draggedKeyframes = new List<DraggedKeyframes>();
  85. private TangentRef draggedTangent;
  86. private Vector2I dragStart;
  87. /// <summary>
  88. /// Triggers whenever user selects a new frame. Reports the index of the selected frame.
  89. /// </summary>
  90. public Action<int> OnFrameSelected;
  91. /// <summary>
  92. /// The displayed range of the curve, where:
  93. /// .x - Range of the horizontal area. Displayed area ranges from [0, x].
  94. /// .y - Range of the vertical area. Displayed area ranges from [-y, y].
  95. /// </summary>
  96. public Vector2 Range
  97. {
  98. get { return new Vector2(xRange, yRange); }
  99. set
  100. {
  101. xRange = value.x;
  102. yRange = value.y;
  103. guiTimeline.SetRange(xRange);
  104. guiEvents.SetRange(xRange);
  105. guiCurveDrawing.SetRange(xRange, yRange * 2.0f);
  106. guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
  107. Redraw();
  108. }
  109. }
  110. /// <summary>
  111. /// Determines how much to offset the displayed curve values.
  112. /// </summary>
  113. public Vector2 Offset
  114. {
  115. get { return offset; }
  116. set
  117. {
  118. offset = value;
  119. guiTimeline.SetOffset(offset.x);
  120. guiEvents.SetOffset(offset.x);
  121. guiCurveDrawing.SetOffset(offset);
  122. guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
  123. Redraw();
  124. }
  125. }
  126. /// <summary>
  127. /// Returns the width of the curve editor, in pixels.
  128. /// </summary>
  129. public int Width
  130. {
  131. get { return width; }
  132. }
  133. /// <summary>
  134. /// Returns the height of the curve editor, in pixels.
  135. /// </summary>
  136. public int Height
  137. {
  138. get { return height; }
  139. }
  140. /// <summary>
  141. /// Animation events displayed on the curve editor.
  142. /// </summary>
  143. public AnimationEvent[] Events
  144. {
  145. get
  146. {
  147. AnimationEvent[] animEvents = new AnimationEvent[events.Count];
  148. for (int i = 0; i < events.Count; i++)
  149. animEvents[i] = events[i].animEvent;
  150. return animEvents;
  151. }
  152. set
  153. {
  154. events.Clear();
  155. for (int i = 0; i < value.Length; i++)
  156. {
  157. EventInfo eventInfo = new EventInfo();
  158. eventInfo.animEvent = value[i];
  159. events.Add(eventInfo);
  160. }
  161. UpdateEventsGUI();
  162. }
  163. }
  164. /// <summary>
  165. /// Creates a new curve editor GUI elements.
  166. /// </summary>
  167. /// <param name="window">Parent window of the GUI element.</param>
  168. /// <param name="gui">GUI layout into which to place the GUI element.</param>
  169. /// <param name="width">Width in pixels.</param>
  170. /// <param name="height">Height in pixels.</param>
  171. public GUICurveEditor(EditorWindow window, GUILayout gui, int width, int height)
  172. {
  173. this.window = window;
  174. this.gui = gui;
  175. this.width = width;
  176. this.height = height;
  177. blankContextMenu = new ContextMenu();
  178. blankContextMenu.AddItem("Add keyframe", AddKeyframeAtPosition);
  179. blankEventContextMenu = new ContextMenu();
  180. blankEventContextMenu.AddItem("Add event", AddEventAtPosition);
  181. keyframeContextMenu = new ContextMenu();
  182. keyframeContextMenu.AddItem("Delete", DeleteSelectedKeyframes);
  183. keyframeContextMenu.AddItem("Tangents/Auto", () => { ChangeSelectionTangentMode(TangentMode.Auto); });
  184. keyframeContextMenu.AddItem("Tangents/Free", () => { ChangeSelectionTangentMode(TangentMode.Free); });
  185. keyframeContextMenu.AddItem("Tangents/In/Auto", () => { ChangeSelectionTangentMode(TangentMode.InAuto); });
  186. keyframeContextMenu.AddItem("Tangents/In/Free", () => { ChangeSelectionTangentMode(TangentMode.InFree); });
  187. keyframeContextMenu.AddItem("Tangents/In/Linear", () => { ChangeSelectionTangentMode(TangentMode.InLinear); });
  188. keyframeContextMenu.AddItem("Tangents/In/Step", () => { ChangeSelectionTangentMode(TangentMode.InStep); });
  189. keyframeContextMenu.AddItem("Tangents/Out/Auto", () => { ChangeSelectionTangentMode(TangentMode.OutAuto); });
  190. keyframeContextMenu.AddItem("Tangents/Out/Free", () => { ChangeSelectionTangentMode(TangentMode.OutFree); });
  191. keyframeContextMenu.AddItem("Tangents/Out/Linear", () => { ChangeSelectionTangentMode(TangentMode.OutLinear); });
  192. keyframeContextMenu.AddItem("Tangents/Out/Step", () => { ChangeSelectionTangentMode(TangentMode.OutStep); });
  193. eventContextMenu = new ContextMenu();
  194. eventContextMenu.AddItem("Delete", DeleteSelectedEvents);
  195. eventContextMenu.AddItem("Edit", EditSelectedEvent);
  196. guiTimeline = new GUIGraphTime(gui, width, TIMELINE_HEIGHT);
  197. eventsPanel = gui.AddPanel();
  198. eventsPanel.SetPosition(0, TIMELINE_HEIGHT);
  199. guiEvents = new GUIAnimEvents(eventsPanel, width, EVENTS_HEIGHT);
  200. drawingPanel = gui.AddPanel();
  201. drawingPanel.SetPosition(0, TIMELINE_HEIGHT + EVENTS_HEIGHT);
  202. guiCurveDrawing = new GUICurveDrawing(drawingPanel, width, height - TIMELINE_HEIGHT - EVENTS_HEIGHT, curves);
  203. guiCurveDrawing.SetRange(60.0f, 20.0f);
  204. GUIPanel sidebarPanel = gui.AddPanel(-10);
  205. sidebarPanel.SetPosition(0, TIMELINE_HEIGHT + EVENTS_HEIGHT);
  206. guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
  207. guiSidebar.SetRange(-10.0f, 10.0f);
  208. }
  209. /// <summary>
  210. /// Converts pixel coordinates relative to the curve drawing area into coordinates in curve space.
  211. /// </summary>
  212. /// <param name="pixelCoords">Coordinates relative to this GUI element, in pixels.</param>
  213. /// <param name="curveCoords">Curve coordinates within the range as specified by <see cref="Range"/>. Only
  214. /// valid when function returns true.</param>
  215. /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
  216. public bool PixelToCurveSpace(Vector2I pixelCoords, out Vector2 curveCoords)
  217. {
  218. return guiCurveDrawing.PixelToCurveSpace(pixelCoords, out curveCoords);
  219. }
  220. /// <summary>
  221. /// Converts coordinate in curve space (time, value) into pixel coordinates relative to the curve drawing area
  222. /// origin.
  223. /// </summary>
  224. /// <param name="curveCoords">Time and value of the location to convert.</param>
  225. /// <returns>Coordinates relative to curve drawing area's origin, in pixels.</returns>
  226. public Vector2I CurveToPixelSpace(Vector2 curveCoords)
  227. {
  228. return guiCurveDrawing.CurveToPixelSpace(curveCoords);
  229. }
  230. /// <summary>
  231. /// Converts coordinates in window space (relative to the parent window origin) into coordinates in curve space.
  232. /// </summary>
  233. /// <param name="windowPos">Coordinates relative to parent editor window, in pixels.</param>
  234. /// <param name="curveCoord">Curve coordinates within the range as specified by <see cref="Range"/>. Only
  235. /// valid when function returns true.</param>
  236. /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
  237. public bool WindowToCurveSpace(Vector2I windowPos, out Vector2 curveCoord)
  238. {
  239. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  240. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  241. Rect2I drawingBounds = drawingPanel.Bounds;
  242. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  243. return guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord);
  244. }
  245. /// <summary>
  246. /// Handles input. Should be called by the owning window whenever a pointer is pressed.
  247. /// </summary>
  248. /// <param name="ev">Object containing pointer press event information.</param>
  249. internal void OnPointerPressed(PointerEvent ev)
  250. {
  251. if (ev.IsUsed)
  252. return;
  253. Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
  254. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  255. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  256. Rect2I drawingBounds = drawingPanel.Bounds;
  257. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  258. Rect2I eventBounds = eventsPanel.Bounds;
  259. Vector2I eventPos = pointerPos - new Vector2I(eventBounds.x, eventBounds.y);
  260. if (ev.Button == PointerButton.Left)
  261. {
  262. Vector2 curveCoord;
  263. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord))
  264. {
  265. KeyframeRef keyframeRef;
  266. if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
  267. {
  268. TangentRef tangentRef;
  269. if (guiCurveDrawing.FindTangent(drawingPos, out tangentRef))
  270. {
  271. isMousePressedOverTangent = true;
  272. dragStart = drawingPos;
  273. draggedTangent = tangentRef;
  274. }
  275. else
  276. ClearSelection();
  277. }
  278. else
  279. {
  280. if (!IsSelected(keyframeRef))
  281. {
  282. if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
  283. ClearSelection();
  284. SelectKeyframe(keyframeRef);
  285. }
  286. isMousePressedOverKey = true;
  287. dragStart = drawingPos;
  288. }
  289. guiCurveDrawing.Rebuild();
  290. UpdateEventsGUI();
  291. }
  292. else
  293. {
  294. int frameIdx = guiTimeline.GetFrame(pointerPos);
  295. if (frameIdx != -1)
  296. SetMarkedFrame(frameIdx);
  297. else
  298. {
  299. int eventIdx;
  300. if (guiEvents.FindEvent(eventPos, out eventIdx))
  301. {
  302. if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
  303. ClearSelection();
  304. events[eventIdx].selected = true;
  305. UpdateEventsGUI();
  306. }
  307. else
  308. {
  309. ClearSelection();
  310. guiCurveDrawing.Rebuild();
  311. UpdateEventsGUI();
  312. }
  313. }
  314. OnFrameSelected?.Invoke(frameIdx);
  315. }
  316. isPointerHeld = true;
  317. }
  318. else if (ev.Button == PointerButton.Right)
  319. {
  320. Vector2 curveCoord;
  321. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord))
  322. {
  323. contextClickPosition = drawingPos;
  324. KeyframeRef keyframeRef;
  325. if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
  326. {
  327. ClearSelection();
  328. blankContextMenu.Open(pointerPos, gui);
  329. guiCurveDrawing.Rebuild();
  330. UpdateEventsGUI();
  331. }
  332. else
  333. {
  334. // If clicked outside of current selection, just select the one keyframe
  335. if (!IsSelected(keyframeRef))
  336. {
  337. ClearSelection();
  338. SelectKeyframe(keyframeRef);
  339. guiCurveDrawing.Rebuild();
  340. UpdateEventsGUI();
  341. }
  342. keyframeContextMenu.Open(pointerPos, gui);
  343. }
  344. }
  345. else if (guiEvents.GetFrame(eventPos) != -1) // Clicked over events bar
  346. {
  347. contextClickPosition = eventPos;
  348. int eventIdx;
  349. if (!guiEvents.FindEvent(eventPos, out eventIdx))
  350. {
  351. ClearSelection();
  352. blankEventContextMenu.Open(pointerPos, gui);
  353. guiCurveDrawing.Rebuild();
  354. UpdateEventsGUI();
  355. }
  356. else
  357. {
  358. // If clicked outside of current selection, just select the one event
  359. if (!events[eventIdx].selected)
  360. {
  361. ClearSelection();
  362. events[eventIdx].selected = true;
  363. guiCurveDrawing.Rebuild();
  364. UpdateEventsGUI();
  365. }
  366. eventContextMenu.Open(pointerPos, gui);
  367. }
  368. }
  369. }
  370. }
  371. /// <summary>
  372. /// Handles input. Should be called by the owning window whenever a pointer is moved.
  373. /// </summary>
  374. /// <param name="ev">Object containing pointer move event information.</param>
  375. internal void OnPointerMoved(PointerEvent ev)
  376. {
  377. if (ev.Button != PointerButton.Left)
  378. return;
  379. if (isPointerHeld)
  380. {
  381. Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
  382. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  383. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  384. if (isMousePressedOverKey || isMousePressedOverTangent)
  385. {
  386. Rect2I drawingBounds = drawingPanel.Bounds;
  387. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  388. if (!isDragInProgress)
  389. {
  390. int distance = Vector2I.Distance(drawingPos, dragStart);
  391. if (distance >= DRAG_START_DISTANCE)
  392. {
  393. if (isMousePressedOverKey)
  394. {
  395. draggedKeyframes.Clear();
  396. foreach (var selectedEntry in selectedKeyframes)
  397. {
  398. EdAnimationCurve curve = curves[selectedEntry.curveIdx];
  399. KeyFrame[] keyFrames = curve.KeyFrames;
  400. DraggedKeyframes newEntry = new DraggedKeyframes();
  401. newEntry.curveIdx = selectedEntry.curveIdx;
  402. draggedKeyframes.Add(newEntry);
  403. foreach (var keyframeIdx in selectedEntry.keyIndices)
  404. newEntry.keys.Add(new DraggedKeyframe(keyframeIdx, keyFrames[keyframeIdx]));
  405. }
  406. }
  407. // TODO - UNDOREDO record keyframe or tangent
  408. isDragInProgress = true;
  409. }
  410. }
  411. if (isDragInProgress)
  412. {
  413. if (isMousePressedOverKey)
  414. {
  415. Vector2 diff = Vector2.Zero;
  416. Vector2 dragStartCurve;
  417. if (guiCurveDrawing.PixelToCurveSpace(dragStart, out dragStartCurve))
  418. {
  419. Vector2 currentPosCurve;
  420. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve))
  421. diff = currentPosCurve - dragStartCurve;
  422. }
  423. foreach (var draggedEntry in draggedKeyframes)
  424. {
  425. EdAnimationCurve curve = curves[draggedEntry.curveIdx];
  426. for (int i = 0; i < draggedEntry.keys.Count; i++)
  427. {
  428. DraggedKeyframe draggedKey = draggedEntry.keys[i];
  429. float newTime = draggedKey.original.time + diff.x;
  430. float newValue = draggedKey.original.value + diff.y;
  431. int newIndex = curve.UpdateKeyframe(draggedKey.index, newTime, newValue);
  432. // It's possible key changed position due to time change, but since we're moving all
  433. // keys at once they cannot change position relative to one another, otherwise we would
  434. // need to update indices for other keys as well.
  435. draggedKey.index = newIndex;
  436. draggedEntry.keys[i] = draggedKey;
  437. }
  438. curve.Apply();
  439. }
  440. // Rebuild selected keys from dragged keys (after potential sorting)
  441. ClearSelection();
  442. foreach (var draggedEntry in draggedKeyframes)
  443. {
  444. foreach (var keyframe in draggedEntry.keys)
  445. SelectKeyframe(new KeyframeRef(draggedEntry.curveIdx, keyframe.index));
  446. }
  447. guiCurveDrawing.Rebuild();
  448. UpdateEventsGUI();
  449. }
  450. else if (isMousePressedOverTangent)
  451. {
  452. EdAnimationCurve curve = curves[draggedTangent.keyframeRef.curveIdx];
  453. KeyFrame keyframe = curve.KeyFrames[draggedTangent.keyframeRef.keyIdx];
  454. Vector2 keyframeCurveCoords = new Vector2(keyframe.time, keyframe.value);
  455. Vector2 currentPosCurve;
  456. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve))
  457. {
  458. Vector2 normal = currentPosCurve - keyframeCurveCoords;
  459. normal = normal.Normalized;
  460. float tangent = EdAnimationCurve.NormalToTangent(normal);
  461. if (draggedTangent.type == TangentType.In)
  462. {
  463. if (normal.x > 0.0f)
  464. tangent = float.PositiveInfinity;
  465. keyframe.inTangent = tangent;
  466. }
  467. else
  468. {
  469. if (normal.x < 0.0f)
  470. tangent = float.PositiveInfinity;
  471. keyframe.outTangent = tangent;
  472. }
  473. curve.KeyFrames[draggedTangent.keyframeRef.keyIdx] = keyframe;
  474. curve.Apply();
  475. guiCurveDrawing.Rebuild();
  476. }
  477. }
  478. }
  479. }
  480. else // Move frame marker
  481. {
  482. int frameIdx = guiTimeline.GetFrame(pointerPos);
  483. if (frameIdx != -1)
  484. SetMarkedFrame(frameIdx);
  485. OnFrameSelected?.Invoke(frameIdx);
  486. }
  487. }
  488. }
  489. /// <summary>
  490. /// Handles input. Should be called by the owning window whenever a pointer is released.
  491. /// </summary>
  492. /// <param name="ev">Object containing pointer release event information.</param>
  493. internal void OnPointerReleased(PointerEvent ev)
  494. {
  495. isPointerHeld = false;
  496. isDragInProgress = false;
  497. isMousePressedOverKey = false;
  498. isMousePressedOverTangent = false;
  499. }
  500. /// <summary>
  501. /// Handles input. Should be called by the owning window whenever a button is released.
  502. /// </summary>
  503. /// <param name="ev">Object containing button release event information.</param>
  504. internal void OnButtonUp(ButtonEvent ev)
  505. {
  506. if(ev.Button == ButtonCode.Delete)
  507. DeleteSelectedKeyframes();
  508. }
  509. /// <summary>
  510. /// Change the set of curves to display.
  511. /// </summary>
  512. /// <param name="curves">New set of curves to draw on the GUI element.</param>
  513. public void SetCurves(EdAnimationCurve[] curves)
  514. {
  515. this.curves = curves;
  516. guiCurveDrawing.SetCurves(curves);
  517. Redraw();
  518. }
  519. /// <summary>
  520. /// Change the physical size of the GUI element.
  521. /// </summary>
  522. /// <param name="width">Width of the element in pixels.</param>
  523. /// <param name="height">Height of the element in pixels.</param>
  524. public void SetSize(int width, int height)
  525. {
  526. this.width = width;
  527. this.height = height;
  528. guiTimeline.SetSize(width, TIMELINE_HEIGHT);
  529. guiEvents.SetSize(height, EVENTS_HEIGHT);
  530. guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
  531. guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
  532. Redraw();
  533. }
  534. /// <summary>
  535. /// Number of frames per second, used for frame selection and marking.
  536. /// </summary>
  537. /// <param name="fps">Number of prames per second.</param>
  538. public void SetFPS(int fps)
  539. {
  540. guiTimeline.SetFPS(fps);
  541. guiEvents.SetFPS(fps);
  542. guiCurveDrawing.SetFPS(fps);
  543. Redraw();
  544. }
  545. /// <summary>
  546. /// Returns time for a frame with the specified index. Depends on set range and FPS.
  547. /// </summary>
  548. /// <param name="frameIdx">Index of the frame (not a key-frame) to get the time for.</param>
  549. /// <returns>Time of the frame with the provided index. </returns>
  550. public float GetTimeForFrame(int frameIdx)
  551. {
  552. return guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
  553. }
  554. /// <summary>
  555. /// Sets the frame at which to display the frame marker.
  556. /// </summary>
  557. /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
  558. public void SetMarkedFrame(int frameIdx)
  559. {
  560. markedFrameIdx = frameIdx;
  561. guiTimeline.SetMarkedFrame(frameIdx);
  562. guiEvents.SetMarkedFrame(frameIdx);
  563. guiCurveDrawing.SetMarkedFrame(frameIdx);
  564. Redraw();
  565. }
  566. /// <summary>
  567. /// Adds a new keyframe at the currently selected frame.
  568. /// </summary>
  569. public void AddKeyFrameAtMarker()
  570. {
  571. ClearSelection();
  572. foreach (var curve in curves)
  573. {
  574. float t = guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
  575. float value = curve.Evaluate(t);
  576. curve.AddKeyframe(t, value);
  577. curve.Apply();
  578. }
  579. // TODO - UNDOREDO
  580. guiCurveDrawing.Rebuild();
  581. UpdateEventsGUI();
  582. }
  583. /// <summary>
  584. /// Adds a new event at the currently selected event.
  585. /// </summary>
  586. public void AddEventAtMarker()
  587. {
  588. ClearSelection();
  589. float eventTime = guiEvents.GetTimeForFrame(markedFrameIdx);
  590. EventInfo eventInfo = new EventInfo();
  591. eventInfo.animEvent = new AnimationEvent("", eventTime);
  592. events.Add(eventInfo); // TODO - UNDOREDO
  593. UpdateEventsGUI();
  594. guiCurveDrawing.Rebuild();
  595. StartEventEdit(events.Count - 1);
  596. }
  597. /// <summary>
  598. /// Rebuilds GUI displaying the animation events list.
  599. /// </summary>
  600. private void UpdateEventsGUI()
  601. {
  602. AnimationEvent[] animEvents = new AnimationEvent[events.Count];
  603. bool[] selected = new bool[events.Count];
  604. for (int i = 0; i < events.Count; i++)
  605. {
  606. animEvents[i] = events[i].animEvent;
  607. selected[i] = events[i].selected;
  608. }
  609. guiEvents.SetEvents(animEvents, selected);
  610. guiEvents.Rebuild();
  611. }
  612. /// <summary>
  613. /// Rebuilds the entire curve editor GUI.
  614. /// </summary>
  615. public void Redraw()
  616. {
  617. guiCurveDrawing.Rebuild();
  618. guiTimeline.Rebuild();
  619. guiEvents.Rebuild();
  620. guiSidebar.Rebuild();
  621. }
  622. /// <summary>
  623. /// Changes the tangent mode for all currently selected keyframes.
  624. /// </summary>
  625. /// <param name="mode">Tangent mode to set. If only in or out tangent mode is provided, the mode for the opposite
  626. /// tangent will be kept as is.</param>
  627. private void ChangeSelectionTangentMode(TangentMode mode)
  628. {
  629. foreach (var selectedEntry in selectedKeyframes)
  630. {
  631. EdAnimationCurve curve = curves[selectedEntry.curveIdx];
  632. foreach (var keyframeIdx in selectedEntry.keyIndices)
  633. {
  634. if (mode == TangentMode.Auto || mode == TangentMode.Free)
  635. curve.SetTangentMode(keyframeIdx, mode);
  636. else
  637. {
  638. TangentMode newMode = curve.TangentModes[keyframeIdx];
  639. if (mode.HasFlag((TangentMode) TangentType.In))
  640. {
  641. // Replace only the in tangent mode, keeping the out tangent as is
  642. TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
  643. TangentMode.InAuto);
  644. newMode &= ~inFlags;
  645. newMode |= (mode & inFlags);
  646. }
  647. else
  648. {
  649. // Replace only the out tangent mode, keeping the in tangent as is
  650. TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
  651. TangentMode.OutAuto);
  652. newMode &= ~outFlags;
  653. newMode |= (mode & outFlags);
  654. }
  655. curve.SetTangentMode(keyframeIdx, newMode);
  656. }
  657. }
  658. curve.Apply();
  659. }
  660. // TODO - UNDOREDO
  661. guiCurveDrawing.Rebuild();
  662. }
  663. /// <summary>
  664. /// Adds a new keyframe at the position the context menu was opened at.
  665. /// </summary>
  666. private void AddKeyframeAtPosition()
  667. {
  668. Vector2 curveCoord;
  669. if (guiCurveDrawing.PixelToCurveSpace(contextClickPosition, out curveCoord))
  670. {
  671. ClearSelection();
  672. foreach (var curve in curves)
  673. {
  674. float t = curveCoord.x;
  675. float value = curveCoord.y;
  676. curve.AddKeyframe(t, value);
  677. curve.Apply();
  678. }
  679. // TODO - UNDOREDO
  680. guiCurveDrawing.Rebuild();
  681. UpdateEventsGUI();
  682. }
  683. }
  684. /// <summary>
  685. /// Adds a new event at the position the context menu was opened at.
  686. /// </summary>
  687. private void AddEventAtPosition()
  688. {
  689. int frame = guiEvents.GetFrame(contextClickPosition);
  690. if (frame != -1)
  691. {
  692. ClearSelection();
  693. float time = guiEvents.GetTimeForFrame(frame);
  694. EventInfo eventInfo = new EventInfo();
  695. eventInfo.animEvent = new AnimationEvent("", time);
  696. events.Add(eventInfo); // TODO - UNDOREDO
  697. UpdateEventsGUI();
  698. guiCurveDrawing.Rebuild();
  699. StartEventEdit(events.Count - 1);
  700. }
  701. }
  702. /// <summary>
  703. /// Removes all currently selected keyframes from the curves.
  704. /// </summary>
  705. private void DeleteSelectedKeyframes()
  706. {
  707. foreach (var selectedEntry in selectedKeyframes)
  708. {
  709. EdAnimationCurve curve = curves[selectedEntry.curveIdx];
  710. // Sort keys from highest to lowest so the indices don't change
  711. selectedEntry.keyIndices.Sort((x, y) =>
  712. {
  713. return y.CompareTo(x);
  714. });
  715. foreach (var keyframeIdx in selectedEntry.keyIndices)
  716. curve.RemoveKeyframe(keyframeIdx);
  717. curve.Apply();
  718. }
  719. // TODO - UNDOREDO
  720. ClearSelection();
  721. guiCurveDrawing.Rebuild();
  722. UpdateEventsGUI();
  723. }
  724. /// <summary>
  725. /// Deletes all currently selected events.
  726. /// </summary>
  727. private void DeleteSelectedEvents()
  728. {
  729. List<EventInfo> newEvents = new List<EventInfo>();
  730. foreach (var entry in events)
  731. {
  732. if(!entry.selected)
  733. newEvents.Add(entry);
  734. }
  735. events = newEvents; // TODO - UNDOREDO
  736. ClearSelection();
  737. guiCurveDrawing.Rebuild();
  738. UpdateEventsGUI();
  739. }
  740. /// <summary>
  741. /// Unselects any selected keyframes and events.
  742. /// </summary>
  743. private void ClearSelection()
  744. {
  745. guiCurveDrawing.ClearSelectedKeyframes();
  746. selectedKeyframes.Clear();
  747. foreach (var entry in events)
  748. entry.selected = false;
  749. }
  750. /// <summary>
  751. /// Adds the provided keyframe to the selection list (doesn't clear existing ones).
  752. /// </summary>
  753. /// <param name="keyframeRef">Keyframe to select.</param>
  754. private void SelectKeyframe(KeyframeRef keyframeRef)
  755. {
  756. guiCurveDrawing.SelectKeyframe(keyframeRef, true);
  757. if (!IsSelected(keyframeRef))
  758. {
  759. int curveIdx = selectedKeyframes.FindIndex(x =>
  760. {
  761. return x.curveIdx == keyframeRef.curveIdx;
  762. });
  763. if (curveIdx == -1)
  764. {
  765. curveIdx = selectedKeyframes.Count;
  766. SelectedKeyframes newKeyframes = new SelectedKeyframes();
  767. newKeyframes.curveIdx = keyframeRef.curveIdx;
  768. selectedKeyframes.Add(newKeyframes);
  769. }
  770. selectedKeyframes[curveIdx].keyIndices.Add(keyframeRef.keyIdx);
  771. }
  772. }
  773. /// <summary>
  774. /// Checks is the provided keyframe currently selected.
  775. /// </summary>
  776. /// <param name="keyframeRef">Keyframe to check.</param>
  777. /// <returns>True if selected, false otherwise.</returns>
  778. private bool IsSelected(KeyframeRef keyframeRef)
  779. {
  780. int curveIdx = selectedKeyframes.FindIndex(x =>
  781. {
  782. return x.curveIdx == keyframeRef.curveIdx;
  783. });
  784. if (curveIdx == -1)
  785. return false;
  786. int keyIdx = selectedKeyframes[curveIdx].keyIndices.FindIndex(x =>
  787. {
  788. return x == keyframeRef.keyIdx;
  789. });
  790. return keyIdx != -1;
  791. }
  792. /// <summary>
  793. /// Opens the edit window for the currently selected event.
  794. /// </summary>
  795. private void EditSelectedEvent()
  796. {
  797. for (int i = 0; i < events.Count; i++)
  798. {
  799. if (events[i].selected)
  800. {
  801. StartEventEdit(i);
  802. break;
  803. }
  804. }
  805. }
  806. /// <summary>
  807. /// Opens the event edit window for the specified event.
  808. /// </summary>
  809. /// <param name="eventIdx">Event index to open the edit window for.</param>
  810. private void StartEventEdit(int eventIdx)
  811. {
  812. AnimationEvent animEvent = events[eventIdx].animEvent;
  813. Vector2I position = new Vector2I();
  814. position.x = guiEvents.GetOffset(animEvent.Time);
  815. position.y = EVENTS_HEIGHT/2;
  816. Rect2I eventBounds = GUIUtility.CalculateBounds(eventsPanel, window.GUI);
  817. Vector2I windowPos = position + new Vector2I(eventBounds.x, eventBounds.y);
  818. EventEditWindow editWindow = DropDownWindow.Open<EventEditWindow>(window, windowPos);
  819. editWindow.Initialize(animEvent, () =>
  820. {
  821. UpdateEventsGUI();
  822. });
  823. }
  824. }
  825. /// <summary>
  826. /// Drop down window that displays input boxes used for editing an event.
  827. /// </summary>
  828. [DefaultSize(200, 50)]
  829. internal class EventEditWindow : DropDownWindow
  830. {
  831. /// <summary>
  832. /// Initializes the drop down window by creating the necessary GUI. Must be called after construction and before
  833. /// use.
  834. /// </summary>
  835. /// <param name="animEvent">Event whose properties to edit.</param>
  836. /// <param name="updateCallback">Callback triggered when event values change.</param>
  837. internal void Initialize(AnimationEvent animEvent, Action updateCallback)
  838. {
  839. GUIFloatField timeField = new GUIFloatField(new LocEdString("Time"), 40, "");
  840. timeField.Value = animEvent.Time;
  841. timeField.OnChanged += x => { animEvent.Time = x; updateCallback(); }; // TODO UNDOREDO
  842. GUITextField methodField = new GUITextField(new LocEdString("Method"), 40, false, "", GUIOption.FixedWidth(190));
  843. methodField.Value = animEvent.Name;
  844. methodField.OnChanged += x => { animEvent.Name = x; updateCallback(); }; // TODO UNDOREDO
  845. GUILayoutY vertLayout = GUI.AddLayoutY();
  846. vertLayout.AddFlexibleSpace();
  847. GUILayoutX horzLayout = vertLayout.AddLayoutX();
  848. horzLayout.AddFlexibleSpace();
  849. GUILayout contentLayout = horzLayout.AddLayoutY();
  850. GUILayout timeLayout = contentLayout.AddLayoutX();
  851. timeLayout.AddSpace(5);
  852. timeLayout.AddElement(timeField);
  853. timeLayout.AddFlexibleSpace();
  854. GUILayout methodLayout = contentLayout.AddLayoutX();
  855. methodLayout.AddSpace(5);
  856. methodLayout.AddElement(methodField);
  857. methodLayout.AddFlexibleSpace();
  858. horzLayout.AddFlexibleSpace();
  859. vertLayout.AddFlexibleSpace();
  860. }
  861. }
  862. /** @} */
  863. }