InspectableField.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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 Inspector
  9. * @{
  10. */
  11. /// <summary>
  12. /// Inspectable field displays GUI elements for a single <see cref="SerializableProperty"/>. This is a base class that
  13. /// should be specialized for all supported types contained by <see cref="SerializableProperty"/>. Inspectable fields
  14. /// can and should be created recursively - normally complex types like objects and arrays will contain fields of their
  15. /// own, while primitive types like integer or boolean will consist of only a GUI element.
  16. /// </summary>
  17. public abstract class InspectableField
  18. {
  19. private const int IndentAmount = 5;
  20. protected Inspector parent;
  21. protected InspectableFieldLayout layout;
  22. protected SerializableProperty property;
  23. protected string title;
  24. protected string path;
  25. protected int depth;
  26. protected SerializableProperty.FieldType type;
  27. /// <summary>
  28. /// Property this field is displaying contents of.
  29. /// </summary>
  30. public SerializableProperty Property
  31. {
  32. get { return property; }
  33. set
  34. {
  35. if (value == null)
  36. throw new ArgumentException("Cannot assign a null property to an inspectable field.");
  37. if (value.Type != type)
  38. {
  39. throw new ArgumentException(
  40. "Attempting to initialize an inspectable field with a property of invalid type.");
  41. }
  42. property = value;
  43. }
  44. }
  45. /// <summary>
  46. /// Creates a new inspectable field GUI for the specified property.
  47. /// </summary>
  48. /// <param name="parent">Parent Inspector this field belongs to.</param>
  49. /// <param name="title">Name of the property, or some other value to set as the title.</param>
  50. /// <param name="path">Full path to this property (includes name of this property and all parent properties).</param>
  51. /// <param name="type">Type of property this field will be used for displaying.</param>
  52. /// <param name="depth">Determines how deep within the inspector nesting hierarchy is this field. Some fields may
  53. /// contain other fields, in which case you should increase this value by one.</param>
  54. /// <param name="layout">Parent layout that all the field elements will be added to.</param>
  55. /// <param name="property">Serializable property referencing the array whose contents to display.</param>
  56. public InspectableField(Inspector parent, string title, string path, SerializableProperty.FieldType type,
  57. int depth, InspectableFieldLayout layout, SerializableProperty property)
  58. {
  59. this.parent = parent;
  60. this.layout = layout;
  61. this.title = title;
  62. this.path = path;
  63. this.type = type;
  64. this.depth = depth;
  65. Property = property;
  66. }
  67. /// <summary>
  68. /// Checks if contents of the field have been modified, and updates them if needed.
  69. /// </summary>
  70. /// <param name="layoutIndex">Index in the parent's layout at which to insert the GUI elements for this field.
  71. /// </param>
  72. /// <returns>State representing was anything modified between two last calls to <see cref="Refresh"/>.</returns>
  73. public virtual InspectableState Refresh(int layoutIndex)
  74. {
  75. return InspectableState.NotModified;
  76. }
  77. /// <summary>
  78. /// Returns the total number of GUI elements in the field's layout.
  79. /// </summary>
  80. /// <returns>Number of GUI elements in the field's layout.</returns>
  81. public int GetNumLayoutElements()
  82. {
  83. return layout.NumElements;
  84. }
  85. /// <summary>
  86. /// Returns an optional title layout. Certain fields may contain separate title and content layouts. Parent fields
  87. /// may use the separate title layout instead of the content layout to append elements. Having a separate title
  88. /// layout is purely cosmetical.
  89. /// </summary>
  90. /// <returns>Title layout if the field has one, null otherwise.</returns>
  91. public virtual GUILayoutX GetTitleLayout()
  92. {
  93. return null;
  94. }
  95. /// <summary>
  96. /// Initializes the GUI elements for the field.
  97. /// </summary>
  98. /// <param name="layoutIndex">Index at which to insert the GUI elements.</param>
  99. protected internal abstract void Initialize(int layoutIndex);
  100. /// <summary>
  101. /// Destroys all GUI elements in the inspectable field.
  102. /// </summary>
  103. public virtual void Destroy()
  104. {
  105. layout.DestroyElements();
  106. }
  107. /// <summary>
  108. /// Allows the user to override the default inspector GUI for a specific field in an object. If this method
  109. /// returns null the default field will be used instead.
  110. /// </summary>
  111. /// <param name="field">Field to generate inspector GUI for.</param>
  112. /// <param name="parent">Parent Inspector the GUI will be rendered on.</param>
  113. /// <param name="path">Full path to the provided field (includes name of this field and all parent fields).</param>
  114. /// <param name="layout">Parent layout that all the field elements will be added to.</param>
  115. /// <param name="layoutIndex">Index into the parent layout at which to insert the GUI elements for the field .</param>
  116. /// <param name="depth">
  117. /// Determines how deep within the inspector nesting hierarchy is this field. Some fields may contain other fields,
  118. /// in which case you should increase this value by one.
  119. /// </param>
  120. /// <returns>
  121. /// Inspectable field implementation that can be used for displaying the GUI for the provided field. Or null if
  122. /// default field GUI should be used instead.
  123. /// </returns>
  124. public delegate InspectableField FieldOverrideCallback(SerializableField field, Inspector parent, string path,
  125. InspectableFieldLayout layout, int layoutIndex, int depth);
  126. /// <summary>
  127. /// Creates inspectable fields all the fields/properties of the specified object.
  128. /// </summary>
  129. /// <param name="obj">Object whose fields the GUI will be drawn for.</param>
  130. /// <param name="parent">Parent Inspector to draw in.</param>
  131. /// <param name="path">Full path to the field this provided object was retrieved from.</param>
  132. /// <param name="depth">
  133. /// Determines how deep within the inspector nesting hierarchy is this objects. Some fields may contain other
  134. /// fields, in which case you should increase this value by one.
  135. /// </param>
  136. /// <param name="layout">Parent layout that all the field GUI elements will be added to.</param>
  137. /// <param name="overrideCallback">
  138. /// Optional callback that allows you to override the look of individual fields in the object. If non-null the
  139. /// callback will be called with information about every field in the provided object. If the callback returns
  140. /// non-null that inspectable field will be used for drawing the GUI, otherwise the default inspector field type
  141. /// will be used.
  142. /// </param>
  143. public static List<InspectableField> CreateFields(SerializableObject obj, Inspector parent, string path,
  144. int depth, GUILayoutY layout, FieldOverrideCallback overrideCallback = null)
  145. {
  146. // Retrieve fields and sort by order
  147. SerializableField[] fields = obj.Fields;
  148. Array.Sort(fields,
  149. (x, y) =>
  150. {
  151. int orderX = x.Flags.HasFlag(SerializableFieldAttributes.Order) ? x.Style.Order : 0;
  152. int orderY = y.Flags.HasFlag(SerializableFieldAttributes.Order) ? y.Style.Order : 0;
  153. return orderX.CompareTo(orderY);
  154. });
  155. // Generate per-field GUI while grouping by category
  156. Dictionary<string, Tuple<int, GUILayoutY>> categories = new Dictionary<string, Tuple<int, GUILayoutY>>();
  157. int rootIndex = 0;
  158. List<InspectableField> inspectableFields = new List<InspectableField>();
  159. foreach (var field in fields)
  160. {
  161. if (!field.Flags.HasFlag(SerializableFieldAttributes.Inspectable))
  162. continue;
  163. string category = null;
  164. if (field.Flags.HasFlag(SerializableFieldAttributes.Category))
  165. category = field.Style.CategoryName;
  166. Tuple<int, GUILayoutY> categoryInfo = null;
  167. if (!string.IsNullOrEmpty(category))
  168. {
  169. if (!categories.TryGetValue(category, out categoryInfo))
  170. {
  171. InspectableFieldLayout fieldLayout = new InspectableFieldLayout(layout);
  172. GUILayoutY categoryRootLayout = fieldLayout.AddLayoutY(rootIndex);
  173. GUILayoutX guiTitleLayout = categoryRootLayout.AddLayoutX();
  174. bool isExpanded = parent.Persistent.GetBool(path + "/[" + category + "]_Expanded");
  175. GUIToggle guiFoldout = new GUIToggle(category, EditorStyles.Foldout);
  176. guiFoldout.Value = isExpanded;
  177. guiFoldout.AcceptsKeyFocus = false;
  178. guiFoldout.OnToggled += x =>
  179. {
  180. parent.Persistent.SetBool(path + "/[" + category + "]_Expanded", x);
  181. };
  182. guiTitleLayout.AddElement(guiFoldout);
  183. GUILayoutX categoryContentLayout = categoryRootLayout.AddLayoutX();
  184. categoryContentLayout.AddSpace(IndentAmount);
  185. GUIPanel guiContentPanel = categoryContentLayout.AddPanel();
  186. GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX();
  187. guiIndentLayoutX.AddSpace(IndentAmount);
  188. GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY();
  189. guiIndentLayoutY.AddSpace(IndentAmount);
  190. GUILayoutY categoryLayout = guiIndentLayoutY.AddLayoutY();
  191. guiIndentLayoutY.AddSpace(IndentAmount);
  192. guiIndentLayoutX.AddSpace(IndentAmount);
  193. categoryContentLayout.AddSpace(IndentAmount);
  194. short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1);
  195. string bgPanelStyle = depth % 2 == 0
  196. ? EditorStylesInternal.InspectorContentBgAlternate
  197. : EditorStylesInternal.InspectorContentBg;
  198. GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth);
  199. GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
  200. backgroundPanel.AddElement(inspectorContentBg);
  201. categories[category] = new Tuple<int, GUILayoutY>(0, categoryLayout);
  202. rootIndex++;
  203. }
  204. }
  205. int currentIndex;
  206. GUILayoutY parentLayout;
  207. if (categoryInfo != null)
  208. {
  209. currentIndex = categoryInfo.Item1;
  210. parentLayout = categoryInfo.Item2;
  211. }
  212. else
  213. {
  214. currentIndex = rootIndex;
  215. parentLayout = layout;
  216. }
  217. string fieldName = field.Name;
  218. string childPath = string.IsNullOrEmpty(path) ? fieldName : path + "/" + fieldName;
  219. InspectableField inspectableField = null;
  220. if(overrideCallback != null)
  221. inspectableField = overrideCallback(field, parent, path, new InspectableFieldLayout(parentLayout),
  222. currentIndex, depth);
  223. if (inspectableField == null)
  224. {
  225. inspectableField = CreateField(parent, fieldName, childPath,
  226. currentIndex, depth, new InspectableFieldLayout(parentLayout), field.GetProperty(),
  227. InspectableFieldStyle.Create(field));
  228. }
  229. inspectableFields.Add(inspectableField);
  230. currentIndex += inspectableField.GetNumLayoutElements();
  231. if (categoryInfo != null)
  232. categories[category] = new Tuple<int, GUILayoutY>(currentIndex, parentLayout);
  233. else
  234. rootIndex = currentIndex;
  235. }
  236. return inspectableFields;
  237. }
  238. /// <summary>
  239. /// Creates a new inspectable field, automatically detecting the most appropriate implementation for the type
  240. /// contained in the provided serializable property. This may be one of the built-in inspectable field implemetations
  241. /// (like ones for primitives like int or bool), or a user defined implementation defined with a
  242. /// <see cref="CustomInspector"/> attribute.
  243. /// </summary>
  244. /// <param name="parent">Parent Inspector this field belongs to.</param>
  245. /// <param name="title">Name of the property, or some other value to set as the title.</param>
  246. /// <param name="path">Full path to this property (includes name of this property and all parent properties).</param>
  247. /// <param name="layoutIndex">Index into the parent layout at which to insert the GUI elements for the field .</param>
  248. /// <param name="depth">Determines how deep within the inspector nesting hierarchy is this field. Some fields may
  249. /// contain other fields, in which case you should increase this value by one.</param>
  250. /// <param name="layout">Parent layout that all the field elements will be added to.</param>
  251. /// <param name="property">Serializable property referencing the array whose contents to display.</param>
  252. /// <param name="style">Information that can be used for customizing field rendering and behaviour.</param>
  253. /// <returns>Inspectable field implementation that can be used for displaying the GUI for a serializable property
  254. /// of the provided type.</returns>
  255. public static InspectableField CreateField(Inspector parent, string title, string path, int layoutIndex,
  256. int depth, InspectableFieldLayout layout, SerializableProperty property, InspectableFieldStyleInfo style = null)
  257. {
  258. InspectableField field = null;
  259. Type type = property.InternalType;
  260. if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(RRef<>))
  261. type = type.GenericTypeArguments[0];
  262. Type customInspectable = InspectorUtility.GetCustomInspectable(type);
  263. if (customInspectable != null)
  264. {
  265. field = (InspectableField) Activator.CreateInstance(customInspectable, parent, title, path, depth, layout,
  266. property, style);
  267. }
  268. else
  269. {
  270. switch (property.Type)
  271. {
  272. case SerializableProperty.FieldType.Int:
  273. if (style != null && style.StyleFlags.HasFlag(InspectableFieldStyleFlags.AsLayerMask))
  274. field = new InspectableLayerMask(parent, title, path, depth, layout, property);
  275. else
  276. {
  277. if (style?.RangeStyle == null || !style.RangeStyle.Slider)
  278. field = new InspectableInt(parent, title, path, depth, layout, property, style);
  279. else
  280. field = new InspectableRangedInt(parent, title, path, depth, layout, property, style);
  281. }
  282. break;
  283. case SerializableProperty.FieldType.Float:
  284. if (style?.RangeStyle == null || !style.RangeStyle.Slider)
  285. field = new InspectableFloat(parent, title, path, depth, layout, property, style);
  286. else
  287. field = new InspectableRangedFloat(parent, title, path, depth, layout, property, style);
  288. break;
  289. case SerializableProperty.FieldType.Bool:
  290. field = new InspectableBool(parent, title, path, depth, layout, property);
  291. break;
  292. case SerializableProperty.FieldType.Color:
  293. field = new InspectableColor(parent, title, path, depth, layout, property);
  294. break;
  295. case SerializableProperty.FieldType.ColorGradient:
  296. field = new InspectableColorGradient(parent, title, path, depth, layout, property);
  297. break;
  298. case SerializableProperty.FieldType.Curve:
  299. field = new InspectableCurve(parent, title, path, depth, layout, property);
  300. break;
  301. case SerializableProperty.FieldType.FloatDistribution:
  302. field = new InspectableFloatDistribution(parent, title, path, depth, layout, property);
  303. break;
  304. case SerializableProperty.FieldType.Vector2Distribution:
  305. field = new InspectableVector2Distribution(parent, title, path, depth, layout, property);
  306. break;
  307. case SerializableProperty.FieldType.Vector3Distribution:
  308. field = new InspectableVector3Distribution(parent, title, path, depth, layout, property);
  309. break;
  310. case SerializableProperty.FieldType.ColorDistribution:
  311. field = new InspectableColorDistribution(parent, title, path, depth, layout, property);
  312. break;
  313. case SerializableProperty.FieldType.String:
  314. field = new InspectableString(parent, title, path, depth, layout, property);
  315. break;
  316. case SerializableProperty.FieldType.Vector2:
  317. field = new InspectableVector2(parent, title, path, depth, layout, property);
  318. break;
  319. case SerializableProperty.FieldType.Vector3:
  320. field = new InspectableVector3(parent, title, path, depth, layout, property);
  321. break;
  322. case SerializableProperty.FieldType.Vector4:
  323. field = new InspectableVector4(parent, title, path, depth, layout, property);
  324. break;
  325. case SerializableProperty.FieldType.Quaternion:
  326. if (style != null && style.StyleFlags.HasFlag(InspectableFieldStyleFlags.AsQuaternion))
  327. field = new InspectableQuaternion(parent, title, path, depth, layout, property);
  328. else
  329. field = new InspectableEuler(parent, title, path, depth, layout, property);
  330. break;
  331. case SerializableProperty.FieldType.Resource:
  332. field = new InspectableResource(parent, title, path, depth, layout, property);
  333. break;
  334. case SerializableProperty.FieldType.RRef:
  335. field = new InspectableRRef(parent, title, path, depth, layout, property);
  336. break;
  337. case SerializableProperty.FieldType.GameObjectRef:
  338. field = new InspectableGameObjectRef(parent, title, path, depth, layout, property);
  339. break;
  340. case SerializableProperty.FieldType.Object:
  341. field = new InspectableObject(parent, title, path, depth, layout, property, style);
  342. break;
  343. case SerializableProperty.FieldType.Array:
  344. field = new InspectableArray(parent, title, path, depth, layout, property, style);
  345. break;
  346. case SerializableProperty.FieldType.List:
  347. field = new InspectableList(parent, title, path, depth, layout, property);
  348. break;
  349. case SerializableProperty.FieldType.Dictionary:
  350. field = new InspectableDictionary(parent, title, path, depth, layout, property);
  351. break;
  352. case SerializableProperty.FieldType.Enum:
  353. field = new InspectableEnum(parent, title, path, depth, layout, property);
  354. break;
  355. }
  356. }
  357. if (field == null)
  358. throw new Exception("No inspector exists for the provided field type.");
  359. field.Initialize(layoutIndex);
  360. field.Refresh(layoutIndex);
  361. return field;
  362. }
  363. }
  364. /** @} */
  365. }