GUIAnimFieldDisplay.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  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();
  25. layout.AddElement(scrollArea);
  26. SetSize(width, height);
  27. }
  28. public Action<string, bool> OnSelectionChanged;
  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. private SerializableProperty FindProperty(string path)
  64. {
  65. if (string.IsNullOrEmpty(path) || root == null)
  66. return null;
  67. string[] entries = path.Split('/');
  68. // Find scene object referenced by the path
  69. SceneObject so = null;
  70. int pathIdx = 0;
  71. for (; pathIdx < entries.Length; pathIdx++)
  72. {
  73. string entry = entries[pathIdx];
  74. if (string.IsNullOrEmpty(entry))
  75. continue;
  76. // Not a scene object, break
  77. if (entry[0] != '!')
  78. break;
  79. if (so == null)
  80. so = root;
  81. else
  82. {
  83. string childName = entry.Substring(1, entry.Length - 1);
  84. so = so.FindChild(childName);
  85. if (so == null)
  86. break;
  87. }
  88. }
  89. // Cannot find scene object with specified hierarchy & name
  90. if (so == null)
  91. return null;
  92. if (pathIdx >= entries.Length)
  93. return null;
  94. // If path is referencing a component, find it
  95. Component component = null;
  96. {
  97. string entry = entries[pathIdx];
  98. if (entry[0] == ':')
  99. {
  100. string componentName = entry.Substring(1, entry.Length - 1);
  101. Component[] components = so.GetComponents();
  102. component = Array.Find(components, x => x.GetType().Name == componentName);
  103. // Cannot find component with specified type
  104. if (component == null)
  105. return null;
  106. }
  107. }
  108. // Look for a field within a component
  109. if (component != null)
  110. {
  111. pathIdx++;
  112. if (pathIdx >= entries.Length)
  113. return null;
  114. SerializableObject componentObj = new SerializableObject(component);
  115. StringBuilder pathBuilder = new StringBuilder();
  116. for(; pathIdx < entries.Length; pathIdx++)
  117. pathBuilder.Append(entries[pathIdx] + "/");
  118. return componentObj.FindProperty(pathBuilder.ToString());
  119. }
  120. else // Field is one of the builtin ones on the SceneObject itself
  121. {
  122. if ((pathIdx + 1) < entries.Length)
  123. return null;
  124. string entry = entries[pathIdx];
  125. if (entry == "Position")
  126. {
  127. SerializableProperty property = new SerializableProperty(
  128. SerializableProperty.FieldType.Vector3,
  129. typeof(Vector3),
  130. () => so.LocalPosition,
  131. (x) => so.LocalPosition = (Vector3)x);
  132. return property;
  133. }
  134. else if (entry == "Rotation")
  135. {
  136. SerializableProperty property = new SerializableProperty(
  137. SerializableProperty.FieldType.Vector3,
  138. typeof(Vector3),
  139. () => so.LocalRotation.ToEuler(),
  140. (x) => so.LocalRotation = Quaternion.FromEuler((Vector3)x));
  141. return property;
  142. }
  143. else if (entry == "Scale")
  144. {
  145. SerializableProperty property = new SerializableProperty(
  146. SerializableProperty.FieldType.Vector3,
  147. typeof(Vector3),
  148. () => so.LocalScale,
  149. (x) => so.LocalScale = (Vector3)x);
  150. return property;
  151. }
  152. return null;
  153. }
  154. }
  155. private void Rebuild()
  156. {
  157. scrollArea.Layout.Clear();
  158. fields = null;
  159. if (paths == null || root == null)
  160. return;
  161. layouts = new GUIAnimFieldLayouts();
  162. GUIPanel rootPanel = scrollArea.Layout.AddPanel();
  163. GUIPanel mainPanel = rootPanel.AddPanel();
  164. GUIPanel underlayPanel = rootPanel.AddPanel(1);
  165. GUIPanel overlayPanel = rootPanel.AddPanel(-1);
  166. layouts.main = mainPanel.AddLayoutY();
  167. layouts.underlay = underlayPanel.AddLayoutY();
  168. layouts.overlay = overlayPanel.AddLayoutY();
  169. fields = new GUIAnimFieldEntry[paths.Count];
  170. for (int i = 0; i < paths.Count; i++)
  171. {
  172. SerializableProperty property = FindProperty(paths[i]);
  173. if (property != null)
  174. {
  175. switch (property.Type)
  176. {
  177. case SerializableProperty.FieldType.Vector2:
  178. fields[i] = new GUIAnimVec2Entry(layouts, paths[i]);
  179. break;
  180. case SerializableProperty.FieldType.Vector3:
  181. fields[i] = new GUIAnimVec3Entry(layouts, paths[i]);
  182. break;
  183. case SerializableProperty.FieldType.Vector4:
  184. fields[i] = new GUIAnimVec4Entry(layouts, paths[i]);
  185. break;
  186. case SerializableProperty.FieldType.Color:
  187. fields[i] = new GUIAnimColorEntry(layouts, paths[i]);
  188. break;
  189. case SerializableProperty.FieldType.Bool:
  190. case SerializableProperty.FieldType.Int:
  191. case SerializableProperty.FieldType.Float:
  192. fields[i] = new GUIAnimSimpleEntry(layouts, paths[i]);
  193. break;
  194. }
  195. if (fields[i] != null)
  196. fields[i].OnSelectionChanged += OnSelectionChanged;
  197. }
  198. else
  199. {
  200. fields[i] = new GUIAnimMissingEntry(layouts, paths[i]);
  201. }
  202. }
  203. layouts.main.AddFlexibleSpace();
  204. layouts.underlay.AddFlexibleSpace();
  205. layouts.overlay.AddFlexibleSpace();
  206. }
  207. }
  208. internal class GUIAnimFieldLayouts
  209. {
  210. public GUILayout main;
  211. public GUILayout underlay;
  212. public GUILayout overlay;
  213. }
  214. internal struct GUIAnimFieldPathValue
  215. {
  216. public string path;
  217. public object value;
  218. }
  219. internal abstract class GUIAnimFieldEntry
  220. {
  221. private const int MAX_PATH_LENGTH = 20;
  222. protected const int INDENT_AMOUNT = 10;
  223. protected string path;
  224. private GUIToggle toggle;
  225. private int entryHeight;
  226. public Action<string, bool> OnSelectionChanged;
  227. public string Path { get { return path; } }
  228. public GUIAnimFieldEntry(GUIAnimFieldLayouts layouts, string path)
  229. {
  230. this.path = path;
  231. GUILayoutX toggleLayout = layouts.main.AddLayoutX();
  232. toggleLayout.AddSpace(15);
  233. toggle = new GUIToggle(GetDisplayName(path), EditorStyles.SelectableLabel, GUIOption.FlexibleWidth());
  234. toggle.OnToggled += x => { OnSelectionChanged?.Invoke(path, x); };
  235. toggleLayout.AddElement(toggle);
  236. entryHeight = toggle.Bounds.height;
  237. }
  238. public virtual void Toggle(bool on)
  239. {
  240. toggle.Active = on;
  241. }
  242. public virtual void SetValue(object value) { }
  243. protected int GetEntryHeight()
  244. {
  245. return entryHeight;
  246. }
  247. protected static string GetDisplayName(string path)
  248. {
  249. if (string.IsNullOrEmpty(path))
  250. return "";
  251. string soName;
  252. string compName;
  253. string propertyPath;
  254. GetNames(path, out soName, out compName, out propertyPath);
  255. if (soName == null || propertyPath == null)
  256. return "";
  257. string truncatedPropPath;
  258. if (propertyPath.Length > MAX_PATH_LENGTH)
  259. truncatedPropPath = "..." + propertyPath.Substring(propertyPath.Length - MAX_PATH_LENGTH);
  260. else
  261. truncatedPropPath = propertyPath;
  262. if (compName != null)
  263. return soName + "(" + compName + ")." + truncatedPropPath;
  264. else
  265. return soName + "." + truncatedPropPath;
  266. }
  267. protected static void GetNames(string path, out string soName, out string compName, out string propertyPath)
  268. {
  269. string[] entries = path.Split('/');
  270. // Find name of the last scene object in the path
  271. int pathIdx = 0;
  272. for (; pathIdx < entries.Length; pathIdx++)
  273. {
  274. string entry = entries[pathIdx];
  275. if (string.IsNullOrEmpty(entry))
  276. continue;
  277. // Not a scene object, break
  278. if (entry[0] != '!')
  279. break;
  280. }
  281. if (pathIdx == 0)
  282. {
  283. soName = null;
  284. compName = null;
  285. propertyPath = null;
  286. return;
  287. }
  288. soName = entries[pathIdx - 1].Substring(1, entries[pathIdx - 1].Length - 1);
  289. if (pathIdx >= entries.Length)
  290. {
  291. compName = null;
  292. propertyPath = null;
  293. return;
  294. }
  295. // If path is referencing a component, find it
  296. {
  297. string entry = entries[pathIdx];
  298. if (entry[0] == ':')
  299. compName = entry.Substring(1, entry.Length - 1);
  300. else
  301. compName = null;
  302. }
  303. // Look for a field name
  304. if (compName != null)
  305. {
  306. pathIdx++;
  307. if (pathIdx >= entries.Length)
  308. {
  309. propertyPath = null;
  310. return;
  311. }
  312. }
  313. StringBuilder pathBuilder = new StringBuilder();
  314. for (; pathIdx < entries.Length; pathIdx++)
  315. pathBuilder.Append(entries[pathIdx] + "/");
  316. propertyPath = pathBuilder.ToString();
  317. }
  318. }
  319. internal class GUIAnimSimpleEntry : GUIAnimFieldEntry
  320. {
  321. private GUILabel valueDisplay;
  322. private GUILayoutX underlayLayout;
  323. private GUILabel overlaySpacing;
  324. public GUIAnimSimpleEntry(GUIAnimFieldLayouts layouts, string path)
  325. : base(layouts, path)
  326. {
  327. valueDisplay = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
  328. underlayLayout = layouts.underlay.AddLayoutX();
  329. underlayLayout.AddFlexibleSpace();
  330. underlayLayout.AddElement(valueDisplay);
  331. underlayLayout.AddSpace(50);
  332. overlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
  333. layouts.overlay.AddElement(overlaySpacing);
  334. // TODO - Alternating backgrounds
  335. }
  336. public override void Toggle(bool on)
  337. {
  338. underlayLayout.Active = on;
  339. overlaySpacing.Active = on;
  340. base.Toggle(on);
  341. }
  342. public override void SetValue(object value)
  343. {
  344. if (value == null)
  345. return;
  346. string strValue = value.ToString();
  347. valueDisplay.SetContent(strValue);
  348. }
  349. }
  350. internal class GUIAnimComplexEntry : GUIAnimFieldEntry
  351. {
  352. private GUILayout foldoutLayout;
  353. private GUIToggle foldout;
  354. private GUILabel underlaySpacing;
  355. protected GUIAnimSimpleEntry[] children;
  356. public GUIAnimComplexEntry(GUIAnimFieldLayouts layouts, string path, string[] childEntries)
  357. : base(layouts, path)
  358. {
  359. foldout = new GUIToggle("", EditorStyles.Expand);
  360. foldout.OnToggled += Toggle;
  361. foldoutLayout = layouts.overlay.AddLayoutX();
  362. foldoutLayout.AddElement(foldout);
  363. foldoutLayout.AddFlexibleSpace();
  364. underlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
  365. layouts.underlay.AddElement(underlaySpacing);
  366. children = new GUIAnimSimpleEntry[childEntries.Length];
  367. for (int i = 0; i < childEntries.Length; i++)
  368. children[i] = new GUIAnimSimpleEntry(layouts, path + childEntries[i]);
  369. Toggle(false);
  370. }
  371. public override void Toggle(bool on)
  372. {
  373. foreach(var child in children)
  374. child.Toggle(on);
  375. }
  376. }
  377. internal class GUIAnimVec2Entry : GUIAnimComplexEntry
  378. {
  379. public GUIAnimVec2Entry(GUIAnimFieldLayouts layouts, string path)
  380. : base(layouts, path, new[] { ".x", ".y" })
  381. { }
  382. public override void SetValue(object value)
  383. {
  384. if (value == null)
  385. return;
  386. Vector2 vector = (Vector2)value;
  387. children[0].SetValue(vector.x);
  388. children[1].SetValue(vector.y);
  389. }
  390. }
  391. internal class GUIAnimVec3Entry : GUIAnimComplexEntry
  392. {
  393. public GUIAnimVec3Entry(GUIAnimFieldLayouts layouts, string path)
  394. : base(layouts, path, new[] { ".x", ".y", ".z" })
  395. { }
  396. public override void SetValue(object value)
  397. {
  398. if (value == null)
  399. return;
  400. Vector3 vector = (Vector3)value;
  401. children[0].SetValue(vector.x);
  402. children[1].SetValue(vector.y);
  403. children[2].SetValue(vector.z);
  404. }
  405. }
  406. internal class GUIAnimVec4Entry : GUIAnimComplexEntry
  407. {
  408. public GUIAnimVec4Entry(GUIAnimFieldLayouts layouts, string path)
  409. : base(layouts, path, new[] { ".x", ".y", ".z", ".w" })
  410. { }
  411. public override void SetValue(object value)
  412. {
  413. if (value == null)
  414. return;
  415. Vector4 vector = (Vector4)value;
  416. children[0].SetValue(vector.x);
  417. children[1].SetValue(vector.y);
  418. children[2].SetValue(vector.z);
  419. children[3].SetValue(vector.w);
  420. }
  421. }
  422. internal class GUIAnimColorEntry : GUIAnimComplexEntry
  423. {
  424. public GUIAnimColorEntry(GUIAnimFieldLayouts layouts, string path)
  425. : base(layouts, path, new[] { ".r", ".g", ".b", ".a" })
  426. { }
  427. public override void SetValue(object value)
  428. {
  429. if (value == null)
  430. return;
  431. Color color = (Color)value;
  432. children[0].SetValue(color.r);
  433. children[1].SetValue(color.g);
  434. children[2].SetValue(color.b);
  435. children[3].SetValue(color.a);
  436. }
  437. }
  438. internal class GUIAnimMissingEntry : GUIAnimFieldEntry
  439. {
  440. private GUILabel missingLabel;
  441. private GUILayoutX underlayLayout;
  442. private GUILabel overlaySpacing;
  443. public GUIAnimMissingEntry(GUIAnimFieldLayouts layouts, string path)
  444. : base(layouts, path)
  445. {
  446. missingLabel = new GUILabel("Missing property!", GUIOption.FixedHeight(GetEntryHeight()));
  447. underlayLayout = layouts.underlay.AddLayoutX();
  448. underlayLayout.AddFlexibleSpace();
  449. underlayLayout.AddElement(missingLabel);
  450. underlayLayout.AddSpace(50);
  451. overlaySpacing = new GUILabel("", GUIOption.FixedHeight(GetEntryHeight()));
  452. layouts.overlay.AddElement(overlaySpacing);
  453. // TODO - Alternating backgrounds
  454. }
  455. public override void Toggle(bool on)
  456. {
  457. underlayLayout.Active = on;
  458. overlaySpacing.Active = on;
  459. base.Toggle(on);
  460. }
  461. }
  462. /** @} */
  463. }