InspectableList.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Text;
  7. using bs;
  8. namespace bs.Editor
  9. {
  10. /** @addtogroup Inspector
  11. * @{
  12. */
  13. /// <summary>
  14. /// Displays GUI for a serializable property containing a list. List contents are displayed as rows of entries
  15. /// that can be shown, hidden or manipulated.
  16. /// </summary>
  17. public class InspectableList : InspectableField
  18. {
  19. private InspectableListGUI listGUIField;
  20. /// <summary>
  21. /// Creates a new inspectable list GUI for the specified property.
  22. /// </summary>
  23. /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
  24. /// <param name="title">Name of the property, or some other value to set as the title.</param>
  25. /// <param name="path">Full path to this property (includes name of this property and all parent properties).</param>
  26. /// <param name="depth">Determines how deep within the inspector nesting hierarchy is this field. Some fields may
  27. /// contain other fields, in which case you should increase this value by one.</param>
  28. /// <param name="layout">Parent layout that all the field elements will be added to.</param>
  29. /// <param name="property">Serializable property referencing the list whose contents to display.</param>
  30. public InspectableList(InspectableContext context, string title, string path, int depth, InspectableFieldLayout layout,
  31. SerializableProperty property)
  32. : base(context, title, path, SerializableProperty.FieldType.List, depth, layout, property)
  33. {
  34. }
  35. /// <inheritdoc/>
  36. public override GUILayoutX GetTitleLayout()
  37. {
  38. return listGUIField.GetTitleLayout();
  39. }
  40. /// <inheritdoc/>
  41. public override InspectableState Refresh(int layoutIndex)
  42. {
  43. return listGUIField.Refresh();
  44. }
  45. /// <inheritdoc/>
  46. protected internal override void Initialize(int layoutIndex)
  47. {
  48. GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
  49. listGUIField = InspectableListGUI.Create(context, title, path, property, arrayLayout, depth);
  50. listGUIField.IsExpanded = context.Persistent.GetBool(path + "_Expanded");
  51. listGUIField.OnExpand += x => context.Persistent.SetBool(path + "_Expanded", x);
  52. }
  53. /// <inheritdoc />
  54. public override InspectableField FindPath(string path)
  55. {
  56. string subPath = GetSubPath(path, depth + 1);
  57. if (string.IsNullOrEmpty(subPath))
  58. return null;
  59. int lastLeftIdx = subPath.LastIndexOf('[');
  60. int lastRightIdx = subPath.LastIndexOf(']', lastLeftIdx);
  61. if (lastLeftIdx == -1 || lastRightIdx == -1)
  62. return null;
  63. int count = lastRightIdx - 1 - lastLeftIdx;
  64. if (count <= 0)
  65. return null;
  66. string arrayIdxStr = subPath.Substring(lastLeftIdx, count);
  67. if (!int.TryParse(arrayIdxStr, out int idx))
  68. return null;
  69. if (idx >= listGUIField.NumRows)
  70. return null;
  71. InspectableListGUIRow row = listGUIField.GetRow(idx);
  72. InspectableField field = row?.Field;
  73. if (field != null)
  74. {
  75. if (field.Path == path)
  76. return field;
  77. return field.FindPath(path);
  78. }
  79. return null;
  80. }
  81. /// <summary>
  82. /// Handles creation of GUI elements for a GUI list field that displays a <see cref="SerializableList"/> object.
  83. /// </summary>
  84. private class InspectableListGUI : GUIListFieldBase
  85. {
  86. private IList list;
  87. private int numElements;
  88. private InspectableContext context;
  89. private SerializableProperty property;
  90. private string path;
  91. /// <summary>
  92. /// Context shared by all inspectable fields created by the same parent.
  93. /// </summary>
  94. public InspectableContext Context
  95. {
  96. get { return context; }
  97. }
  98. /// <summary>
  99. /// Returns a property path to the array field (name of the array field and all parent object fields).
  100. /// </summary>
  101. public string Path
  102. {
  103. get { return path; }
  104. }
  105. /// <summary>
  106. /// Returns the number of rows in the array.
  107. /// </summary>
  108. public int NumRows
  109. {
  110. get { return GetNumRows(); }
  111. }
  112. /// <summary>
  113. /// Constructs a new empty inspectable list GUI.
  114. /// </summary>
  115. /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
  116. /// <param name="title">Label to display on the list GUI title.</param>
  117. /// <param name="path">Full path to this property (includes name of this property and all parent properties).
  118. /// </param>
  119. /// <param name="property">Serializable property referencing a list.</param>
  120. /// <param name="layout">Layout to which to append the list GUI elements to.</param>
  121. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  122. /// nested containers whose backgrounds are overlaping. Also determines background style,
  123. /// depths divisible by two will use an alternate style.</param>
  124. public InspectableListGUI(InspectableContext context, LocString title, string path,
  125. SerializableProperty property, GUILayout layout, int depth)
  126. : base(title, layout, depth)
  127. {
  128. this.property = property;
  129. this.context = context;
  130. this.path = path;
  131. list = property.GetValue<IList>();
  132. if (list != null)
  133. numElements = list.Count;
  134. }
  135. /// <summary>
  136. /// Creates a new inspectable list GUI object that displays the contents of the provided serializable property.
  137. /// </summary>
  138. /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
  139. /// <param name="title">Label to display on the list GUI title.</param>
  140. /// <param name="path">Full path to this property (includes name of this property and all parent properties).
  141. /// </param>
  142. /// <param name="property">Serializable property referencing a list.</param>
  143. /// <param name="layout">Layout to which to append the list GUI elements to.</param>
  144. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  145. /// nested containers whose backgrounds are overlaping. Also determines background style,
  146. /// depths divisible by two will use an alternate style.</param>
  147. public static InspectableListGUI Create(InspectableContext context, LocString title, string path,
  148. SerializableProperty property, GUILayout layout, int depth)
  149. {
  150. InspectableListGUI listGUI = new InspectableListGUI(context, title, path, property, layout, depth);
  151. listGUI.BuildGUI();
  152. return listGUI;
  153. }
  154. /// <summary>
  155. /// Returns a list row at the specified index.
  156. /// </summary>
  157. /// <param name="idx">Index of the row.</param>
  158. /// <returns>List row representation or null if index is out of range.</returns>
  159. public InspectableListGUIRow GetRow(int idx)
  160. {
  161. if (idx < GetNumRows())
  162. return (InspectableListGUIRow)rows[idx];
  163. return null;
  164. }
  165. /// <inheritdoc/>
  166. public override InspectableState Refresh()
  167. {
  168. // Check if any modifications to the array were made outside the inspector
  169. IList newList = property.GetValue<IList>();
  170. if (list == null && newList != null)
  171. {
  172. list = newList;
  173. numElements = list.Count;
  174. BuildGUI();
  175. }
  176. else if (newList == null && list != null)
  177. {
  178. list = null;
  179. numElements = 0;
  180. BuildGUI();
  181. }
  182. else
  183. {
  184. if (list != null)
  185. {
  186. if (numElements != list.Count)
  187. {
  188. numElements = list.Count;
  189. BuildGUI();
  190. }
  191. }
  192. }
  193. return base.Refresh();
  194. }
  195. /// <inheritdoc/>
  196. protected override GUIListFieldRow CreateRow()
  197. {
  198. return new InspectableListGUIRow();
  199. }
  200. /// <inheritdoc/>
  201. protected override bool IsNull()
  202. {
  203. return list == null;
  204. }
  205. /// <inheritdoc/>
  206. protected override int GetNumRows()
  207. {
  208. if (list != null)
  209. return list.Count;
  210. return 0;
  211. }
  212. /// <inheritdoc/>
  213. protected internal override object GetValue(int seqIndex)
  214. {
  215. SerializableList list = property.GetList();
  216. return list.GetProperty(seqIndex);
  217. }
  218. /// <inheritdoc/>
  219. protected internal override void SetValue(int seqIndex, object value)
  220. {
  221. // Setting the value should be done through the property
  222. throw new InvalidOperationException();
  223. }
  224. /// <inheritdoc/>
  225. protected override void CreateList()
  226. {
  227. StartUndo();
  228. list = property.CreateListInstance(0);
  229. property.SetValue(list);
  230. numElements = 0;
  231. EndUndo();
  232. }
  233. /// <inheritdoc/>
  234. protected override void ResizeList()
  235. {
  236. StartUndo();
  237. int size = guiSizeField.Value;
  238. IList newList = property.CreateListInstance(size);
  239. int maxSize = MathEx.Min(size, list.Count);
  240. for (int i = 0; i < maxSize; i++)
  241. newList[i] = list[i];
  242. property.SetValue(newList);
  243. list = newList;
  244. numElements = list.Count;
  245. EndUndo();
  246. }
  247. /// <inheritdoc/>
  248. protected override void ClearList()
  249. {
  250. StartUndo();
  251. property.SetValue<object>(null);
  252. list = null;
  253. numElements = 0;
  254. EndUndo();
  255. }
  256. /// <inheritdoc/>
  257. protected internal override void DeleteElement(int index)
  258. {
  259. StartUndo();
  260. if (index >= 0 && index < list.Count)
  261. list.RemoveAt(index);
  262. numElements = list.Count;
  263. EndUndo();
  264. }
  265. /// <inheritdoc/>
  266. protected internal override void CloneElement(int index)
  267. {
  268. StartUndo();
  269. SerializableList serializableList = property.GetList();
  270. if (index >= 0 && index < list.Count)
  271. list.Add(SerializableUtility.Clone(serializableList.GetProperty(index).GetValue<object>()));
  272. numElements = list.Count;
  273. EndUndo();
  274. }
  275. /// <inheritdoc/>
  276. protected internal override void MoveUpElement(int index)
  277. {
  278. StartUndo();
  279. if ((index - 1) >= 0)
  280. {
  281. object previousEntry = list[index - 1];
  282. list[index - 1] = list[index];
  283. list[index] = previousEntry;
  284. }
  285. EndUndo();
  286. }
  287. /// <inheritdoc/>
  288. protected internal override void MoveDownElement(int index)
  289. {
  290. StartUndo();
  291. if ((index + 1) < list.Count)
  292. {
  293. object nextEntry = list[index + 1];
  294. list[index + 1] = list[index];
  295. list[index] = nextEntry;
  296. }
  297. EndUndo();
  298. }
  299. /// <summary>
  300. /// Notifies the system to start recording a new undo command. Any changes to the field after this is called
  301. /// will be recorded in the command. User must call <see cref="EndUndo"/> after field is done being changed.
  302. /// </summary>
  303. protected void StartUndo()
  304. {
  305. if (context.Component != null)
  306. GameObjectUndo.RecordComponent(context.Component, path);
  307. }
  308. /// <summary>
  309. /// Finishes recording an undo command started via <see cref="StartUndo"/>. If any changes are detected on the
  310. /// field an undo command is recorded onto the undo-redo stack, otherwise nothing is done.
  311. /// </summary>
  312. protected void EndUndo()
  313. {
  314. GameObjectUndo.ResolveDiffs();
  315. }
  316. }
  317. /// <summary>
  318. /// Contains GUI elements for a single entry in the list.
  319. /// </summary>
  320. private class InspectableListGUIRow : GUIListFieldRow
  321. {
  322. /// <summary>
  323. /// Inspectable field displayed on the list row.
  324. /// </summary>
  325. public InspectableField Field { get; private set; }
  326. /// <inheritdoc/>
  327. protected override GUILayoutX CreateGUI(GUILayoutY layout)
  328. {
  329. InspectableListGUI listParent = (InspectableListGUI)parent;
  330. SerializableProperty property = GetValue<SerializableProperty>();
  331. string entryPath = listParent.Path + "[" + SeqIndex + "]";
  332. Field = CreateField(listParent.Context, SeqIndex + ".", entryPath, 0, Depth + 1,
  333. new InspectableFieldLayout(layout), property, new InspectableFieldStyleInfo());
  334. return Field.GetTitleLayout();
  335. }
  336. /// <inheritdoc/>
  337. protected internal override InspectableState Refresh()
  338. {
  339. Field.Property = GetValue<SerializableProperty>();
  340. return Field.Refresh(0);
  341. }
  342. }
  343. }
  344. /** @} */
  345. }