GUICurveEditor.cs 48 KB

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