GUIFieldSelector.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  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 SpriteTexture ICON_SO; // TODO
  20. private SpriteTexture ICON_COMPONENT; // TODO
  21. private GUILayoutY mainLayout;
  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 GUILayoutY childLayout;
  44. public GUILayoutX 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.
  51. /// </summary>
  52. public Action<SceneObject, Component, string> OnElementSelected;
  53. /// <summary>
  54. /// Creates a new GUIFieldSelector and registers its GUI elements in the provided layout.
  55. /// </summary>
  56. /// <param name="layout">Layout into which to add the selector GUI hierarchy.</param>
  57. /// <param name="so">Scene object to inspect the fields for.</param>
  58. public GUIFieldSelector(GUILayout layout, SceneObject so)
  59. {
  60. rootSO = so;
  61. mainLayout = layout.AddLayoutY();
  62. Rebuild();
  63. }
  64. /// <summary>
  65. /// Rebuilds all of the selection GUI.
  66. /// </summary>
  67. private void Rebuild()
  68. {
  69. mainLayout.Clear();
  70. rootElement = new Element();
  71. if (rootSO == null)
  72. return;
  73. rootElement.so = rootSO;
  74. AddSceneObjectRows(rootElement);
  75. }
  76. /// <summary>
  77. /// Registers a set of rows for all components in a <see cref="SceneObject"/>, as well as its transform and
  78. /// child objects.
  79. /// </summary>
  80. /// <param name="parent">Row element under which to create the new rows.</param>
  81. private void AddSceneObjectRows(Element parent)
  82. {
  83. Component[] components = parent.so.GetComponents();
  84. parent.children = new Element[components.Length + 2];
  85. // Add transform
  86. parent.children[0] = AddFoldoutRow(mainLayout, ICON_COMPONENT, "Transform", parent.so,
  87. null, "", ToggleTransformFoldout);
  88. // Add components
  89. for (int i = 0; i < components.Length; i++)
  90. {
  91. Component childComponent = components[i];
  92. Action<Element, bool> toggleCallback =
  93. (toggleParent, expand) =>
  94. {
  95. SerializableObject componentObject = new SerializableObject(childComponent.GetType(), null);
  96. ToggleObjectFoldout(toggleParent, componentObject, expand);
  97. };
  98. string name = childComponent.GetType().Name;
  99. parent.children[i + 1] = AddFoldoutRow(mainLayout, ICON_COMPONENT, name, parent.so, childComponent, "",
  100. toggleCallback);
  101. }
  102. // Add children
  103. parent.children[parent.children.Length - 1] = AddFoldoutRow(mainLayout, ICON_SO, "Children", parent.so, null, "",
  104. ToggleChildFoldout);
  105. }
  106. /// <summary>
  107. /// Registers a set of rows for all child fields of the provided object.
  108. /// </summary>
  109. /// <param name="parent">Parent foldout row to which to append the new elements.</param>
  110. /// <param name="serializableObject">Type of the object whose fields to display.</param>
  111. private void AddObjectRows(Element parent, SerializableObject serializableObject)
  112. {
  113. List<Element> elements = new List<Element>();
  114. foreach (var field in serializableObject.Fields)
  115. {
  116. if (!field.Inspectable)
  117. continue;
  118. string propertyPath = parent.path + "/" + field.Name;
  119. switch (field.Type)
  120. {
  121. case SerializableProperty.FieldType.Bool:
  122. case SerializableProperty.FieldType.Float:
  123. case SerializableProperty.FieldType.Int:
  124. case SerializableProperty.FieldType.Color:
  125. case SerializableProperty.FieldType.Vector2:
  126. case SerializableProperty.FieldType.Vector3:
  127. case SerializableProperty.FieldType.Vector4:
  128. elements.Add(AddFieldRow(parent.childLayout, field.Name, parent.so, parent.comp, propertyPath));
  129. break;
  130. case SerializableProperty.FieldType.Object:
  131. Action<Element, bool> toggleCallback =
  132. (toggleParent, expand) =>
  133. {
  134. SerializableObject childObject = new SerializableObject(field.InternalType, null);
  135. ToggleObjectFoldout(toggleParent, childObject, expand);
  136. };
  137. elements.Add(AddFoldoutRow(parent.childLayout, null, field.Name, parent.so, parent.comp,
  138. propertyPath, toggleCallback));
  139. break;
  140. }
  141. }
  142. parent.children = elements.ToArray();
  143. }
  144. /// <summary>
  145. /// Registers a new row in the layout. The row cannot have any further children and can be selected as the output
  146. /// field.
  147. /// </summary>
  148. /// <param name="layout">Layout to append the row GUI elements to.</param>
  149. /// <param name="name">Name of the field.</param>
  150. /// <param name="so">Parent scene object of the field.</param>
  151. /// <param name="component">Parent component of the field. Can be null if field belongs to <see cref="SceneObject"/>.
  152. /// </param>
  153. /// <param name="path">Slash separated path to the field from its parent object.</param>
  154. /// <returns>Element object storing all information about the added field.</returns>
  155. private Element AddFieldRow(GUILayout layout, string name, SceneObject so, Component component, string path)
  156. {
  157. Element element = new Element(so, component, path);
  158. GUILayoutX elementLayout = layout.AddLayoutX();
  159. GUILabel label = new GUILabel(new LocEdString(name));
  160. elementLayout.AddElement(label);
  161. GUIButton selectBtn = new GUIButton(new LocEdString("Select"));
  162. selectBtn.OnClick += () => { DoOnElementSelected(element); };
  163. elementLayout.AddFlexibleSpace();
  164. elementLayout.AddElement(selectBtn);
  165. element.path = path;
  166. return element;
  167. }
  168. /// <summary>
  169. /// Registers a new row in the layout. The row cannot be selected as the output field, but rather can be expanded
  170. /// so it displays child elements.
  171. /// </summary>
  172. /// <param name="layout">Layout to append the row GUI elements to.</param>
  173. /// <param name="icon">Optional icon to display next to the name. Can be null.</param>
  174. /// <param name="name">Name of the field.</param>
  175. /// <param name="so">Parent scene object of the field.</param>
  176. /// <param name="component">Parent component of the field. Can be null if field belongs to <see cref="SceneObject"/>.
  177. /// </param>
  178. /// <param name="path">Slash separated path to the field from its parent object.</param>
  179. /// <param name="toggleCallback">Callback to trigger when the user expands or collapses the foldout.</param>
  180. /// <returns>Element object storing all information about the added field.</returns>
  181. private Element AddFoldoutRow(GUILayout layout, SpriteTexture icon, string name, SceneObject so, Component component,
  182. string path, Action<Element, bool> toggleCallback)
  183. {
  184. Element element = new Element(so, component, path);
  185. GUILayoutY elementLayout = layout.AddLayoutY();
  186. GUILayoutX foldoutLayout = elementLayout.AddLayoutX();
  187. element.toggle = new GUIToggle(EditorStyles.Expand);
  188. element.toggle.OnToggled += x => toggleCallback(element, x);
  189. foldoutLayout.AddElement(element.toggle);
  190. if (icon != null)
  191. {
  192. GUITexture guiIcon = new GUITexture(icon, GUIOption.FixedWidth(16), GUIOption.FixedWidth(16));
  193. foldoutLayout.AddElement(guiIcon);
  194. }
  195. GUILabel label = new GUILabel(new LocEdString(name));
  196. foldoutLayout.AddElement(label);
  197. foldoutLayout.AddFlexibleSpace();
  198. element.indentLayout = elementLayout.AddLayoutX();
  199. element.indentLayout.AddSpace(INDENT_AMOUNT);
  200. element.childLayout = element.indentLayout.AddLayoutY();
  201. element.indentLayout.Active = false;
  202. return element;
  203. }
  204. /// <summary>
  205. /// Expands or collapses the set of rows displaying a <see cref="SceneObject"/>'s transform (position, rotation,
  206. /// scale).
  207. /// </summary>
  208. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  209. /// <param name="expand">True to expand, false to collapse.</param>
  210. private void ToggleTransformFoldout(Element parent, bool expand)
  211. {
  212. parent.childLayout.Clear();
  213. parent.children = null;
  214. parent.indentLayout.Active = expand;
  215. if (expand)
  216. {
  217. parent.children = new Element[3];
  218. parent.children[0] = AddFieldRow(parent.childLayout, "Position", parent.so, null, parent.path + "/Position");
  219. parent.children[1] = AddFieldRow(parent.childLayout, "Rotation", parent.so, null, parent.path + "/Rotation");
  220. parent.children[2] = AddFieldRow(parent.childLayout, "Scale", parent.so, null, parent.path + "/Scale");
  221. }
  222. }
  223. /// <summary>
  224. /// Expands or collapses the set of rows displaying all <see cref="SceneObject"/> children of a
  225. /// <see cref="SceneObject"/>.
  226. /// </summary>
  227. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  228. /// <param name="expand">True to expand, false to collapse.</param>
  229. private void ToggleChildFoldout(Element parent, bool expand)
  230. {
  231. parent.childLayout.Clear();
  232. parent.children = null;
  233. parent.indentLayout.Active = expand;
  234. if (expand)
  235. {
  236. int numChildren = parent.so.GetNumChildren();
  237. parent.children = new Element[numChildren];
  238. for (int i = 0; i < numChildren; i++)
  239. {
  240. SceneObject child = parent.so.GetChild(i);
  241. parent.children[i] = AddFoldoutRow(parent.childLayout, ICON_SO, child.Name, child, null, "",
  242. ToggleSceneObjectFoldout);
  243. }
  244. }
  245. }
  246. /// <summary>
  247. /// Expands or collapses the set of rows displaying all children of a <see cref="SceneObject"/>. This includes
  248. /// it's components, transform and child scene objects.
  249. /// </summary>
  250. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  251. /// <param name="expand">True to expand, false to collapse.</param>
  252. private void ToggleSceneObjectFoldout(Element parent, bool expand)
  253. {
  254. parent.childLayout.Clear();
  255. parent.children = null;
  256. parent.indentLayout.Active = expand;
  257. if (expand)
  258. {
  259. AddSceneObjectRows(parent);
  260. }
  261. }
  262. /// <summary>
  263. /// Expands or collapses the set of rows displaying all fields of a serializable object.
  264. /// </summary>
  265. /// <param name="parent">Parent row element whose children to expand/collapse.</param>
  266. /// <param name="obj">Object describing the type of the object whose fields to display.</param>
  267. /// <param name="expand">True to expand, false to collapse.</param>
  268. private void ToggleObjectFoldout(Element parent, SerializableObject obj,
  269. bool expand)
  270. {
  271. parent.childLayout.Clear();
  272. parent.children = null;
  273. parent.indentLayout.Active = expand;
  274. if (expand)
  275. AddObjectRows(parent, obj);
  276. }
  277. /// <summary>
  278. /// Triggered when the user selects a field.
  279. /// </summary>
  280. /// <param name="element">Row element for the field that was selected.</param>
  281. private void DoOnElementSelected(Element element)
  282. {
  283. if (OnElementSelected != null)
  284. OnElementSelected(element.so, element.comp, element.path);
  285. }
  286. }
  287. /** @} */
  288. }