GUIAnimFieldDisplay.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679
  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 System.Text;
  6. using BansheeEngine;
  7. namespace BansheeEditor
  8. {
  9. /** @addtogroup AnimationEditor
  10. * @{
  11. */
  12. internal class GUIAnimFieldDisplay
  13. {
  14. private SceneObject root;
  15. private int width;
  16. private int height;
  17. private GUIScrollArea scrollArea;
  18. private List<string> paths = new List<string>();
  19. private GUIAnimFieldEntry[] fields;
  20. private GUIAnimFieldLayouts layouts;
  21. public GUIAnimFieldDisplay(GUILayout layout, int width, int height, SceneObject root)
  22. {
  23. this.root = root;
  24. scrollArea = new GUIScrollArea(ScrollBarType.ShowIfDoesntFit, ScrollBarType.NeverShow);
  25. layout.AddElement(scrollArea);
  26. SetSize(width, height);
  27. }
  28. public Action<string> OnEntrySelected;
  29. public void SetSize(int width, int height)
  30. {
  31. this.width = width;
  32. this.height = height;
  33. scrollArea.SetWidth(width);
  34. scrollArea.SetHeight(height);
  35. Rebuild();
  36. }
  37. public void SetFields(string[] paths)
  38. {
  39. this.paths.Clear();
  40. this.paths.AddRange(paths);
  41. Rebuild();
  42. }
  43. public void AddField(string path)
  44. {
  45. if (!paths.Contains(path))
  46. {
  47. paths.Add(path);
  48. Rebuild();
  49. }
  50. }
  51. public void SetDisplayValues(GUIAnimFieldPathValue[] values)
  52. {
  53. for (int i = 0; i < fields.Length; i++)
  54. {
  55. string path = fields[i].Path;
  56. for (int j = 0; j < values.Length; j++)
  57. {
  58. if(path == values[j].path)
  59. fields[i].SetValue(values[j].value);
  60. }
  61. }
  62. }
  63. public void SetSelection(string[] paths)
  64. {
  65. Action<GUIAnimFieldEntry> updateSelection = field =>
  66. {
  67. bool foundSelected = false;
  68. for (int j = 0; j < paths.Length; j++)
  69. {
  70. if (field.Path == paths[j])
  71. {
  72. field.SetSelection(true);
  73. foundSelected = true;
  74. break;
  75. }
  76. }
  77. if (!foundSelected)
  78. field.SetSelection(false);
  79. };
  80. for (int i = 0; i < fields.Length; i++)
  81. {
  82. updateSelection(fields[i]);
  83. // Check children (only one level allowed)
  84. GUIAnimFieldEntry[] children = fields[i].GetChildren();
  85. if (children == null)
  86. continue;
  87. for (int j = 0; j < children.Length; j++)
  88. updateSelection(children[j]);
  89. }
  90. }
  91. public static SerializableProperty FindProperty(SceneObject root, string path)
  92. {
  93. if (string.IsNullOrEmpty(path) || root == null)
  94. return null;
  95. string trimmedPath = path.Trim('/');
  96. string[] entries = trimmedPath.Split('/');
  97. // Find scene object referenced by the path
  98. SceneObject so = null;
  99. int pathIdx = 0;
  100. for (; pathIdx < entries.Length; pathIdx++)
  101. {
  102. string entry = entries[pathIdx];
  103. if (string.IsNullOrEmpty(entry))
  104. continue;
  105. // Not a scene object, break
  106. if (entry[0] != '!')
  107. break;
  108. if (so == null)
  109. so = root;
  110. else
  111. {
  112. string childName = entry.Substring(1, entry.Length - 1);
  113. so = so.FindChild(childName);
  114. if (so == null)
  115. break;
  116. }
  117. }
  118. // Cannot find scene object with specified hierarchy & name
  119. if (so == null)
  120. return null;
  121. if (pathIdx >= entries.Length)
  122. return null;
  123. // If path is referencing a component, find it
  124. Component component = null;
  125. {
  126. string entry = entries[pathIdx];
  127. if (entry[0] == ':')
  128. {
  129. string componentName = entry.Substring(1, entry.Length - 1);
  130. Component[] components = so.GetComponents();
  131. component = Array.Find(components, x => x.GetType().Name == componentName);
  132. // Cannot find component with specified type
  133. if (component == null)
  134. return null;
  135. }
  136. }
  137. // Look for a field within a component
  138. if (component != null)
  139. {
  140. pathIdx++;
  141. if (pathIdx >= entries.Length)
  142. return null;
  143. SerializableObject componentObj = new SerializableObject(component);
  144. StringBuilder pathBuilder = new StringBuilder();
  145. for(; pathIdx < entries.Length; pathIdx++)
  146. pathBuilder.Append(entries[pathIdx] + "/");
  147. return componentObj.FindProperty(pathBuilder.ToString());
  148. }
  149. else // Field is one of the builtin ones on the SceneObject itself
  150. {
  151. if ((pathIdx + 1) < entries.Length)
  152. return null;
  153. string entry = entries[pathIdx];
  154. if (entry == "Position")
  155. {
  156. SerializableProperty property = new SerializableProperty(
  157. SerializableProperty.FieldType.Vector3,
  158. typeof(Vector3),
  159. () => so.LocalPosition,
  160. (x) => so.LocalPosition = (Vector3)x);
  161. return property;
  162. }
  163. else if (entry == "Rotation")
  164. {
  165. SerializableProperty property = new SerializableProperty(
  166. SerializableProperty.FieldType.Vector3,
  167. typeof(Vector3),
  168. () => so.LocalRotation.ToEuler(),
  169. (x) => so.LocalRotation = Quaternion.FromEuler((Vector3)x));
  170. return property;
  171. }
  172. else if (entry == "Scale")
  173. {
  174. SerializableProperty property = new SerializableProperty(
  175. SerializableProperty.FieldType.Vector3,
  176. typeof(Vector3),
  177. () => so.LocalScale,
  178. (x) => so.LocalScale = (Vector3)x);
  179. return property;
  180. }
  181. return null;
  182. }
  183. }
  184. private void Rebuild()
  185. {
  186. scrollArea.Layout.Clear();
  187. fields = null;
  188. if (paths == null || root == null)
  189. return;
  190. layouts = new GUIAnimFieldLayouts();
  191. GUIPanel rootPanel = scrollArea.Layout.AddPanel();
  192. GUIPanel mainPanel = rootPanel.AddPanel();
  193. GUIPanel underlayPanel = rootPanel.AddPanel(1);
  194. GUIPanel overlayPanel = rootPanel.AddPanel(-1);
  195. GUIPanel backgroundPanel = rootPanel.AddPanel(2);
  196. layouts.main = mainPanel.AddLayoutY();
  197. layouts.underlay = underlayPanel.AddLayoutY();
  198. layouts.overlay = overlayPanel.AddLayoutY();
  199. layouts.background = backgroundPanel.AddLayoutY();
  200. GUIButton catchAll = new GUIButton("", EditorStyles.Blank);
  201. catchAll.Bounds = new Rect2I(0, 0, width, height);
  202. catchAll.OnClick += () => OnEntrySelected(null);
  203. underlayPanel.AddElement(catchAll);
  204. layouts.main.AddSpace(5);
  205. layouts.underlay.AddSpace(5);
  206. layouts.overlay.AddSpace(5);
  207. layouts.background.AddSpace(5);
  208. fields = new GUIAnimFieldEntry[paths.Count];
  209. for (int i = 0; i < paths.Count; i++)
  210. {
  211. SerializableProperty property = FindProperty(root, paths[i]);
  212. if (property != null)
  213. {
  214. switch (property.Type)
  215. {
  216. case SerializableProperty.FieldType.Vector2:
  217. fields[i] = new GUIAnimVec2Entry(layouts, paths[i]);
  218. break;
  219. case SerializableProperty.FieldType.Vector3:
  220. fields[i] = new GUIAnimVec3Entry(layouts, paths[i]);
  221. break;
  222. case SerializableProperty.FieldType.Vector4:
  223. fields[i] = new GUIAnimVec4Entry(layouts, paths[i]);
  224. break;
  225. case SerializableProperty.FieldType.Color:
  226. fields[i] = new GUIAnimColorEntry(layouts, paths[i]);
  227. break;
  228. case SerializableProperty.FieldType.Bool:
  229. case SerializableProperty.FieldType.Int:
  230. case SerializableProperty.FieldType.Float:
  231. fields[i] = new GUIAnimSimpleEntry(layouts, paths[i]);
  232. break;
  233. }
  234. }
  235. else
  236. {
  237. fields[i] = new GUIAnimMissingEntry(layouts, paths[i]);
  238. }
  239. if (fields[i] != null)
  240. fields[i].OnEntrySelected += OnEntrySelected;
  241. }
  242. layouts.main.AddSpace(5);
  243. layouts.underlay.AddSpace(5);
  244. layouts.overlay.AddSpace(5);
  245. layouts.background.AddSpace(5);
  246. layouts.main.AddFlexibleSpace();
  247. layouts.underlay.AddFlexibleSpace();
  248. layouts.overlay.AddFlexibleSpace();
  249. layouts.background.AddFlexibleSpace();
  250. }
  251. }
  252. internal class GUIAnimFieldLayouts
  253. {
  254. public GUILayout main;
  255. public GUILayout underlay;
  256. public GUILayout overlay;
  257. public GUILayout background;
  258. }
  259. internal struct GUIAnimFieldPathValue
  260. {
  261. public string path;
  262. public object value;
  263. }
  264. internal abstract class GUIAnimFieldEntry
  265. {
  266. private const int MAX_PATH_LENGTH = 20;
  267. protected const int INDENT_AMOUNT = 10;
  268. protected string path;
  269. private GUIButton selectionBtn;
  270. private GUITexture backgroundTexture;
  271. private int entryHeight;
  272. public Action<string> OnEntrySelected;
  273. public string Path { get { return path; } }
  274. public GUIAnimFieldEntry(GUIAnimFieldLayouts layouts, string path, bool shortName)
  275. {
  276. this.path = path;
  277. GUILayoutX toggleLayout = layouts.main.AddLayoutX();
  278. toggleLayout.AddSpace(15);
  279. selectionBtn = new GUIButton(GetDisplayName(path, shortName), EditorStyles.Label, GUIOption.FlexibleWidth());
  280. selectionBtn.OnClick += () =>
  281. {
  282. OnEntrySelected?.Invoke(path);
  283. };
  284. toggleLayout.AddElement(selectionBtn);
  285. entryHeight = selectionBtn.Bounds.height;
  286. backgroundTexture = new GUITexture(Builtin.WhiteTexture, GUITextureScaleMode.StretchToFit,
  287. GUIOption.FlexibleWidth());
  288. backgroundTexture.SetTint(Color.Transparent);
  289. backgroundTexture.SetHeight(entryHeight);
  290. layouts.background.AddElement(backgroundTexture);
  291. }
  292. public virtual void Toggle(bool on)
  293. {
  294. selectionBtn.Active = on;
  295. backgroundTexture.Active = on;
  296. }
  297. public void SetSelection(bool selected)
  298. {
  299. if(selected)
  300. backgroundTexture.SetTint(Color.DarkCyan);
  301. else
  302. backgroundTexture.SetTint(Color.Transparent);
  303. }
  304. public virtual void SetValue(object value) { }
  305. public virtual GUIAnimFieldEntry[] GetChildren()
  306. {
  307. return null;
  308. }
  309. protected int GetEntryHeight()
  310. {
  311. return entryHeight;
  312. }
  313. protected static string GetDisplayName(string path, bool shortName)
  314. {
  315. if (string.IsNullOrEmpty(path))
  316. return "";
  317. string soName;
  318. string compName;
  319. string propertyPath;
  320. string trimmedPath = path.Trim('/');
  321. GetNames(trimmedPath, shortName, out soName, out compName, out propertyPath);
  322. if (soName == null || propertyPath == null)
  323. return "";
  324. if (shortName)
  325. return propertyPath;
  326. else
  327. {
  328. string truncatedPropPath;
  329. if (propertyPath.Length > MAX_PATH_LENGTH)
  330. truncatedPropPath = "..." + propertyPath.Substring(propertyPath.Length - MAX_PATH_LENGTH);
  331. else
  332. truncatedPropPath = propertyPath;
  333. if (compName != null)
  334. return soName + "(" + compName + ") - " + truncatedPropPath;
  335. else
  336. return soName + " - " + truncatedPropPath;
  337. }
  338. }
  339. protected static void GetNames(string path, bool shortName, out string soName, out string compName, out string propertyPath)
  340. {
  341. string[] entries = path.Split('/');
  342. // Find name of the last scene object in the path
  343. int pathIdx = 0;
  344. for (; pathIdx < entries.Length; pathIdx++)
  345. {
  346. string entry = entries[pathIdx];
  347. if (string.IsNullOrEmpty(entry))
  348. continue;
  349. // Not a scene object, break
  350. if (entry[0] != '!')
  351. break;
  352. }
  353. if (pathIdx == 0)
  354. {
  355. soName = null;
  356. compName = null;
  357. propertyPath = null;
  358. return;
  359. }
  360. soName = entries[pathIdx - 1].Substring(1, entries[pathIdx - 1].Length - 1);
  361. if (pathIdx >= entries.Length)
  362. {
  363. compName = null;
  364. propertyPath = null;
  365. return;
  366. }
  367. // If path is referencing a component, find it
  368. {
  369. string entry = entries[pathIdx];
  370. if (entry[0] == ':')
  371. compName = entry.Substring(1, entry.Length - 1);
  372. else
  373. compName = null;
  374. }
  375. // Look for a field name
  376. if (compName != null)
  377. {
  378. pathIdx++;
  379. if (pathIdx >= entries.Length)
  380. {
  381. propertyPath = null;
  382. return;
  383. }
  384. }
  385. if (shortName)
  386. {
  387. if (pathIdx < entries.Length)
  388. propertyPath = entries[entries.Length - 1];
  389. else
  390. propertyPath = null;
  391. }
  392. else
  393. {
  394. StringBuilder pathBuilder = new StringBuilder();
  395. for (; pathIdx < entries.Length - 1; pathIdx++)
  396. pathBuilder.Append(entries[pathIdx] + "/");
  397. if (pathIdx < entries.Length)
  398. pathBuilder.Append(entries[pathIdx]);
  399. propertyPath = pathBuilder.ToString();
  400. }
  401. }
  402. }
  403. internal class GUIAnimSimpleEntry : GUIAnimFieldEntry
  404. {
  405. private GUILabel valueDisplay;
  406. private GUILayoutX underlayLayout;
  407. private GUILabel overlaySpacing;
  408. public GUIAnimSimpleEntry(GUIAnimFieldLayouts layouts, string path, bool child = false)
  409. : base(layouts, path, child)
  410. {
  411. valueDisplay = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
  412. underlayLayout = layouts.underlay.AddLayoutX();
  413. underlayLayout.AddFlexibleSpace();
  414. underlayLayout.AddElement(valueDisplay);
  415. underlayLayout.AddSpace(50);
  416. overlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
  417. layouts.overlay.AddElement(overlaySpacing);
  418. }
  419. public override void Toggle(bool on)
  420. {
  421. underlayLayout.Active = on;
  422. overlaySpacing.Active = on;
  423. base.Toggle(on);
  424. }
  425. public override void SetValue(object value)
  426. {
  427. if (value == null)
  428. return;
  429. string strValue = value.ToString();
  430. valueDisplay.SetContent(strValue);
  431. }
  432. }
  433. internal class GUIAnimComplexEntry : GUIAnimFieldEntry
  434. {
  435. private GUILayout foldoutLayout;
  436. private GUIToggle foldout;
  437. private GUILabel underlaySpacing;
  438. protected GUIAnimSimpleEntry[] children;
  439. public GUIAnimComplexEntry(GUIAnimFieldLayouts layouts, string path, string[] childEntries)
  440. : base(layouts, path, false)
  441. {
  442. foldout = new GUIToggle("", EditorStyles.Expand);
  443. foldout.OnToggled += Toggle;
  444. foldoutLayout = layouts.overlay.AddLayoutX();
  445. foldoutLayout.AddElement(foldout);
  446. foldoutLayout.AddFlexibleSpace();
  447. underlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
  448. layouts.underlay.AddElement(underlaySpacing);
  449. children = new GUIAnimSimpleEntry[childEntries.Length];
  450. for (int i = 0; i < childEntries.Length; i++)
  451. {
  452. children[i] = new GUIAnimSimpleEntry(layouts, path + childEntries[i], true);
  453. children[i].OnEntrySelected += x => { OnEntrySelected?.Invoke(x); };
  454. }
  455. Toggle(false);
  456. }
  457. public override void Toggle(bool on)
  458. {
  459. foreach(var child in children)
  460. child.Toggle(on);
  461. }
  462. public override GUIAnimFieldEntry[] GetChildren()
  463. {
  464. return children;
  465. }
  466. }
  467. internal class GUIAnimVec2Entry : GUIAnimComplexEntry
  468. {
  469. public GUIAnimVec2Entry(GUIAnimFieldLayouts layouts, string path)
  470. : base(layouts, path, new[] { ".x", ".y" })
  471. { }
  472. public override void SetValue(object value)
  473. {
  474. if (value == null)
  475. return;
  476. Vector2 vector = (Vector2)value;
  477. children[0].SetValue(vector.x);
  478. children[1].SetValue(vector.y);
  479. }
  480. }
  481. internal class GUIAnimVec3Entry : GUIAnimComplexEntry
  482. {
  483. public GUIAnimVec3Entry(GUIAnimFieldLayouts layouts, string path)
  484. : base(layouts, path, new[] { ".x", ".y", ".z" })
  485. { }
  486. public override void SetValue(object value)
  487. {
  488. if (value == null)
  489. return;
  490. Vector3 vector = (Vector3)value;
  491. children[0].SetValue(vector.x);
  492. children[1].SetValue(vector.y);
  493. children[2].SetValue(vector.z);
  494. }
  495. }
  496. internal class GUIAnimVec4Entry : GUIAnimComplexEntry
  497. {
  498. public GUIAnimVec4Entry(GUIAnimFieldLayouts layouts, string path)
  499. : base(layouts, path, new[] { ".x", ".y", ".z", ".w" })
  500. { }
  501. public override void SetValue(object value)
  502. {
  503. if (value == null)
  504. return;
  505. Vector4 vector = (Vector4)value;
  506. children[0].SetValue(vector.x);
  507. children[1].SetValue(vector.y);
  508. children[2].SetValue(vector.z);
  509. children[3].SetValue(vector.w);
  510. }
  511. }
  512. internal class GUIAnimColorEntry : GUIAnimComplexEntry
  513. {
  514. public GUIAnimColorEntry(GUIAnimFieldLayouts layouts, string path)
  515. : base(layouts, path, new[] { ".r", ".g", ".b", ".a" })
  516. { }
  517. public override void SetValue(object value)
  518. {
  519. if (value == null)
  520. return;
  521. Color color = (Color)value;
  522. children[0].SetValue(color.r);
  523. children[1].SetValue(color.g);
  524. children[2].SetValue(color.b);
  525. children[3].SetValue(color.a);
  526. }
  527. }
  528. internal class GUIAnimMissingEntry : GUIAnimFieldEntry
  529. {
  530. private GUILabel missingLabel;
  531. private GUILayoutX underlayLayout;
  532. private GUILabel overlaySpacing;
  533. public GUIAnimMissingEntry(GUIAnimFieldLayouts layouts, string path)
  534. : base(layouts, path, false)
  535. {
  536. missingLabel = new GUILabel("Missing property!", GUIOption.FixedHeight(GetEntryHeight()));
  537. underlayLayout = layouts.underlay.AddLayoutX();
  538. underlayLayout.AddFlexibleSpace();
  539. underlayLayout.AddElement(missingLabel);
  540. underlayLayout.AddSpace(50);
  541. overlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
  542. layouts.overlay.AddElement(overlaySpacing);
  543. // TODO - Alternating backgrounds
  544. }
  545. public override void Toggle(bool on)
  546. {
  547. underlayLayout.Active = on;
  548. overlaySpacing.Active = on;
  549. base.Toggle(on);
  550. }
  551. }
  552. /** @} */
  553. }