GUICurveEditor.cs 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431
  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 double-clicked.
  423. /// </summary>
  424. /// <param name="ev">Object containing pointer press event information.</param>
  425. internal void OnPointerDoubleClicked(PointerEvent ev)
  426. {
  427. if (ev.IsUsed)
  428. return;
  429. Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
  430. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  431. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  432. bool isOverEditor = pointerPos.x >= 0 && pointerPos.x < width && pointerPos.y >= 0 && pointerPos.y < height;
  433. if (!isOverEditor)
  434. return;
  435. Rect2I drawingBounds = drawingPanel.Bounds;
  436. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  437. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out var curveCoord, true))
  438. {
  439. int curveIdx = guiCurveDrawing.FindCurve(drawingPos);
  440. if (curveIdx == -1)
  441. return;
  442. AddKeyframe(curveIdx, curveCoord.x);
  443. }
  444. }
  445. /// <summary>
  446. /// Handles input. Should be called by the owning window whenever a pointer is moved.
  447. /// </summary>
  448. /// <param name="ev">Object containing pointer move event information.</param>
  449. internal void OnPointerMoved(PointerEvent ev)
  450. {
  451. if (ev.Button != PointerButton.Left)
  452. return;
  453. if (isPointerHeld)
  454. {
  455. Vector2I windowPos = window.ScreenToWindowPos(ev.ScreenPos);
  456. Rect2I elementBounds = GUIUtility.CalculateBounds(gui, window.GUI);
  457. Vector2I pointerPos = windowPos - new Vector2I(elementBounds.x, elementBounds.y);
  458. if (isMousePressedOverKey || isMousePressedOverTangent)
  459. {
  460. Rect2I drawingBounds = drawingPanel.Bounds;
  461. Vector2I drawingPos = pointerPos - new Vector2I(drawingBounds.x, drawingBounds.y);
  462. if (!isDragInProgress)
  463. {
  464. int distance = Vector2I.Distance(drawingPos, dragStart);
  465. if (distance >= DRAG_START_DISTANCE)
  466. {
  467. if (isMousePressedOverKey && !disableCurveEdit)
  468. {
  469. draggedKeyframes.Clear();
  470. foreach (var selectedEntry in selectedKeyframes)
  471. {
  472. EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
  473. KeyFrame[] keyFrames = curve.KeyFrames;
  474. DraggedKeyframes newEntry = new DraggedKeyframes();
  475. newEntry.curveIdx = selectedEntry.curveIdx;
  476. draggedKeyframes.Add(newEntry);
  477. foreach (var keyframeIdx in selectedEntry.keyIndices)
  478. newEntry.keys.Add(new DraggedKeyframe(keyframeIdx, keyFrames[keyframeIdx]));
  479. }
  480. }
  481. isDragInProgress = true;
  482. }
  483. }
  484. if (isDragInProgress)
  485. {
  486. if (isMousePressedOverKey && !disableCurveEdit)
  487. {
  488. Vector2 diff = Vector2.Zero;
  489. Vector2 dragStartCurve;
  490. if (guiCurveDrawing.PixelToCurveSpace(dragStart, out dragStartCurve, true))
  491. {
  492. Vector2 currentPosCurve;
  493. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve, true))
  494. diff = currentPosCurve - dragStartCurve;
  495. }
  496. foreach (var draggedEntry in draggedKeyframes)
  497. {
  498. EdAnimationCurve curve = curveInfos[draggedEntry.curveIdx].curve;
  499. for (int i = 0; i < draggedEntry.keys.Count; i++)
  500. {
  501. DraggedKeyframe draggedKey = draggedEntry.keys[i];
  502. float newTime = Math.Max(0.0f, draggedKey.original.time + diff.x);
  503. float newValue = draggedKey.original.value + diff.y;
  504. int newIndex = curve.UpdateKeyframe(draggedKey.index, newTime, newValue);
  505. // It's possible key changed position due to time change, but since we're moving all
  506. // keys at once they cannot change position relative to one another, otherwise we would
  507. // need to update indices for other keys as well.
  508. draggedKey.index = newIndex;
  509. draggedEntry.keys[i] = draggedKey;
  510. }
  511. curve.Apply();
  512. }
  513. // Rebuild selected keys from dragged keys (after potential sorting)
  514. ClearSelection();
  515. foreach (var draggedEntry in draggedKeyframes)
  516. {
  517. foreach (var keyframe in draggedEntry.keys)
  518. SelectKeyframe(new KeyframeRef(draggedEntry.curveIdx, keyframe.index));
  519. }
  520. isModifiedDuringDrag = true;
  521. guiCurveDrawing.Rebuild();
  522. UpdateEventsGUI();
  523. }
  524. else if (isMousePressedOverTangent && !disableCurveEdit)
  525. {
  526. EdAnimationCurve curve = curveInfos[draggedTangent.keyframeRef.curveIdx].curve;
  527. KeyFrame keyframe = curve.KeyFrames[draggedTangent.keyframeRef.keyIdx];
  528. Vector2 keyframeCurveCoords = new Vector2(keyframe.time, keyframe.value);
  529. Vector2 currentPosCurve;
  530. if (guiCurveDrawing.PixelToCurveSpace(drawingPos, out currentPosCurve, true))
  531. {
  532. Vector2 normal = currentPosCurve - keyframeCurveCoords;
  533. normal = normal.Normalized;
  534. float tangent = EdAnimationCurve.NormalToTangent(normal);
  535. if (draggedTangent.type == TangentType.In)
  536. {
  537. if (normal.x > 0.0f)
  538. tangent = float.PositiveInfinity;
  539. keyframe.inTangent = -tangent;
  540. if(curve.TangentModes[draggedTangent.keyframeRef.keyIdx] == TangentMode.Free)
  541. keyframe.outTangent = -tangent;
  542. }
  543. else
  544. {
  545. if (normal.x < 0.0f)
  546. tangent = float.PositiveInfinity;
  547. keyframe.outTangent = tangent;
  548. if (curve.TangentModes[draggedTangent.keyframeRef.keyIdx] == TangentMode.Free)
  549. keyframe.inTangent = tangent;
  550. }
  551. curve.KeyFrames[draggedTangent.keyframeRef.keyIdx] = keyframe;
  552. curve.Apply();
  553. isModifiedDuringDrag = true;
  554. guiCurveDrawing.Rebuild();
  555. }
  556. }
  557. }
  558. }
  559. else // Move frame marker
  560. {
  561. int frameIdx = guiTimeline.GetFrame(pointerPos);
  562. if (frameIdx != -1)
  563. SetMarkedFrame(frameIdx);
  564. OnFrameSelected?.Invoke(frameIdx);
  565. }
  566. }
  567. }
  568. /// <summary>
  569. /// Handles input. Should be called by the owning window whenever a pointer is released.
  570. /// </summary>
  571. /// <param name="ev">Object containing pointer release event information.</param>
  572. internal void OnPointerReleased(PointerEvent ev)
  573. {
  574. if (isModifiedDuringDrag)
  575. OnCurveModified?.Invoke();
  576. isPointerHeld = false;
  577. isDragInProgress = false;
  578. isMousePressedOverKey = false;
  579. isMousePressedOverTangent = false;
  580. isModifiedDuringDrag = false;
  581. }
  582. /// <summary>
  583. /// Handles input. Should be called by the owning window whenever a button is released.
  584. /// </summary>
  585. /// <param name="ev">Object containing button release event information.</param>
  586. internal void OnButtonUp(ButtonEvent ev)
  587. {
  588. if(ev.Button == ButtonCode.Delete)
  589. DeleteSelectedKeyframes();
  590. }
  591. /// <summary>
  592. /// Change the set of curves to display.
  593. /// </summary>
  594. /// <param name="curveInfos">New set of curves to draw on the GUI element.</param>
  595. public void SetCurves(CurveDrawInfo[] curveInfos)
  596. {
  597. this.curveInfos = curveInfos;
  598. guiCurveDrawing.SetCurves(curveInfos);
  599. Redraw();
  600. }
  601. /// <summary>
  602. /// Changes curve rendering mode. Normally the curves are drawn individually, but when range rendering is enabled
  603. /// the area between the first two curves is drawn instead. This setting is ignored if less than two curves are
  604. /// present. More than two curves are also ignored.
  605. /// </summary>
  606. /// <param name="drawRange">True to enable range rendering mode, false to enable individual curve rendering.</param>
  607. public void SetDrawRange(bool drawRange)
  608. {
  609. drawCurveRange = drawRange;
  610. guiCurveDrawing.SetDrawRange(drawRange);
  611. Redraw();
  612. }
  613. /// <summary>
  614. /// Change the physical size of the GUI element.
  615. /// </summary>
  616. /// <param name="width">Width of the element in pixels.</param>
  617. /// <param name="height">Height of the element in pixels.</param>
  618. public void SetSize(int width, int height)
  619. {
  620. this.width = width;
  621. this.height = height;
  622. int eventsHeaderHeight = 0;
  623. if (showEvents)
  624. {
  625. eventsHeaderHeight = EVENTS_HEIGHT;
  626. guiEvents.SetSize(width, EVENTS_HEIGHT);
  627. eventsBackground.Bounds = new Rect2I(0, 0, width, EVENTS_HEIGHT + VERT_PADDING);
  628. }
  629. guiTimeline.SetSize(width, TIMELINE_HEIGHT);
  630. guiCurveDrawing.SetSize(width, height - TIMELINE_HEIGHT - eventsHeaderHeight);
  631. guiSidebar.SetSize(SIDEBAR_WIDTH, height - TIMELINE_HEIGHT - eventsHeaderHeight);
  632. timelineBackground.Bounds = new Rect2I(0, 0, width, TIMELINE_HEIGHT + VERT_PADDING);
  633. Redraw();
  634. }
  635. /// <summary>
  636. /// Number of frames per second, used for frame selection and marking.
  637. /// </summary>
  638. /// <param name="fps">Number of prames per second.</param>
  639. public void SetFPS(int fps)
  640. {
  641. guiTimeline.SetFPS(fps);
  642. guiCurveDrawing.SetFPS(fps);
  643. if(showEvents)
  644. guiEvents.SetFPS(fps);
  645. Redraw();
  646. }
  647. /// <summary>
  648. /// Sets a scene object that will be used for enumerating components/methods used for adding events.
  649. /// </summary>
  650. /// <param name="so">Scene object containing the animation component.</param>
  651. public void SetEventSceneObject(SceneObject so)
  652. {
  653. eventsSO = so;
  654. }
  655. /// <summary>
  656. /// Returns time for a frame with the specified index. Depends on set range and FPS.
  657. /// </summary>
  658. /// <param name="frameIdx">Index of the frame (not a key-frame) to get the time for.</param>
  659. /// <returns>Time of the frame with the provided index. </returns>
  660. public float GetTimeForFrame(int frameIdx)
  661. {
  662. return guiCurveDrawing.GetTimeForFrame(frameIdx);
  663. }
  664. /// <summary>
  665. /// Sets the frame at which to display the frame marker.
  666. /// </summary>
  667. /// <param name="frameIdx">Index of the frame to display the marker on, or -1 to clear the marker.</param>
  668. public void SetMarkedFrame(int frameIdx)
  669. {
  670. markedFrameIdx = frameIdx;
  671. guiTimeline.SetMarkedFrame(frameIdx);
  672. guiCurveDrawing.SetMarkedFrame(frameIdx);
  673. if(showEvents)
  674. guiEvents.SetMarkedFrame(frameIdx);
  675. Redraw();
  676. }
  677. /// <summary>
  678. /// Adds a new keyframe at the currently selected frame.
  679. /// </summary>
  680. public void AddKeyFrameAtMarker()
  681. {
  682. ClearSelection();
  683. if (!disableCurveEdit)
  684. {
  685. foreach (var curveInfo in curveInfos)
  686. {
  687. float t = guiCurveDrawing.GetTimeForFrame(markedFrameIdx);
  688. float value = curveInfo.curve.Evaluate(t);
  689. curveInfo.curve.AddOrUpdateKeyframe(t, value);
  690. curveInfo.curve.Apply();
  691. }
  692. }
  693. else
  694. ShowReadOnlyMessage();
  695. OnCurveModified?.Invoke();
  696. guiCurveDrawing.Rebuild();
  697. UpdateEventsGUI();
  698. }
  699. /// <summary>
  700. /// Adds a new event at the currently selected event.
  701. /// </summary>
  702. public void AddEventAtMarker()
  703. {
  704. ClearSelection();
  705. if (!showEvents)
  706. return;
  707. float eventTime = guiEvents.GetTimeForFrame(markedFrameIdx);
  708. EventInfo eventInfo = new EventInfo();
  709. eventInfo.animEvent = new AnimationEvent("", eventTime);
  710. events.Add(eventInfo);
  711. OnEventAdded?.Invoke();
  712. UpdateEventsGUI();
  713. guiCurveDrawing.Rebuild();
  714. StartEventEdit(events.Count - 1);
  715. }
  716. /// <summary>
  717. /// Rebuilds GUI displaying the animation events list.
  718. /// </summary>
  719. private void UpdateEventsGUI()
  720. {
  721. if (!showEvents)
  722. return;
  723. AnimationEvent[] animEvents = new AnimationEvent[events.Count];
  724. bool[] selected = new bool[events.Count];
  725. for (int i = 0; i < events.Count; i++)
  726. {
  727. animEvents[i] = events[i].animEvent;
  728. selected[i] = events[i].selected;
  729. }
  730. guiEvents.SetEvents(animEvents, selected);
  731. guiEvents.Rebuild();
  732. }
  733. /// <summary>
  734. /// Rebuilds the entire curve editor GUI.
  735. /// </summary>
  736. public void Redraw()
  737. {
  738. guiCurveDrawing.Rebuild();
  739. guiTimeline.Rebuild();
  740. guiSidebar.Rebuild();
  741. if(showEvents)
  742. guiEvents.Rebuild();
  743. }
  744. /// <summary>
  745. /// Changes the tangent mode for all currently selected keyframes.
  746. /// </summary>
  747. /// <param name="mode">Tangent mode to set. If only in or out tangent mode is provided, the mode for the opposite
  748. /// tangent will be kept as is.</param>
  749. private void ChangeSelectionTangentMode(TangentMode mode)
  750. {
  751. if (disableCurveEdit)
  752. {
  753. ShowReadOnlyMessage();
  754. return;
  755. }
  756. foreach (var selectedEntry in selectedKeyframes)
  757. {
  758. EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
  759. foreach (var keyframeIdx in selectedEntry.keyIndices)
  760. {
  761. if (mode == TangentMode.Auto || mode == TangentMode.Free)
  762. curve.SetTangentMode(keyframeIdx, mode);
  763. else
  764. {
  765. TangentMode newMode = curve.TangentModes[keyframeIdx];
  766. if (mode.HasFlag((TangentMode) TangentType.In))
  767. {
  768. // Replace only the in tangent mode, keeping the out tangent as is
  769. TangentMode inFlags = (TangentMode.InAuto | TangentMode.InFree | TangentMode.InLinear |
  770. TangentMode.InStep);
  771. newMode &= ~inFlags;
  772. newMode |= (mode & inFlags);
  773. }
  774. else
  775. {
  776. // Replace only the out tangent mode, keeping the in tangent as is
  777. TangentMode outFlags = (TangentMode.OutAuto | TangentMode.OutFree | TangentMode.OutLinear |
  778. TangentMode.OutStep);
  779. newMode &= ~outFlags;
  780. newMode |= (mode & outFlags);
  781. }
  782. curve.SetTangentMode(keyframeIdx, newMode);
  783. }
  784. }
  785. curve.Apply();
  786. }
  787. OnCurveModified?.Invoke();
  788. guiCurveDrawing.Rebuild();
  789. }
  790. /// <summary>
  791. /// Adds a new keyframe at the specified time on the provided curve.
  792. /// </summary>
  793. /// <param name="curveIdx">Index of the curve to add the keyframe to.</param>
  794. /// <param name="time">Time at which to add the keyframe.</param>
  795. private void AddKeyframe(int curveIdx, float time)
  796. {
  797. ClearSelection();
  798. if (!disableCurveEdit)
  799. {
  800. if (curveIdx < curveInfos.Length)
  801. {
  802. EdAnimationCurve curve = curveInfos[curveIdx].curve;
  803. float value = curve.Evaluate(time, false);
  804. curve.AddOrUpdateKeyframe(time, value);
  805. curve.Apply();
  806. }
  807. }
  808. else
  809. ShowReadOnlyMessage();
  810. OnCurveModified?.Invoke();
  811. guiCurveDrawing.Rebuild();
  812. UpdateEventsGUI();
  813. }
  814. /// <summary>
  815. /// Adds a new keyframe at the position the context menu was opened at.
  816. /// </summary>
  817. private void AddKeyframeAtPosition()
  818. {
  819. Vector2 curveCoord;
  820. if (guiCurveDrawing.PixelToCurveSpace(contextClickPosition, out curveCoord))
  821. {
  822. ClearSelection();
  823. if (!disableCurveEdit)
  824. {
  825. foreach (var curveInfo in curveInfos)
  826. {
  827. float t = curveCoord.x;
  828. float value = curveCoord.y;
  829. curveInfo.curve.AddOrUpdateKeyframe(t, value);
  830. curveInfo.curve.Apply();
  831. }
  832. }
  833. else
  834. ShowReadOnlyMessage();
  835. OnCurveModified?.Invoke();
  836. guiCurveDrawing.Rebuild();
  837. UpdateEventsGUI();
  838. }
  839. }
  840. /// <summary>
  841. /// Adds a new event at the position the context menu was opened at.
  842. /// </summary>
  843. private void AddEventAtPosition()
  844. {
  845. if (!showEvents)
  846. return;
  847. int frame = guiEvents.GetFrame(contextClickPosition);
  848. if (frame != -1)
  849. {
  850. ClearSelection();
  851. float time = guiEvents.GetTime(contextClickPosition.x);
  852. EventInfo eventInfo = new EventInfo();
  853. eventInfo.animEvent = new AnimationEvent("", time);
  854. events.Add(eventInfo);
  855. OnEventAdded?.Invoke();
  856. UpdateEventsGUI();
  857. guiCurveDrawing.Rebuild();
  858. StartEventEdit(events.Count - 1);
  859. }
  860. }
  861. /// <summary>
  862. /// Removes all currently selected keyframes from the curves.
  863. /// </summary>
  864. private void DeleteSelectedKeyframes()
  865. {
  866. if (!disableCurveEdit)
  867. {
  868. foreach (var selectedEntry in selectedKeyframes)
  869. {
  870. EdAnimationCurve curve = curveInfos[selectedEntry.curveIdx].curve;
  871. // Sort keys from highest to lowest so the indices don't change
  872. selectedEntry.keyIndices.Sort((x, y) =>
  873. {
  874. return y.CompareTo(x);
  875. });
  876. foreach (var keyframeIdx in selectedEntry.keyIndices)
  877. curve.RemoveKeyframe(keyframeIdx);
  878. curve.Apply();
  879. }
  880. }
  881. else
  882. ShowReadOnlyMessage();
  883. ClearSelection();
  884. OnCurveModified?.Invoke();
  885. guiCurveDrawing.Rebuild();
  886. UpdateEventsGUI();
  887. }
  888. /// <summary>
  889. /// Deletes all currently selected events.
  890. /// </summary>
  891. private void DeleteSelectedEvents()
  892. {
  893. List<EventInfo> newEvents = new List<EventInfo>();
  894. foreach (var entry in events)
  895. {
  896. if(!entry.selected)
  897. newEvents.Add(entry);
  898. }
  899. events = newEvents;
  900. OnEventDeleted?.Invoke();
  901. ClearSelection();
  902. guiCurveDrawing.Rebuild();
  903. UpdateEventsGUI();
  904. }
  905. /// <summary>
  906. /// Unselects any selected keyframes and events.
  907. /// </summary>
  908. private void ClearSelection()
  909. {
  910. guiCurveDrawing.ClearSelectedKeyframes();
  911. selectedKeyframes.Clear();
  912. foreach (var entry in events)
  913. entry.selected = false;
  914. }
  915. /// <summary>
  916. /// Adds the provided keyframe to the selection list (doesn't clear existing ones).
  917. /// </summary>
  918. /// <param name="keyframeRef">Keyframe to select.</param>
  919. private void SelectKeyframe(KeyframeRef keyframeRef)
  920. {
  921. guiCurveDrawing.SelectKeyframe(keyframeRef, true);
  922. if (!IsSelected(keyframeRef))
  923. {
  924. int curveIdx = selectedKeyframes.FindIndex(x =>
  925. {
  926. return x.curveIdx == keyframeRef.curveIdx;
  927. });
  928. if (curveIdx == -1)
  929. {
  930. curveIdx = selectedKeyframes.Count;
  931. SelectedKeyframes newKeyframes = new SelectedKeyframes();
  932. newKeyframes.curveIdx = keyframeRef.curveIdx;
  933. selectedKeyframes.Add(newKeyframes);
  934. }
  935. selectedKeyframes[curveIdx].keyIndices.Add(keyframeRef.keyIdx);
  936. }
  937. }
  938. /// <summary>
  939. /// Checks is the provided keyframe currently selected.
  940. /// </summary>
  941. /// <param name="keyframeRef">Keyframe to check.</param>
  942. /// <returns>True if selected, false otherwise.</returns>
  943. private bool IsSelected(KeyframeRef keyframeRef)
  944. {
  945. int curveIdx = selectedKeyframes.FindIndex(x =>
  946. {
  947. return x.curveIdx == keyframeRef.curveIdx;
  948. });
  949. if (curveIdx == -1)
  950. return false;
  951. int keyIdx = selectedKeyframes[curveIdx].keyIndices.FindIndex(x =>
  952. {
  953. return x == keyframeRef.keyIdx;
  954. });
  955. return keyIdx != -1;
  956. }
  957. /// <summary>
  958. /// Opens the edit window for the currently selected keyframe.
  959. /// </summary>
  960. private void EditSelectedKeyframe()
  961. {
  962. if (disableCurveEdit)
  963. {
  964. ShowReadOnlyMessage();
  965. return;
  966. }
  967. if (selectedKeyframes.Count == 0)
  968. return;
  969. EdAnimationCurve curve = curveInfos[selectedKeyframes[0].curveIdx].curve;
  970. KeyFrame[] keyFrames = curve.KeyFrames;
  971. int keyIndex = selectedKeyframes[0].keyIndices[0];
  972. KeyFrame keyFrame = keyFrames[keyIndex];
  973. Vector2I position = guiCurveDrawing.CurveToPixelSpace(new Vector2(keyFrame.time, keyFrame.value));
  974. Rect2I drawingBounds = GUIUtility.CalculateBounds(drawingPanel, window.GUI);
  975. position.x = MathEx.Clamp(position.x, 0, drawingBounds.width);
  976. position.y = MathEx.Clamp(position.y, 0, drawingBounds.height);
  977. Vector2I windowPos = position + new Vector2I(drawingBounds.x, drawingBounds.y);
  978. KeyframeEditWindow editWindow = DropDownWindow.Open<KeyframeEditWindow>(window, windowPos);
  979. editWindow.Initialize(keyFrame, x =>
  980. {
  981. curve.UpdateKeyframe(keyIndex, x.time, x.value);
  982. curve.Apply();
  983. guiCurveDrawing.Rebuild();
  984. },
  985. x =>
  986. {
  987. if (x)
  988. OnCurveModified?.Invoke();
  989. });
  990. }
  991. /// <summary>
  992. /// Opens the edit window for the currently selected event.
  993. /// </summary>
  994. private void EditSelectedEvent()
  995. {
  996. if (!showEvents)
  997. return;
  998. for (int i = 0; i < events.Count; i++)
  999. {
  1000. if (events[i].selected)
  1001. {
  1002. StartEventEdit(i);
  1003. break;
  1004. }
  1005. }
  1006. }
  1007. /// <summary>
  1008. /// Opens the event edit window for the specified event.
  1009. /// </summary>
  1010. /// <param name="eventIdx">Event index to open the edit window for.</param>
  1011. private void StartEventEdit(int eventIdx)
  1012. {
  1013. AnimationEvent animEvent = events[eventIdx].animEvent;
  1014. Vector2I position = new Vector2I();
  1015. position.x = guiEvents.GetOffset(animEvent.time);
  1016. position.y = EVENTS_HEIGHT/2;
  1017. Rect2I eventBounds = GUIUtility.CalculateBounds(eventsPanel, window.GUI);
  1018. Vector2I windowPos = position + new Vector2I(eventBounds.x, eventBounds.y);
  1019. if (eventsSO == null)
  1020. return;
  1021. Component[] components = eventsSO.GetComponents();
  1022. string[] componentNames = new string[components.Length];
  1023. for (int i = 0; i < components.Length; i++)
  1024. componentNames[i] = components[i].GetType().Name;
  1025. EventEditWindow editWindow = DropDownWindow.Open<EventEditWindow>(window, windowPos);
  1026. editWindow.Initialize(animEvent, componentNames, () =>
  1027. {
  1028. UpdateEventsGUI();
  1029. },
  1030. x =>
  1031. {
  1032. if(x)
  1033. OnEventModified?.Invoke();
  1034. });
  1035. }
  1036. /// <summary>
  1037. /// Shows a dialog box that notifies the user that the animation clip is read only.
  1038. /// </summary>
  1039. private void ShowReadOnlyMessage()
  1040. {
  1041. LocEdString title = new LocEdString("Warning");
  1042. LocEdString message =
  1043. new LocEdString("You cannot edit keyframes on animation clips that" +
  1044. " are imported from an external file.");
  1045. DialogBox.Open(title, message, DialogBox.Type.OK);
  1046. }
  1047. }
  1048. /// <summary>
  1049. /// Drop down window that displays input boxes used for editing a keyframe.
  1050. /// </summary>
  1051. [DefaultSize(120, 80)]
  1052. internal class KeyframeEditWindow : DropDownWindow
  1053. {
  1054. private Action<bool> closeCallback;
  1055. private bool changesMade;
  1056. /// <summary>
  1057. /// Initializes the drop down window by creating the necessary GUI. Must be called after construction and before
  1058. /// use.
  1059. /// </summary>
  1060. /// <param name="keyFrame">Keyframe whose properties to edit.</param>
  1061. /// <param name="updateCallback">Callback triggered when event values change.</param>
  1062. /// <param name="closeCallback">Callback triggered just before the window closes.</param>
  1063. internal void Initialize(KeyFrame keyFrame, Action<KeyFrame> updateCallback, Action<bool> closeCallback)
  1064. {
  1065. GUIFloatField timeField = new GUIFloatField(new LocEdString("Time"), 40, "");
  1066. timeField.Value = keyFrame.time;
  1067. timeField.OnChanged += x => { keyFrame.time = x; changesMade = true; updateCallback(keyFrame); };
  1068. GUIFloatField valueField = new GUIFloatField(new LocEdString("Value"), 40, "");
  1069. valueField.Value = keyFrame.value;
  1070. valueField.OnChanged += x => { keyFrame.value = x; changesMade = true; updateCallback(keyFrame); };
  1071. GUILayoutY vertLayout = GUI.AddLayoutY();
  1072. vertLayout.AddFlexibleSpace();
  1073. GUILayoutX horzLayout = vertLayout.AddLayoutX();
  1074. horzLayout.AddFlexibleSpace();
  1075. GUILayout contentLayout = horzLayout.AddLayoutY();
  1076. GUILayout timeLayout = contentLayout.AddLayoutX();
  1077. timeLayout.AddSpace(5);
  1078. timeLayout.AddElement(timeField);
  1079. timeLayout.AddFlexibleSpace();
  1080. GUILayout componentLayout = contentLayout.AddLayoutX();
  1081. componentLayout.AddSpace(5);
  1082. componentLayout.AddElement(valueField);
  1083. componentLayout.AddFlexibleSpace();
  1084. horzLayout.AddFlexibleSpace();
  1085. vertLayout.AddFlexibleSpace();
  1086. this.closeCallback = closeCallback;
  1087. }
  1088. private void OnDestroy()
  1089. {
  1090. closeCallback?.Invoke(changesMade);
  1091. }
  1092. }
  1093. /// <summary>
  1094. /// Drop down window that displays input boxes used for editing an event.
  1095. /// </summary>
  1096. [DefaultSize(200, 80)]
  1097. internal class EventEditWindow : DropDownWindow
  1098. {
  1099. private Action<bool> closeCallback;
  1100. private bool changesMade;
  1101. /// <summary>
  1102. /// Initializes the drop down window by creating the necessary GUI. Must be called after construction and before
  1103. /// use.
  1104. /// </summary>
  1105. /// <param name="animEvent">Event whose properties to edit.</param>
  1106. /// <param name="componentNames">List of component names that the user can select from.</param>
  1107. /// <param name="updateCallback">Callback triggered when event values change.</param>
  1108. /// <param name="closeCallback">Callback triggered just before the window closes.</param>
  1109. internal void Initialize(AnimationEvent animEvent, string[] componentNames, Action updateCallback,
  1110. Action<bool> closeCallback)
  1111. {
  1112. int selectedIndex = -1;
  1113. string methodName = "";
  1114. if (!string.IsNullOrEmpty(animEvent.name))
  1115. {
  1116. string[] nameEntries = animEvent.name.Split('/');
  1117. if (nameEntries.Length > 1)
  1118. {
  1119. string typeName = nameEntries[0];
  1120. for (int i = 0; i < componentNames.Length; i++)
  1121. {
  1122. if (componentNames[i] == typeName)
  1123. {
  1124. selectedIndex = i;
  1125. break;
  1126. }
  1127. }
  1128. methodName = nameEntries[nameEntries.Length - 1];
  1129. }
  1130. }
  1131. GUIFloatField timeField = new GUIFloatField(new LocEdString("Time"), 40, "");
  1132. timeField.Value = animEvent.time;
  1133. timeField.OnChanged += x => { animEvent.time = x; changesMade = true; updateCallback(); };
  1134. GUIListBoxField componentField = new GUIListBoxField(componentNames, new LocEdString("Component"), 40);
  1135. if (selectedIndex != -1)
  1136. componentField.Index = selectedIndex;
  1137. componentField.OnSelectionChanged += x =>
  1138. {
  1139. string compName = "";
  1140. if (x != -1)
  1141. compName = componentNames[x] + "/";
  1142. animEvent.name = compName + x;
  1143. changesMade = true;
  1144. updateCallback();
  1145. };
  1146. GUITextField methodField = new GUITextField(new LocEdString("Method"), 40, false, "", GUIOption.FixedWidth(190));
  1147. methodField.Value = methodName;
  1148. methodField.OnChanged += x =>
  1149. {
  1150. string compName = "";
  1151. if(componentField.Index != -1)
  1152. compName = componentNames[componentField.Index] + "/";
  1153. animEvent.name = compName + x;
  1154. changesMade = true;
  1155. updateCallback();
  1156. };
  1157. GUILayoutY vertLayout = GUI.AddLayoutY();
  1158. vertLayout.AddFlexibleSpace();
  1159. GUILayoutX horzLayout = vertLayout.AddLayoutX();
  1160. horzLayout.AddFlexibleSpace();
  1161. GUILayout contentLayout = horzLayout.AddLayoutY();
  1162. GUILayout timeLayout = contentLayout.AddLayoutX();
  1163. timeLayout.AddSpace(5);
  1164. timeLayout.AddElement(timeField);
  1165. timeLayout.AddFlexibleSpace();
  1166. GUILayout componentLayout = contentLayout.AddLayoutX();
  1167. componentLayout.AddSpace(5);
  1168. componentLayout.AddElement(componentField);
  1169. componentLayout.AddFlexibleSpace();
  1170. GUILayout methodLayout = contentLayout.AddLayoutX();
  1171. methodLayout.AddSpace(5);
  1172. methodLayout.AddElement(methodField);
  1173. methodLayout.AddFlexibleSpace();
  1174. horzLayout.AddFlexibleSpace();
  1175. vertLayout.AddFlexibleSpace();
  1176. this.closeCallback = closeCallback;
  1177. }
  1178. private void OnDestroy()
  1179. {
  1180. closeCallback?.Invoke(changesMade);
  1181. }
  1182. }
  1183. /** @} */
  1184. }