GUIFieldSelector.cs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568
  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. /// Renders GUI elements that display a Scene Object, its transform, components and child objects, as well as all of
  13. /// their fields. User can then select one of the fields and the class will output a path to the selected field, as
  14. /// well as its parent scene object and component.
  15. /// </summary>
  16. public class GUIFieldSelector
  17. {
  18. private const int INDENT_AMOUNT = 5;
  19. private const int PADDING = 5;
  20. private GUIScrollArea scrollArea;
  21. private int foldoutWidth;
  22. private SceneObject rootSO;
  23. private Element rootElement;
  24. /// <summary>
  25. /// Stores a single entry in the field hierarchy.
  26. /// </summary>
  27. private struct Element
  28. {
  29. public Element(SceneObject so, Component comp, string path)
  30. {
  31. this.so = so;
  32. this.comp = comp;
  33. this.path = path;
  34. toggle = null;
  35. childLayout = null;
  36. indentLayout = null;
  37. children = null;
  38. }
  39. public SceneObject so;
  40. public Component comp;
  41. public string path;
  42. public GUIToggle toggle;
  43. public GUILayout childLayout;
  44. public GUILayout indentLayout;
  45. public Element[] children;
  46. }
  47. /// <summary>
  48. /// Triggers when the user selects a field. The subscriber will be receive a scene object the field is part of,
  49. /// component the field is part of, and a path to the field, each entry separated by "/". Component can be null
  50. /// in which case it is assumed the field is part of the SceneObject itself. Scene object names are always prefixed
  51. /// with "!", components are always prefixed with ":", while field entries have no prefix.
  52. ///
  53. /// For example: !My Scene Object/:Camera/path/to/field
  54. /// </summary>
  55. public Action<SceneObject, Component, string, SerializableProperty.FieldType> OnElementSelected;
  56. /// <summary>
  57. /// Creates a new GUIFieldSelector and registers its GUI elements in the provided layout.
  58. /// </summary>
  59. /// <param name="layout">Layout into which to add the selector GUI hierarchy.</param>
  60. /// <param name="so">Scene object to inspect the fields for.</param>
  61. /// <param name="width">Width of the selector area, in pixels.</param>
  62. /// <param name="height">Height of the selector area, in pixels.</param>
  63. public GUIFieldSelector(GUILayout layout, SceneObject so, int width, int height)
  64. {
  65. rootSO = so;
  66. scrollArea = new GUIScrollArea();
  67. scrollArea.SetWidth(width);
  68. scrollArea.SetHeight(height);
  69. layout.AddElement(scrollArea);
  70. GUISkin skin = EditorBuiltin.GUISkin;
  71. GUIElementStyle style = skin.GetStyle(EditorStyles.Expand);
  72. foldoutWidth = style.Width;
  73. Rebuild();
  74. }
  75. /// <summary>
  76. /// Rebuilds all of the selection GUI.
  77. /// </summary>
  78. private void Rebuild()
  79. {
  80. scrollArea.Layout.Clear();
  81. rootElement = new Element();
  82. if (rootSO == null)
  83. return;
  84. rootElement.so = rootSO;
  85. rootElement.childLayout = scrollArea.Layout;
  86. rootElement.indentLayout = null;
  87. GUILabel header = new GUILabel(new LocEdString("Select a property"), EditorStyles.Header);
  88. scrollArea.Layout.AddElement(header);
  89. scrollArea.Layout.AddSpace(5);
  90. AddSceneObjectRows(rootElement);
  91. scrollArea.Layout.AddSpace(5);
  92. scrollArea.Layout.AddFlexibleSpace();
  93. }
  94. /// <summary>
  95. /// Registers a set of rows for all components in a <see cref="SceneObject"/>, as well as its transform and
  96. /// child objects.
  97. /// </summary>
  98. /// <param name="parent">Row element under which to create the new rows.</param>
  99. private void AddSceneObjectRows(Element parent)
  100. {
  101. string soName = "!" + parent.so.Name;
  102. Component[] components = parent.so.GetComponents();
  103. parent.children = new Element[components.Length + 2];
  104. SpriteTexture soIcon = EditorBuiltin.GetEditorIcon(EditorIcon.SceneObject);
  105. SpriteTexture compIcon = EditorBuiltin.GetEditorIcon(EditorIcon.Component);
  106. // Add transform
  107. parent.children[0] = AddFoldoutRow(parent.childLayout, soIcon, "Transform", parent.so,
  108. null, parent.path + "/" + soName, ToggleTransformFoldout);
  109. // Add components
  110. for (int i = 0; i < components.Length; i++)
  111. {
  112. Component childComponent = components[i];
  113. Action<Element, bool> toggleCallback =
  114. (toggleParent, expand) =>
  115. {
  116. SerializableObject componentObject = new SerializableObject(childComponent.GetType(), childComponent);
  117. ToggleObjectFoldout(toggleParent, componentObject, expand);
  118. };
  119. string name = childComponent.GetType().Name;
  120. string path = parent.path + "/" + soName + "/:" + name;
  121. parent.children[i + 1] = AddFoldoutRow(parent.childLayout, compIcon, name, parent.so, childComponent, path,
  122. toggleCallback);
  123. }
  124. // Add children
  125. if (parent.so.GetNumChildren() > 0)
  126. {
  127. parent.children[parent.children.Length - 1] = AddFoldoutRow(parent.childLayout, soIcon, "Children",
  128. parent.so, null, parent.path + "/" + soName, ToggleChildFoldout);
  129. }
  130. }
  131. /// <summary>
  132. /// Registers a new row in the layout for the provided property. The type of row is determined by the property type.
  133. /// </summary>
  134. /// <param name="parent">Parent foldout row to which to append the new elements.</param>
  135. /// <param name="name">Name of the field the property belongs to.</param>
  136. /// <param name="path">Slash separated path to the field from its parent object.</param>
  137. /// <param name="property">Property to create the row for.</param>
  138. /// <param name="element">Element containing data for the newly created row. Only valid if method returns true.
  139. /// </param>
  140. /// <returns>Returns true if the row was successfully added, false otherwise.</returns>
  141. private bool AddPropertyRow(Element parent, string name, string path, SerializableProperty property,
  142. out Element element)
  143. {
  144. switch (property.Type)
  145. {
  146. case SerializableProperty.FieldType.Bool:
  147. case SerializableProperty.FieldType.Float:
  148. case SerializableProperty.FieldType.Int:
  149. case SerializableProperty.FieldType.Color:
  150. case SerializableProperty.FieldType.Vector2:
  151. case SerializableProperty.FieldType.Vector3:
  152. case SerializableProperty.FieldType.Vector4:
  153. element = AddFieldRow(parent.childLayout, name, parent.so, parent.comp, path, property.Type);
  154. return true;
  155. case SerializableProperty.FieldType.Object:
  156. {
  157. Action<Element, bool> toggleCallback =
  158. (toggleParent, expand) =>
  159. {
  160. SerializableObject childObject = new SerializableObject(property.InternalType, null);
  161. ToggleObjectFoldout(toggleParent, childObject, expand);
  162. };
  163. element = AddFoldoutRow(parent.childLayout, null, name, parent.so, parent.comp, path, toggleCallback);
  164. }
  165. return true;
  166. case SerializableProperty.FieldType.Array:
  167. {
  168. Action<Element, bool> toggleCallback =
  169. (toggleParent, expand) =>
  170. {
  171. SerializableArray childObject = property.GetArray();
  172. if (childObject != null)
  173. ToggleArrayFoldout(toggleParent, childObject, expand);
  174. };
  175. element = AddFoldoutRow(parent.childLayout, null, name, parent.so, parent.comp, path, toggleCallback);
  176. }
  177. return true;
  178. case SerializableProperty.FieldType.List:
  179. {
  180. Action<Element, bool> toggleCallback =
  181. (toggleParent, expand) =>
  182. {
  183. SerializableList childObject = property.GetList();
  184. if (childObject != null)
  185. ToggleListFoldout(toggleParent, childObject, expand);
  186. };
  187. element = AddFoldoutRow(parent.childLayout, null, name, parent.so, parent.comp, path, toggleCallback);
  188. }
  189. return true;
  190. }
  191. element = new Element();
  192. return false;
  193. }
  194. /// <summary>
  195. /// Registers a set of rows for all child fields of the provided object.
  196. /// </summary>
  197. /// <param name="parent">Parent foldout row to which to append the new elements.</param>
  198. /// <param name="serializableObject">Type of the object whose fields to display.</param>
  199. private void AddObjectRows(Element parent, SerializableObject serializableObject)
  200. {
  201. List<Element> elements = new List<Element>();
  202. foreach (var field in serializableObject.Fields)
  203. {
  204. if (!field.Animable)
  205. continue;
  206. string propertyPath = parent.path + "/" + field.Name;
  207. Element element;
  208. if(AddPropertyRow(parent, field.Name, propertyPath, field.GetProperty(), out element))
  209. elements.Add(element);
  210. }
  211. // Handle special fields
  212. Debug.Log("Type: " + serializableObject.Type);
  213. if (serializableObject.Type == typeof(Animation))
  214. {
  215. Animation anim = serializableObject.Object as Animation;
  216. Debug.Log("Anim: " + (anim != null));
  217. MorphShapes morphShapes = anim?.SceneObject.GetComponent<Renderable>()?.Mesh?.MorphShapes;
  218. Debug.Log("Shapes: " + (morphShapes != null));
  219. if (morphShapes != null)
  220. {
  221. string propertyPath = parent.path + "/MorphShapes";
  222. Action<Element, bool> toggleCallback =
  223. (toggleParent, expand) =>
  224. {
  225. toggleParent.childLayout.Clear();
  226. toggleParent.children = null;
  227. toggleParent.indentLayout.Active = expand;
  228. if (expand)
  229. {
  230. List<Element> childElements = new List<Element>();
  231. MorphChannel[] channels = morphShapes.Channels;
  232. for (int i = 0; i < channels.Length; i++)
  233. {
  234. string channelName = channels[i].Name;
  235. string framePropertyPath = parent.path + "/MorphShapes/Frames/" + channelName;
  236. string weightPropertyPath = parent.path + "/MorphShapes/Weight/" + channelName;
  237. childElements.Add(AddFieldRow(toggleParent.childLayout, channelName + " (Frames)",
  238. toggleParent.so, toggleParent.comp, framePropertyPath,
  239. SerializableProperty.FieldType.Float));
  240. childElements.Add(AddFieldRow(toggleParent.childLayout, channelName + " (Weight)",
  241. toggleParent.so, toggleParent.comp, weightPropertyPath,
  242. SerializableProperty.FieldType.Float));
  243. }
  244. toggleParent.children = childElements.ToArray();
  245. }
  246. };
  247. elements.Add(AddFoldoutRow(parent.childLayout, null, "MorphShapes", parent.so, parent.comp,
  248. propertyPath, toggleCallback));
  249. }
  250. }
  251. parent.children = elements.ToArray();
  252. }
  253. /// <summary>
  254. /// Registers a set of rows for all entries of the provided array.
  255. /// </summary>
  256. /// <param name="parent">Parent foldout row to which to append the new elements.</param>
  257. /// <param name="serializableArray">Array whose fields to display.</param>
  258. private void AddArrayRows(Element parent, SerializableArray serializableArray)
  259. {
  260. List<Element> elements = new List<Element>();
  261. int length = serializableArray.GetLength();
  262. for (int i = 0; i < length; i++)
  263. {
  264. string name = "[" + i + "]";
  265. string propertyPath = parent.path + name;
  266. Element element;
  267. if (AddPropertyRow(parent, name, propertyPath, serializableArray.GetProperty(i), out element))
  268. elements.Add(element);
  269. }
  270. parent.children = elements.ToArray();
  271. }
  272. /// <summary>
  273. /// Registers a set of rows for all entries of the provided list.
  274. /// </summary>
  275. /// <param name="parent">Parent foldout row to which to append the new elements.</param>
  276. /// <param name="serializableList">List whose fields to display.</param>
  277. private void AddListRows(Element parent, SerializableList serializableList)
  278. {
  279. List<Element> elements = new List<Element>();
  280. int length = serializableList.GetLength();
  281. for (int i = 0; i < length; i++)
  282. {
  283. string name = "[" + i + "]";
  284. string propertyPath = parent.path + name;
  285. Element element;
  286. if (AddPropertyRow(parent, name, propertyPath, serializableList.GetProperty(i), out element))
  287. elements.Add(element);
  288. }
  289. parent.children = elements.ToArray();
  290. }
  291. /// <summary>
  292. /// Registers a new row in the layout. The row cannot have any further children and can be selected as the output
  293. /// field.
  294. /// </summary>
  295. /// <param name="layout">Layout to append the row GUI elements to.</param>
  296. /// <param name="name">Name of the field.</param>
  297. /// <param name="so">Parent scene object of the field.</param>
  298. /// <param name="component">Parent component of the field. Can be null if field belongs to <see cref="SceneObject"/>.
  299. /// </param>
  300. /// <param name="path">Slash separated path to the field from its parent object.</param>
  301. /// <param name="type">Data type stored in the field.</param>
  302. /// <returns>Element object storing all information about the added field.</returns>
  303. private Element AddFieldRow(GUILayout layout, string name, SceneObject so, Component component, string path, SerializableProperty.FieldType type)
  304. {
  305. Element element = new Element(so, component, path);
  306. GUILayoutX elementLayout = layout.AddLayoutX();
  307. elementLayout.AddSpace(PADDING);
  308. elementLayout.AddSpace(foldoutWidth);
  309. GUILabel label = new GUILabel(new LocEdString(name));
  310. elementLayout.AddElement(label);
  311. GUIButton selectBtn = new GUIButton(new LocEdString("Select"));
  312. selectBtn.OnClick += () => { DoOnElementSelected(element, type); };
  313. elementLayout.AddFlexibleSpace();
  314. elementLayout.AddElement(selectBtn);
  315. elementLayout.AddSpace(5);
  316. element.path = path;
  317. return element;
  318. }
  319. /// <summary>
  320. /// Registers a new row in the layout. The row cannot be selected as the output field, but rather can be expanded
  321. /// so it displays child elements.
  322. /// </summary>
  323. /// <param name="layout">Layout to append the row GUI elements to.</param>
  324. /// <param name="icon">Optional icon to display next to the name. Can be null.</param>
  325. /// <param name="name">Name of the field.</param>
  326. /// <param name="so">Parent scene object of the field.</param>
  327. /// <param name="component">Parent component of the field. Can be null if field belongs to <see cref="SceneObject"/>.
  328. /// </param>
  329. /// <param name="path">Slash separated path to the field from its parent object.</param>
  330. /// <param name="toggleCallback">Callback to trigger when the user expands or collapses the foldout.</param>
  331. /// <returns>Element object storing all information about the added field.</returns>
  332. private Element AddFoldoutRow(GUILayout layout, SpriteTexture icon, string name, SceneObject so, Component component,
  333. string path, Action<Element, bool> toggleCallback)
  334. {
  335. Element element = new Element(so, component, path);
  336. GUILayoutY elementLayout = layout.AddLayoutY();
  337. GUILayoutX foldoutLayout = elementLayout.AddLayoutX();
  338. element.toggle = new GUIToggle("", EditorStyles.Expand);
  339. element.toggle.OnToggled += x => toggleCallback(element, x);
  340. foldoutLayout.AddSpace(PADDING);
  341. foldoutLayout.AddElement(element.toggle);
  342. if (icon != null)
  343. {
  344. GUITexture guiIcon = new GUITexture(icon, GUIOption.FixedWidth(16), GUIOption.FixedWidth(16));
  345. foldoutLayout.AddElement(guiIcon);
  346. }
  347. GUILabel label = new GUILabel(new LocEdString(name));
  348. foldoutLayout.AddElement(label);
  349. foldoutLayout.AddFlexibleSpace();
  350. element.indentLayout = elementLayout.AddLayoutX();
  351. element.indentLayout.AddSpace(INDENT_AMOUNT);
  352. element.childLayout = element.indentLayout.AddLayoutY();
  353. element.indentLayout.Active = false;
  354. return element;
  355. }
  356. /// <summary>
  357. /// Expands or collapses the set of rows displaying a <see cref="SceneObject"/>'s transform (position, rotation,
  358. /// scale).
  359. /// </summary>
  360. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  361. /// <param name="expand">True to expand, false to collapse.</param>
  362. private void ToggleTransformFoldout(Element parent, bool expand)
  363. {
  364. parent.childLayout.Clear();
  365. parent.children = null;
  366. parent.indentLayout.Active = expand;
  367. if (expand)
  368. {
  369. parent.children = new Element[3];
  370. parent.children[0] = AddFieldRow(parent.childLayout, "Position", parent.so, null, parent.path + "/Position", SerializableProperty.FieldType.Vector3);
  371. parent.children[1] = AddFieldRow(parent.childLayout, "Rotation", parent.so, null, parent.path + "/Rotation", SerializableProperty.FieldType.Vector3);
  372. parent.children[2] = AddFieldRow(parent.childLayout, "Scale", parent.so, null, parent.path + "/Scale", SerializableProperty.FieldType.Vector3);
  373. }
  374. }
  375. /// <summary>
  376. /// Expands or collapses the set of rows displaying all <see cref="SceneObject"/> children of a
  377. /// <see cref="SceneObject"/>.
  378. /// </summary>
  379. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  380. /// <param name="expand">True to expand, false to collapse.</param>
  381. private void ToggleChildFoldout(Element parent, bool expand)
  382. {
  383. parent.childLayout.Clear();
  384. parent.children = null;
  385. parent.indentLayout.Active = expand;
  386. if (expand)
  387. {
  388. int numChildren = parent.so.GetNumChildren();
  389. parent.children = new Element[numChildren];
  390. for (int i = 0; i < numChildren; i++)
  391. {
  392. SceneObject child = parent.so.GetChild(i);
  393. SpriteTexture soIcon = EditorBuiltin.GetEditorIcon(EditorIcon.SceneObject);
  394. parent.children[i] = AddFoldoutRow(parent.childLayout, soIcon, child.Name, child, null, parent.path,
  395. ToggleSceneObjectFoldout);
  396. }
  397. }
  398. }
  399. /// <summary>
  400. /// Expands or collapses the set of rows displaying all children of a <see cref="SceneObject"/>. This includes
  401. /// it's components, transform and child scene objects.
  402. /// </summary>
  403. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  404. /// <param name="expand">True to expand, false to collapse.</param>
  405. private void ToggleSceneObjectFoldout(Element parent, bool expand)
  406. {
  407. parent.childLayout.Clear();
  408. parent.children = null;
  409. parent.indentLayout.Active = expand;
  410. if (expand)
  411. AddSceneObjectRows(parent);
  412. }
  413. /// <summary>
  414. /// Expands or collapses the set of rows displaying all fields of a serializable object.
  415. /// </summary>
  416. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  417. /// <param name="obj">Object describing the type of the object whose fields to display.</param>
  418. /// <param name="expand">True to expand, false to collapse.</param>
  419. private void ToggleObjectFoldout(Element parent, SerializableObject obj,
  420. bool expand)
  421. {
  422. parent.childLayout.Clear();
  423. parent.children = null;
  424. parent.indentLayout.Active = expand;
  425. if (expand)
  426. AddObjectRows(parent, obj);
  427. }
  428. /// <summary>
  429. /// Expands or collapses the set of rows displaying all entries of a serializable array.
  430. /// </summary>
  431. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  432. /// <param name="obj">Object describing the array whose entries to display.</param>
  433. /// <param name="expand">True to expand, false to collapse.</param>
  434. private void ToggleArrayFoldout(Element parent, SerializableArray obj,
  435. bool expand)
  436. {
  437. parent.childLayout.Clear();
  438. parent.children = null;
  439. parent.indentLayout.Active = expand;
  440. if (expand)
  441. AddArrayRows(parent, obj);
  442. }
  443. /// <summary>
  444. /// Expands or collapses the set of rows displaying all entries of a serializable list.
  445. /// </summary>
  446. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  447. /// <param name="obj">Object describing the list whose entries to display.</param>
  448. /// <param name="expand">True to expand, false to collapse.</param>
  449. private void ToggleListFoldout(Element parent, SerializableList obj,
  450. bool expand)
  451. {
  452. parent.childLayout.Clear();
  453. parent.children = null;
  454. parent.indentLayout.Active = expand;
  455. if (expand)
  456. AddListRows(parent, obj);
  457. }
  458. /// <summary>
  459. /// Triggered when the user selects a field.
  460. /// </summary>
  461. /// <param name="element">Row element for the field that was selected.</param>
  462. /// <param name="type">Data type stored in the field.</param>
  463. private void DoOnElementSelected(Element element, SerializableProperty.FieldType type)
  464. {
  465. if (OnElementSelected != null)
  466. OnElementSelected(element.so, element.comp, element.path, type);
  467. }
  468. }
  469. /** @} */
  470. }