GUICurveEditor.cs 49 KB

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