GUICurveEditor.cs 51 KB

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