2
0

GUICurveEditor.cs 48 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268
  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 VERT_PADDING = 2;
  56. private const int EVENTS_HEIGHT = 15;
  57. private const int SIDEBAR_WIDTH = 30;
  58. private const int DRAG_START_DISTANCE = 3;
  59. private AnimationWindow window;
  60. private GUILayout gui;
  61. private GUIPanel drawingPanel;
  62. private GUIPanel eventsPanel;
  63. private GUIGraphTime guiTimeline;
  64. private GUIAnimEvents guiEvents;
  65. private GUICurveDrawing guiCurveDrawing;
  66. private GUIGraphValues guiSidebar;
  67. private ContextMenu blankContextMenu;
  68. private ContextMenu keyframeContextMenu;
  69. private ContextMenu blankEventContextMenu;
  70. private ContextMenu eventContextMenu;
  71. private Vector2I contextClickPosition;
  72. private CurveDrawInfo[] curveInfos = new CurveDrawInfo[0];
  73. private bool disableCurveEdit = false;
  74. private float xRange = 60.0f;
  75. private float yRange = 10.0f;
  76. private Vector2 offset;
  77. private int width;
  78. private int height;
  79. private int markedFrameIdx;
  80. private List<SelectedKeyframes> selectedKeyframes = new List<SelectedKeyframes>();
  81. private List<EventInfo> events = new List<EventInfo>();
  82. private bool isPointerHeld;
  83. private bool isMousePressedOverKey;
  84. private bool isMousePressedOverTangent;
  85. private bool isDragInProgress;
  86. private List<DraggedKeyframes> draggedKeyframes = new List<DraggedKeyframes>();
  87. private TangentRef draggedTangent;
  88. private Vector2I dragStart;
  89. /// <summary>
  90. /// Triggers whenever user selects a new frame. Reports the index of the selected frame.
  91. /// </summary>
  92. public Action<int> OnFrameSelected;
  93. /// <summary>
  94. /// Triggered whenever a new animation event is added.
  95. /// </summary>
  96. public Action OnEventAdded;
  97. /// <summary>
  98. /// Triggered whenever values in an animation event change.
  99. /// </summary>
  100. public Action OnEventModified;
  101. /// <summary>
  102. /// Triggered whenever an animation event is deleted.
  103. /// </summary>
  104. public Action OnEventDeleted;
  105. /// <summary>
  106. /// Triggered whenever keyframe in a curve is modified (added, removed or edited).
  107. /// </summary>
  108. public Action OnCurveModified;
  109. /// <summary>
  110. /// The displayed range of the curve, where:
  111. /// .x - Range of the horizontal area. Displayed area ranges from [0, x].
  112. /// .y - Range of the vertical area. Displayed area ranges from [-y, y].
  113. /// </summary>
  114. public Vector2 Range
  115. {
  116. get { return new Vector2(xRange, yRange); }
  117. set
  118. {
  119. xRange = value.x;
  120. yRange = value.y;
  121. guiTimeline.SetRange(xRange);
  122. guiEvents.SetRange(xRange);
  123. guiCurveDrawing.SetRange(xRange, yRange * 2.0f);
  124. guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
  125. Redraw();
  126. }
  127. }
  128. /// <summary>
  129. /// Determines how much to offset the displayed curve values.
  130. /// </summary>
  131. public Vector2 Offset
  132. {
  133. get { return offset; }
  134. set
  135. {
  136. offset = value;
  137. guiTimeline.SetOffset(offset.x);
  138. guiEvents.SetOffset(offset.x);
  139. guiCurveDrawing.SetOffset(offset);
  140. guiSidebar.SetRange(offset.y - yRange, offset.y + yRange);
  141. Redraw();
  142. }
  143. }
  144. /// <summary>
  145. /// Returns the width of the curve editor, in pixels.
  146. /// </summary>
  147. public int Width
  148. {
  149. get { return width; }
  150. }
  151. /// <summary>
  152. /// Returns the height of the curve editor, in pixels.
  153. /// </summary>
  154. public int Height
  155. {
  156. get { return height; }
  157. }
  158. /// <summary>
  159. /// Set to true if curves are not allowed to be edited.
  160. /// </summary>
  161. public bool DisableCurveEdit
  162. {
  163. set { disableCurveEdit = value; }
  164. }
  165. /// <summary>
  166. /// Animation events displayed on the curve editor.
  167. /// </summary>
  168. public AnimationEvent[] Events
  169. {
  170. get
  171. {
  172. AnimationEvent[] animEvents = new AnimationEvent[events.Count];
  173. // Note: Hidden dependency. Returned events must point to the same event class this object is using, so
  174. // that any modifications made in this class will be visible in the returned values.
  175. for (int i = 0; i < events.Count; i++)
  176. animEvents[i] = events[i].animEvent;
  177. return animEvents;
  178. }
  179. set
  180. {
  181. events.Clear();
  182. for (int i = 0; i < value.Length; i++)
  183. {
  184. EventInfo eventInfo = new EventInfo();
  185. eventInfo.animEvent = value[i];
  186. events.Add(eventInfo);
  187. }
  188. UpdateEventsGUI();
  189. }
  190. }
  191. /// <summary>
  192. /// Creates a new curve editor GUI elements.
  193. /// </summary>
  194. /// <param name="window">Parent window of the GUI element.</param>
  195. /// <param name="gui">GUI layout into which to place the GUI element.</param>
  196. /// <param name="width">Width in pixels.</param>
  197. /// <param name="height">Height in pixels.</param>
  198. public GUICurveEditor(AnimationWindow window, GUILayout gui, int width, int height)
  199. {
  200. this.window = window;
  201. this.gui = gui;
  202. this.width = width;
  203. this.height = height;
  204. blankContextMenu = new ContextMenu();
  205. blankContextMenu.AddItem("Add keyframe", AddKeyframeAtPosition);
  206. blankEventContextMenu = new ContextMenu();
  207. blankEventContextMenu.AddItem("Add event", AddEventAtPosition);
  208. keyframeContextMenu = new ContextMenu();
  209. keyframeContextMenu.AddItem("Delete", DeleteSelectedKeyframes);
  210. keyframeContextMenu.AddItem("Edit", EditSelectedKeyframe);
  211. keyframeContextMenu.AddItem("Tangents/Auto", () => { ChangeSelectionTangentMode(TangentMode.Auto); });
  212. keyframeContextMenu.AddItem("Tangents/Free", () => { ChangeSelectionTangentMode(TangentMode.Free); });
  213. keyframeContextMenu.AddItem("Tangents/In/Auto", () => { ChangeSelectionTangentMode(TangentMode.InAuto); });
  214. keyframeContextMenu.AddItem("Tangents/In/Free", () => { ChangeSelectionTangentMode(TangentMode.InFree); });
  215. keyframeContextMenu.AddItem("Tangents/In/Linear", () => { ChangeSelectionTangentMode(TangentMode.InLinear); });
  216. keyframeContextMenu.AddItem("Tangents/In/Step", () => { ChangeSelectionTangentMode(TangentMode.InStep); });
  217. keyframeContextMenu.AddItem("Tangents/Out/Auto", () => { ChangeSelectionTangentMode(TangentMode.OutAuto); });
  218. keyframeContextMenu.AddItem("Tangents/Out/Free", () => { ChangeSelectionTangentMode(TangentMode.OutFree); });
  219. keyframeContextMenu.AddItem("Tangents/Out/Linear", () => { ChangeSelectionTangentMode(TangentMode.OutLinear); });
  220. keyframeContextMenu.AddItem("Tangents/Out/Step", () => { ChangeSelectionTangentMode(TangentMode.OutStep); });
  221. eventContextMenu = new ContextMenu();
  222. eventContextMenu.AddItem("Delete", DeleteSelectedEvents);
  223. eventContextMenu.AddItem("Edit", EditSelectedEvent);
  224. GUIPanel timelinePanel = gui.AddPanel();
  225. guiTimeline = new GUIGraphTime(timelinePanel, width, TIMELINE_HEIGHT);
  226. GUIPanel timelineBgPanel = gui.AddPanel(1);
  227. GUITexture timelineBackground = new GUITexture(null, EditorStyles.Header);
  228. timelineBackground.Bounds = new Rect2I(0, 0, width, TIMELINE_HEIGHT + VERT_PADDING);
  229. timelineBgPanel.AddElement(timelineBackground);
  230. eventsPanel = gui.AddPanel();
  231. eventsPanel.SetPosition(0, TIMELINE_HEIGHT + VERT_PADDING);
  232. guiEvents = new GUIAnimEvents(eventsPanel, width, EVENTS_HEIGHT);
  233. GUIPanel eventsBgPanel = eventsPanel.AddPanel(1);
  234. GUITexture eventsBackground = new GUITexture(null, EditorStyles.Header);
  235. eventsBackground.Bounds = new Rect2I(0, 0, width, EVENTS_HEIGHT + VERT_PADDING);
  236. eventsBgPanel.AddElement(eventsBackground);
  237. drawingPanel = gui.AddPanel();
  238. drawingPanel.SetPosition(0, TIMELINE_HEIGHT + EVENTS_HEIGHT + VERT_PADDING);
  239. guiCurveDrawing = new GUICurveDrawing(drawingPanel, width, height - TIMELINE_HEIGHT - EVENTS_HEIGHT - VERT_PADDING * 2, curveInfos);
  240. guiCurveDrawing.SetRange(60.0f, 20.0f);
  241. GUIPanel sidebarPanel = gui.AddPanel(-10);
  242. sidebarPanel.SetPosition(0, TIMELINE_HEIGHT + EVENTS_HEIGHT + VERT_PADDING);
  243. guiSidebar = new GUIGraphValues(sidebarPanel, SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - EVENTS_HEIGHT - VERT_PADDING * 2);
  244. guiSidebar.SetRange(-10.0f, 10.0f);
  245. }
  246. /// <summary>
  247. /// Converts pixel coordinates relative to the curve drawing area into coordinates in curve space.
  248. /// </summary>
  249. /// <param name="pixelCoords">Coordinates relative to this GUI element, in pixels.</param>
  250. /// <param name="curveCoords">Curve coordinates within the range as specified by <see cref="Range"/>. Only
  251. /// valid when function returns true.</param>
  252. /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
  253. public bool PixelToCurveSpace(Vector2I pixelCoords, out Vector2 curveCoords)
  254. {
  255. return guiCurveDrawing.PixelToCurveSpace(pixelCoords, out curveCoords);
  256. }
  257. /// <summary>
  258. /// Converts coordinate in curve space (time, value) into pixel coordinates relative to the curve drawing area
  259. /// origin.
  260. /// </summary>
  261. /// <param name="curveCoords">Time and value of the location to convert.</param>
  262. /// <returns>Coordinates relative to curve drawing area's origin, in pixels.</returns>
  263. public Vector2I CurveToPixelSpace(Vector2 curveCoords)
  264. {
  265. return guiCurveDrawing.CurveToPixelSpace(curveCoords);
  266. }
  267. /// <summary>
  268. /// Converts coordinates in window space (relative to the parent window origin) into coordinates in curve space.
  269. /// </summary>
  270. /// <param name="windowPos">Coordinates relative to parent editor window, in pixels.</param>
  271. /// <param name="curveCoord">Curve coordinates within the range as specified by <see cref="Range"/>. Only
  272. /// valid when function returns true.</param>
  273. /// <returns>True if the coordinates are within the curve area, false otherwise.</returns>
  274. public bool WindowToCurveSpace(Vector2I windowPos, out Vector2 curveCoord)
  275. {
  276. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  277. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  278. Rect2I drawingBounds = drawingPanel.Bounds;
  279. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  280. return guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord);
  281. }
  282. /// <summary>
  283. /// Handles input. Should be called by the owning window whenever a pointer is pressed.
  284. /// </summary>
  285. /// <param name="ev">Object containing pointer press event information.</param>
  286. internal void OnPointerPressed(PointerEvent ev)
  287. {
  288. if (ev.IsUsed)
  289. return;
  290. Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
  291. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  292. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  293. Rect2I drawingBounds = drawingPanel.Bounds;
  294. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  295. Rect2I eventBounds = eventsPanel.Bounds;
  296. Vector2I eventPos = pointerPos - new Vector2I(eventBounds.x, eventBounds.y);
  297. if (ev.Button == PointerButton.Left)
  298. {
  299. Vector2 curveCoord;
  300. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord))
  301. {
  302. KeyframeRef keyframeRef;
  303. if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
  304. {
  305. TangentRef tangentRef;
  306. if (guiCurveDrawing.FindTangent(drawingPos, out tangentRef))
  307. {
  308. isMousePressedOverTangent = true;
  309. dragStart = drawingPos;
  310. draggedTangent = tangentRef;
  311. }
  312. else
  313. ClearSelection();
  314. }
  315. else
  316. {
  317. if (!IsSelected(keyframeRef))
  318. {
  319. if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
  320. ClearSelection();
  321. SelectKeyframe(keyframeRef);
  322. }
  323. isMousePressedOverKey = true;
  324. dragStart = drawingPos;
  325. }
  326. guiCurveDrawing.Rebuild();
  327. UpdateEventsGUI();
  328. }
  329. else
  330. {
  331. int frameIdx = guiTimeline.GetFrame(pointerPos);
  332. if (frameIdx != -1)
  333. SetMarkedFrame(frameIdx);
  334. else
  335. {
  336. int eventIdx;
  337. if (guiEvents.FindEvent(eventPos, out eventIdx))
  338. {
  339. if (!Input.IsButtonHeld(ButtonCode.LeftShift) && !Input.IsButtonHeld(ButtonCode.RightShift))
  340. ClearSelection();
  341. events[eventIdx].selected = true;
  342. UpdateEventsGUI();
  343. }
  344. else
  345. {
  346. ClearSelection();
  347. guiCurveDrawing.Rebuild();
  348. UpdateEventsGUI();
  349. }
  350. }
  351. OnFrameSelected?.Invoke(frameIdx);
  352. }
  353. isPointerHeld = true;
  354. }
  355. else if (ev.Button == PointerButton.Right)
  356. {
  357. Vector2 curveCoord;
  358. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out curveCoord))
  359. {
  360. contextClickPosition = drawingPos;
  361. KeyframeRef keyframeRef;
  362. if (!guiCurveDrawing.FindKeyFrame(drawingPos, out keyframeRef))
  363. {
  364. ClearSelection();
  365. blankContextMenu.Open(pointerPos, gui);
  366. guiCurveDrawing.Rebuild();
  367. UpdateEventsGUI();
  368. }
  369. else
  370. {
  371. // If clicked outside of current selection, just select the one keyframe
  372. if (!IsSelected(keyframeRef))
  373. {
  374. ClearSelection();
  375. SelectKeyframe(keyframeRef);
  376. guiCurveDrawing.Rebuild();
  377. UpdateEventsGUI();
  378. }
  379. keyframeContextMenu.Open(pointerPos, gui);
  380. }
  381. }
  382. else if (guiEvents.GetFrame(eventPos) != -1) // Clicked over events bar
  383. {
  384. contextClickPosition = eventPos;
  385. int eventIdx;
  386. if (!guiEvents.FindEvent(eventPos, out eventIdx))
  387. {
  388. ClearSelection();
  389. blankEventContextMenu.Open(pointerPos, gui);
  390. guiCurveDrawing.Rebuild();
  391. UpdateEventsGUI();
  392. }
  393. else
  394. {
  395. // If clicked outside of current selection, just select the one event
  396. if (!events[eventIdx].selected)
  397. {
  398. ClearSelection();
  399. events[eventIdx].selected = true;
  400. guiCurveDrawing.Rebuild();
  401. UpdateEventsGUI();
  402. }
  403. eventContextMenu.Open(pointerPos, gui);
  404. }
  405. }
  406. }
  407. }
  408. /// <summary>
  409. /// Handles input. Should be called by the owning window whenever a pointer is moved.
  410. /// </summary>
  411. /// <param name="ev">Object containing pointer move event information.</param>
  412. internal void OnPointerMoved(PointerEvent ev)
  413. {
  414. if (ev.Button != PointerButton.Left)
  415. return;
  416. if (isPointerHeld)
  417. {
  418. Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
  419. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  420. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  421. if (isMousePressedOverKey || isMousePressedOverTangent)
  422. {
  423. Rect2I drawingBounds = drawingPanel.Bounds;
  424. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  425. if (!isDragInProgress)
  426. {
  427. int distance = Vector2I.Distance(drawingPos, dragStart);
  428. if (distance >= DRAG_START_DISTANCE)
  429. {
  430. if (isMousePressedOverKey && !disableCurveEdit)
  431. {
  432. draggedKeyframes.Clear();
  433. foreach (var selectedEntry in selectedKeyframes)
  434. {
  435. EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
  436. KeyFrame[] keyFrames = curve.KeyFrames;
  437. DraggedKeyframes newEntry = new DraggedKeyframes();
  438. newEntry.curveIdx = selectedEntry.curveIdx;
  439. draggedKeyframes.Add(newEntry);
  440. foreach (var keyframeIdx in selectedEntry.keyIndices)
  441. newEntry.keys.Add(new DraggedKeyframe(keyframeIdx, keyFrames[keyframeIdx]));
  442. }
  443. }
  444. // TODO - UNDOREDO record keyframe or tangent
  445. isDragInProgress = true;
  446. }
  447. }
  448. if (isDragInProgress)
  449. {
  450. if (isMousePressedOverKey && !disableCurveEdit)
  451. {
  452. Vector2 diff = Vector2.Zero;
  453. Vector2 dragStartCurve;
  454. if (guiCurveDrawing.PixelToCurveSpace(dragStart, out dragStartCurve))
  455. {
  456. Vector2 currentPosCurve;
  457. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve))
  458. diff = currentPosCurve - dragStartCurve;
  459. }
  460. foreach (var draggedEntry in draggedKeyframes)
  461. {
  462. EdAnimationCurve curve = curveInfos[draggedEntry.curveIdx].curve;
  463. for (int i = 0; i < draggedEntry.keys.Count; i++)
  464. {
  465. DraggedKeyframe draggedKey = draggedEntry.keys[i];
  466. float newTime = draggedKey.original.time + diff.x;
  467. float newValue = draggedKey.original.value + diff.y;
  468. int newIndex = curve.UpdateKeyframe(draggedKey.index, newTime, newValue);
  469. // It's possible key changed position due to time change, but since we're moving all
  470. // keys at once they cannot change position relative to one another, otherwise we would
  471. // need to update indices for other keys as well.
  472. draggedKey.index = newIndex;
  473. draggedEntry.keys[i] = draggedKey;
  474. }
  475. curve.Apply();
  476. }
  477. // Rebuild selected keys from dragged keys (after potential sorting)
  478. ClearSelection();
  479. foreach (var draggedEntry in draggedKeyframes)
  480. {
  481. foreach (var keyframe in draggedEntry.keys)
  482. SelectKeyframe(new KeyframeRef(draggedEntry.curveIdx, keyframe.index));
  483. }
  484. OnCurveModified?.Invoke();
  485. guiCurveDrawing.Rebuild();
  486. UpdateEventsGUI();
  487. }
  488. else if (isMousePressedOverTangent && !disableCurveEdit)
  489. {
  490. EdAnimationCurve curve = curveInfos[draggedTangent.keyframeRef.curveIdx].curve;
  491. KeyFrame keyframe = curve.KeyFrames[draggedTangent.keyframeRef.keyIdx];
  492. Vector2 keyframeCurveCoords = new Vector2(keyframe.time, keyframe.value);
  493. Vector2 currentPosCurve;
  494. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve))
  495. {
  496. Vector2 normal = currentPosCurve - keyframeCurveCoords;
  497. normal = normal.Normalized;
  498. float tangent = EdAnimationCurve.NormalToTangent(normal);
  499. if (draggedTangent.type == TangentType.In)
  500. {
  501. if (normal.x > 0.0f)
  502. tangent = float.PositiveInfinity;
  503. keyframe.inTangent = tangent;
  504. }
  505. else
  506. {
  507. if (normal.x < 0.0f)
  508. tangent = float.PositiveInfinity;
  509. keyframe.outTangent = tangent;
  510. }
  511. curve.KeyFrames[draggedTangent.keyframeRef.keyIdx] = keyframe;
  512. curve.Apply();
  513. OnCurveModified?.Invoke();
  514. guiCurveDrawing.Rebuild();
  515. }
  516. }
  517. }
  518. }
  519. else // Move frame marker
  520. {
  521. int frameIdx = guiTimeline.GetFrame(pointerPos);
  522. if (frameIdx != -1)
  523. SetMarkedFrame(frameIdx);
  524. OnFrameSelected?.Invoke(frameIdx);
  525. }
  526. }
  527. }
  528. /// <summary>
  529. /// Handles input. Should be called by the owning window whenever a pointer is released.
  530. /// </summary>
  531. /// <param name="ev">Object containing pointer release event information.</param>
  532. internal void OnPointerReleased(PointerEvent ev)
  533. {
  534. isPointerHeld = false;
  535. isDragInProgress = false;
  536. isMousePressedOverKey = false;
  537. isMousePressedOverTangent = false;
  538. }
  539. /// <summary>
  540. /// Handles input. Should be called by the owning window whenever a button is released.
  541. /// </summary>
  542. /// <param name="ev">Object containing button release event information.</param>
  543. internal void OnButtonUp(ButtonEvent ev)
  544. {
  545. if(ev.Button == ButtonCode.Delete)
  546. DeleteSelectedKeyframes();
  547. }
  548. /// <summary>
  549. /// Change the set of curves to display.
  550. /// </summary>
  551. /// <param name="curveInfos">New set of curves to draw on the GUI element.</param>
  552. public void SetCurves(CurveDrawInfo[] curveInfos)
  553. {
  554. this.curveInfos = curveInfos;
  555. guiCurveDrawing.SetCurves(curveInfos);
  556. Redraw();
  557. }
  558. /// <summary>
  559. /// Change the physical size of the GUI element.
  560. /// </summary>
  561. /// <param name="width">Width of the element in pixels.</param>
  562. /// <param name="height">Height of the element in pixels.</param>
  563. public void SetSize(int width, int height)
  564. {
  565. this.width = width;
  566. this.height = height;
  567. guiTimeline.SetSize(width, TIMELINE_HEIGHT);
  568. guiEvents.SetSize(height, EVENTS_HEIGHT);
  569. guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
  570. guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - EVENTS_HEIGHT);
  571. Redraw();
  572. }
  573. /// <summary>
  574. /// Number of frames per second, used for frame selection and marking.
  575. /// </summary>
  576. /// <param name="fps">Number of prames per second.</param>
  577. public void SetFPS(int fps)
  578. {
  579. guiTimeline.SetFPS(fps);
  580. guiEvents.SetFPS(fps);
  581. guiCurveDrawing.SetFPS(fps);
  582. Redraw();
  583. }
  584. /// <summary>
  585. /// Returns time for a frame with the specified index. Depends on set range and FPS.
  586. /// </summary>
  587. /// <param name="frameIdx">Index of the frame (not a key-frame) to get the time for.</param>
  588. /// <returns>Time of the frame with the provided index. </returns>
  589. public float GetTimeForFrame(int frameIdx)
  590. {
  591. return guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
  592. }
  593. /// <summary>
  594. /// Sets the frame at which to display the frame marker.
  595. /// </summary>
  596. /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
  597. public void SetMarkedFrame(int frameIdx)
  598. {
  599. markedFrameIdx = frameIdx;
  600. guiTimeline.SetMarkedFrame(frameIdx);
  601. guiEvents.SetMarkedFrame(frameIdx);
  602. guiCurveDrawing.SetMarkedFrame(frameIdx);
  603. Redraw();
  604. }
  605. /// <summary>
  606. /// Adds a new keyframe at the currently selected frame.
  607. /// </summary>
  608. public void AddKeyFrameAtMarker()
  609. {
  610. ClearSelection();
  611. if (!disableCurveEdit)
  612. {
  613. foreach (var curveInfo in curveInfos)
  614. {
  615. float t = guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
  616. float value = curveInfo.curve.Evaluate(t);
  617. curveInfo.curve.AddKeyframe(t, value);
  618. curveInfo.curve.Apply();
  619. }
  620. }
  621. else
  622. ShowReadOnlyMessage();
  623. // TODO - UNDOREDO
  624. OnCurveModified?.Invoke();
  625. guiCurveDrawing.Rebuild();
  626. UpdateEventsGUI();
  627. }
  628. /// <summary>
  629. /// Adds a new event at the currently selected event.
  630. /// </summary>
  631. public void AddEventAtMarker()
  632. {
  633. ClearSelection();
  634. float eventTime = guiEvents.GetTimeForFrame(markedFrameIdx);
  635. EventInfo eventInfo = new EventInfo();
  636. eventInfo.animEvent = new AnimationEvent("", eventTime);
  637. events.Add(eventInfo); // TODO - UNDOREDO
  638. OnEventAdded?.Invoke();
  639. UpdateEventsGUI();
  640. guiCurveDrawing.Rebuild();
  641. StartEventEdit(events.Count - 1);
  642. }
  643. /// <summary>
  644. /// Rebuilds GUI displaying the animation events list.
  645. /// </summary>
  646. private void UpdateEventsGUI()
  647. {
  648. AnimationEvent[] animEvents = new AnimationEvent[events.Count];
  649. bool[] selected = new bool[events.Count];
  650. for (int i = 0; i < events.Count; i++)
  651. {
  652. animEvents[i] = events[i].animEvent;
  653. selected[i] = events[i].selected;
  654. }
  655. guiEvents.SetEvents(animEvents, selected);
  656. guiEvents.Rebuild();
  657. }
  658. /// <summary>
  659. /// Rebuilds the entire curve editor GUI.
  660. /// </summary>
  661. public void Redraw()
  662. {
  663. guiCurveDrawing.Rebuild();
  664. guiTimeline.Rebuild();
  665. guiEvents.Rebuild();
  666. guiSidebar.Rebuild();
  667. }
  668. /// <summary>
  669. /// Changes the tangent mode for all currently selected keyframes.
  670. /// </summary>
  671. /// <param name="mode">Tangent mode to set. If only in or out tangent mode is provided, the mode for the opposite
  672. /// tangent will be kept as is.</param>
  673. private void ChangeSelectionTangentMode(TangentMode mode)
  674. {
  675. if (disableCurveEdit)
  676. {
  677. ShowReadOnlyMessage();
  678. return;
  679. }
  680. foreach (var selectedEntry in selectedKeyframes)
  681. {
  682. EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
  683. foreach (var keyframeIdx in selectedEntry.keyIndices)
  684. {
  685. if (mode == TangentMode.Auto || mode == TangentMode.Free)
  686. curve.SetTangentMode(keyframeIdx, mode);
  687. else
  688. {
  689. TangentMode newMode = curve.TangentModes[keyframeIdx];
  690. if (mode.HasFlag((TangentMode) TangentType.In))
  691. {
  692. // Replace only the in tangent mode, keeping the out tangent as is
  693. TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
  694. TangentMode.InStep);
  695. newMode &= ~inFlags;
  696. newMode |= (mode & inFlags);
  697. }
  698. else
  699. {
  700. // Replace only the out tangent mode, keeping the in tangent as is
  701. TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
  702. TangentMode.OutStep);
  703. newMode &= ~outFlags;
  704. newMode |= (mode & outFlags);
  705. }
  706. curve.SetTangentMode(keyframeIdx, newMode);
  707. }
  708. }
  709. curve.Apply();
  710. }
  711. // TODO - UNDOREDO
  712. OnCurveModified?.Invoke();
  713. guiCurveDrawing.Rebuild();
  714. }
  715. /// <summary>
  716. /// Adds a new keyframe at the position the context menu was opened at.
  717. /// </summary>
  718. private void AddKeyframeAtPosition()
  719. {
  720. Vector2 curveCoord;
  721. if (guiCurveDrawing.PixelToCurveSpace(contextClickPosition, out curveCoord))
  722. {
  723. ClearSelection();
  724. if (!disableCurveEdit)
  725. {
  726. foreach (var curveInfo in curveInfos)
  727. {
  728. float t = curveCoord.x;
  729. float value = curveCoord.y;
  730. curveInfo.curve.AddKeyframe(t, value);
  731. curveInfo.curve.Apply();
  732. }
  733. }
  734. else
  735. ShowReadOnlyMessage();
  736. // TODO - UNDOREDO
  737. OnCurveModified?.Invoke();
  738. guiCurveDrawing.Rebuild();
  739. UpdateEventsGUI();
  740. }
  741. }
  742. /// <summary>
  743. /// Adds a new event at the position the context menu was opened at.
  744. /// </summary>
  745. private void AddEventAtPosition()
  746. {
  747. int frame = guiEvents.GetFrame(contextClickPosition);
  748. if (frame != -1)
  749. {
  750. ClearSelection();
  751. float time = guiEvents.GetTime(contextClickPosition.x);
  752. EventInfo eventInfo = new EventInfo();
  753. eventInfo.animEvent = new AnimationEvent("", time);
  754. events.Add(eventInfo); // TODO - UNDOREDO
  755. OnEventAdded?.Invoke();
  756. UpdateEventsGUI();
  757. guiCurveDrawing.Rebuild();
  758. StartEventEdit(events.Count - 1);
  759. }
  760. }
  761. /// <summary>
  762. /// Removes all currently selected keyframes from the curves.
  763. /// </summary>
  764. private void DeleteSelectedKeyframes()
  765. {
  766. if (!disableCurveEdit)
  767. {
  768. foreach (var selectedEntry in selectedKeyframes)
  769. {
  770. EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
  771. // Sort keys from highest to lowest so the indices don't change
  772. selectedEntry.keyIndices.Sort((x, y) =>
  773. {
  774. return y.CompareTo(x);
  775. });
  776. foreach (var keyframeIdx in selectedEntry.keyIndices)
  777. curve.RemoveKeyframe(keyframeIdx);
  778. curve.Apply();
  779. }
  780. }
  781. else
  782. ShowReadOnlyMessage();
  783. // TODO - UNDOREDO
  784. ClearSelection();
  785. OnCurveModified?.Invoke();
  786. guiCurveDrawing.Rebuild();
  787. UpdateEventsGUI();
  788. }
  789. /// <summary>
  790. /// Deletes all currently selected events.
  791. /// </summary>
  792. private void DeleteSelectedEvents()
  793. {
  794. List<EventInfo> newEvents = new List<EventInfo>();
  795. foreach (var entry in events)
  796. {
  797. if(!entry.selected)
  798. newEvents.Add(entry);
  799. }
  800. events = newEvents; // TODO - UNDOREDO
  801. OnEventDeleted?.Invoke();
  802. ClearSelection();
  803. guiCurveDrawing.Rebuild();
  804. UpdateEventsGUI();
  805. }
  806. /// <summary>
  807. /// Unselects any selected keyframes and events.
  808. /// </summary>
  809. private void ClearSelection()
  810. {
  811. guiCurveDrawing.ClearSelectedKeyframes();
  812. selectedKeyframes.Clear();
  813. foreach (var entry in events)
  814. entry.selected = false;
  815. }
  816. /// <summary>
  817. /// Adds the provided keyframe to the selection list (doesn't clear existing ones).
  818. /// </summary>
  819. /// <param name="keyframeRef">Keyframe to select.</param>
  820. private void SelectKeyframe(KeyframeRef keyframeRef)
  821. {
  822. guiCurveDrawing.SelectKeyframe(keyframeRef, true);
  823. if (!IsSelected(keyframeRef))
  824. {
  825. int curveIdx = selectedKeyframes.FindIndex(x =>
  826. {
  827. return x.curveIdx == keyframeRef.curveIdx;
  828. });
  829. if (curveIdx == -1)
  830. {
  831. curveIdx = selectedKeyframes.Count;
  832. SelectedKeyframes newKeyframes = new SelectedKeyframes();
  833. newKeyframes.curveIdx = keyframeRef.curveIdx;
  834. selectedKeyframes.Add(newKeyframes);
  835. }
  836. selectedKeyframes[curveIdx].keyIndices.Add(keyframeRef.keyIdx);
  837. }
  838. }
  839. /// <summary>
  840. /// Checks is the provided keyframe currently selected.
  841. /// </summary>
  842. /// <param name="keyframeRef">Keyframe to check.</param>
  843. /// <returns>True if selected, false otherwise.</returns>
  844. private bool IsSelected(KeyframeRef keyframeRef)
  845. {
  846. int curveIdx = selectedKeyframes.FindIndex(x =>
  847. {
  848. return x.curveIdx == keyframeRef.curveIdx;
  849. });
  850. if (curveIdx == -1)
  851. return false;
  852. int keyIdx = selectedKeyframes[curveIdx].keyIndices.FindIndex(x =>
  853. {
  854. return x == keyframeRef.keyIdx;
  855. });
  856. return keyIdx != -1;
  857. }
  858. /// <summary>
  859. /// Opens the edit window for the currently selected keyframe.
  860. /// </summary>
  861. private void EditSelectedKeyframe()
  862. {
  863. if (disableCurveEdit)
  864. {
  865. ShowReadOnlyMessage();
  866. return;
  867. }
  868. if (selectedKeyframes.Count == 0)
  869. return;
  870. EdAnimationCurve curve = curveInfos[selectedKeyframes[0].curveIdx].curve;
  871. KeyFrame[] keyFrames = curve.KeyFrames;
  872. int keyIndex = selectedKeyframes[0].keyIndices[0];
  873. KeyFrame keyFrame = keyFrames[keyIndex];
  874. Vector2I position = guiCurveDrawing.CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
  875. Rect2I drawingBounds = GUIUtility.CalculateBounds(drawingPanel, window.GUI);
  876. position.x = MathEx.Clamp(position.x, 0, drawingBounds.width);
  877. position.y = MathEx.Clamp(position.y, 0, drawingBounds.height);
  878. Vector2I windowPos = position + new Vector2I(drawingBounds.x, drawingBounds.y);
  879. KeyframeEditWindow editWindow = DropDownWindow.Open<KeyframeEditWindow>(window, windowPos);
  880. editWindow.Initialize(keyFrame, x =>
  881. {
  882. curve.UpdateKeyframe(keyIndex, x.time, x.value);
  883. curve.Apply();
  884. // TODO UNDOREDO
  885. guiCurveDrawing.Rebuild();
  886. OnCurveModified?.Invoke();
  887. });
  888. }
  889. /// <summary>
  890. /// Opens the edit window for the currently selected event.
  891. /// </summary>
  892. private void EditSelectedEvent()
  893. {
  894. for (int i = 0; i < events.Count; i++)
  895. {
  896. if (events[i].selected)
  897. {
  898. StartEventEdit(i);
  899. break;
  900. }
  901. }
  902. }
  903. /// <summary>
  904. /// Opens the event edit window for the specified event.
  905. /// </summary>
  906. /// <param name="eventIdx">Event index to open the edit window for.</param>
  907. private void StartEventEdit(int eventIdx)
  908. {
  909. AnimationEvent animEvent = events[eventIdx].animEvent;
  910. Vector2I position = new Vector2I();
  911. position.x = guiEvents.GetOffset(animEvent.Time);
  912. position.y = EVENTS_HEIGHT/2;
  913. Rect2I eventBounds = GUIUtility.CalculateBounds(eventsPanel, window.GUI);
  914. Vector2I windowPos = position + new Vector2I(eventBounds.x, eventBounds.y);
  915. SceneObject so = window.SelectedSO;
  916. Component[] components = so.GetComponents();
  917. string[] componentNames = new string[components.Length];
  918. for (int i = 0; i < components.Length; i++)
  919. componentNames[i] = components[i].GetType().Name;
  920. EventEditWindow editWindow = DropDownWindow.Open<EventEditWindow>(window, windowPos);
  921. editWindow.Initialize(animEvent, componentNames, () =>
  922. {
  923. UpdateEventsGUI();
  924. OnEventModified?.Invoke();
  925. });
  926. }
  927. /// <summary>
  928. /// Shows a dialog box that notifies the user that the animation clip is read only.
  929. /// </summary>
  930. private void ShowReadOnlyMessage()
  931. {
  932. LocEdString title = new LocEdString("Warning");
  933. LocEdString message =
  934. new LocEdString("You cannot edit keyframes on animation clips that" +
  935. " are imported from an external file.");
  936. DialogBox.Open(title, message, DialogBox.Type.OK);
  937. }
  938. }
  939. /// <summary>
  940. /// Drop down window that displays input boxes used for editing a keyframe.
  941. /// </summary>
  942. [DefaultSize(120, 80)]
  943. internal class KeyframeEditWindow : DropDownWindow
  944. {
  945. /// <summary>
  946. /// Initializes the drop down window by creating the necessary GUI. Must be called after construction and before
  947. /// use.
  948. /// </summary>
  949. /// <param name="keyFrame">Keyframe whose properties to edit.</param>
  950. /// <param name="updateCallback">Callback triggered when event values change.</param>
  951. internal void Initialize(KeyFrame keyFrame, Action<KeyFrame> updateCallback)
  952. {
  953. GUIFloatField timeField = new GUIFloatField(new LocEdString("Time"), 40, "");
  954. timeField.Value = keyFrame.time;
  955. timeField.OnChanged += x => { keyFrame.time = x; updateCallback(keyFrame); };
  956. GUIFloatField valueField = new GUIFloatField(new LocEdString("Value"), 40, "");
  957. valueField.Value = keyFrame.value;
  958. valueField.OnChanged += x => { keyFrame.value = x; updateCallback(keyFrame); };
  959. GUILayoutY vertLayout = GUI.AddLayoutY();
  960. vertLayout.AddFlexibleSpace();
  961. GUILayoutX horzLayout = vertLayout.AddLayoutX();
  962. horzLayout.AddFlexibleSpace();
  963. GUILayout contentLayout = horzLayout.AddLayoutY();
  964. GUILayout timeLayout = contentLayout.AddLayoutX();
  965. timeLayout.AddSpace(5);
  966. timeLayout.AddElement(timeField);
  967. timeLayout.AddFlexibleSpace();
  968. GUILayout componentLayout = contentLayout.AddLayoutX();
  969. componentLayout.AddSpace(5);
  970. componentLayout.AddElement(valueField);
  971. componentLayout.AddFlexibleSpace();
  972. horzLayout.AddFlexibleSpace();
  973. vertLayout.AddFlexibleSpace();
  974. }
  975. }
  976. /// <summary>
  977. /// Drop down window that displays input boxes used for editing an event.
  978. /// </summary>
  979. [DefaultSize(200, 80)]
  980. internal class EventEditWindow : DropDownWindow
  981. {
  982. /// <summary>
  983. /// Initializes the drop down window by creating the necessary GUI. Must be called after construction and before
  984. /// use.
  985. /// </summary>
  986. /// <param name="animEvent">Event whose properties to edit.</param>
  987. /// <param name="componentNames">List of component names that the user can select from.</param>
  988. /// <param name="updateCallback">Callback triggered when event values change.</param>
  989. internal void Initialize(AnimationEvent animEvent, string[] componentNames, Action updateCallback)
  990. {
  991. int selectedIndex = -1;
  992. string methodName = "";
  993. if (!string.IsNullOrEmpty(animEvent.Name))
  994. {
  995. string[] nameEntries = animEvent.Name.Split('/');
  996. if (nameEntries.Length > 1)
  997. {
  998. string typeName = nameEntries[0];
  999. for (int i = 0; i < componentNames.Length; i++)
  1000. {
  1001. if (componentNames[i] == typeName)
  1002. {
  1003. selectedIndex = i;
  1004. break;
  1005. }
  1006. }
  1007. methodName = nameEntries[nameEntries.Length - 1];
  1008. }
  1009. }
  1010. GUIFloatField timeField = new GUIFloatField(new LocEdString("Time"), 40, "");
  1011. timeField.Value = animEvent.Time;
  1012. timeField.OnChanged += x => { animEvent.Time = x; updateCallback(); }; // TODO UNDOREDO
  1013. GUIListBoxField componentField = new GUIListBoxField(componentNames, new LocEdString("Component"), 40);
  1014. if (selectedIndex != -1)
  1015. componentField.Index = selectedIndex;
  1016. componentField.OnSelectionChanged += x =>
  1017. {
  1018. string compName = "";
  1019. if (x != -1)
  1020. compName = componentNames[x] + "/";
  1021. animEvent.Name = compName + x;
  1022. updateCallback();
  1023. };// TODO UNDOREDO
  1024. GUITextField methodField = new GUITextField(new LocEdString("Method"), 40, false, "", GUIOption.FixedWidth(190));
  1025. methodField.Value = methodName;
  1026. methodField.OnChanged += x =>
  1027. {
  1028. string compName = "";
  1029. if(componentField.Index != -1)
  1030. compName = componentNames[componentField.Index] + "/";
  1031. animEvent.Name = compName + x;
  1032. updateCallback();
  1033. }; // TODO UNDOREDO
  1034. GUILayoutY vertLayout = GUI.AddLayoutY();
  1035. vertLayout.AddFlexibleSpace();
  1036. GUILayoutX horzLayout = vertLayout.AddLayoutX();
  1037. horzLayout.AddFlexibleSpace();
  1038. GUILayout contentLayout = horzLayout.AddLayoutY();
  1039. GUILayout timeLayout = contentLayout.AddLayoutX();
  1040. timeLayout.AddSpace(5);
  1041. timeLayout.AddElement(timeField);
  1042. timeLayout.AddFlexibleSpace();
  1043. GUILayout componentLayout = contentLayout.AddLayoutX();
  1044. componentLayout.AddSpace(5);
  1045. componentLayout.AddElement(componentField);
  1046. componentLayout.AddFlexibleSpace();
  1047. GUILayout methodLayout = contentLayout.AddLayoutX();
  1048. methodLayout.AddSpace(5);
  1049. methodLayout.AddElement(methodField);
  1050. methodLayout.AddFlexibleSpace();
  1051. horzLayout.AddFlexibleSpace();
  1052. vertLayout.AddFlexibleSpace();
  1053. }
  1054. }
  1055. /** @} */
  1056. }