InspectableList.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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, bool force = false)
  42. {
  43. return listGUIField.Refresh(force);
  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(bool force)
  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(true);
  175. }
  176. else if (newList == null && list != null)
  177. {
  178. list = null;
  179. numElements = 0;
  180. BuildGUI(true);
  181. }
  182. else
  183. {
  184. if (newList != null)
  185. {
  186. if (numElements != newList.Count || force)
  187. {
  188. list = newList;
  189. numElements = list.Count;
  190. BuildGUI(true);
  191. }
  192. }
  193. }
  194. return base.Refresh(force);
  195. }
  196. /// <inheritdoc/>
  197. protected override GUIListFieldRow CreateRow()
  198. {
  199. return new InspectableListGUIRow();
  200. }
  201. /// <inheritdoc/>
  202. protected override bool IsNull()
  203. {
  204. return list == null;
  205. }
  206. /// <inheritdoc/>
  207. protected override int GetNumRows()
  208. {
  209. if (list != null)
  210. return list.Count;
  211. return 0;
  212. }
  213. /// <inheritdoc/>
  214. protected internal override object GetValue(int seqIndex)
  215. {
  216. SerializableList list = property.GetList();
  217. return list.GetProperty(seqIndex);
  218. }
  219. /// <inheritdoc/>
  220. protected internal override void SetValue(int seqIndex, object value)
  221. {
  222. // Setting the value should be done through the property
  223. throw new InvalidOperationException();
  224. }
  225. /// <inheritdoc/>
  226. protected override void CreateList()
  227. {
  228. StartUndo();
  229. list = property.CreateListInstance(0);
  230. property.SetValue(list);
  231. numElements = 0;
  232. EndUndo();
  233. }
  234. /// <inheritdoc/>
  235. protected override void ResizeList()
  236. {
  237. StartUndo();
  238. int size = guiSizeField.Value;
  239. IList newList = property.CreateListInstance(size);
  240. int maxSize = MathEx.Min(size, list.Count);
  241. for (int i = 0; i < maxSize; i++)
  242. newList[i] = list[i];
  243. property.SetValue(newList);
  244. list = newList;
  245. numElements = list.Count;
  246. EndUndo();
  247. }
  248. /// <inheritdoc/>
  249. protected override void ClearList()
  250. {
  251. StartUndo();
  252. property.SetValue<object>(null);
  253. list = null;
  254. numElements = 0;
  255. EndUndo();
  256. }
  257. /// <inheritdoc/>
  258. protected internal override void DeleteElement(int index)
  259. {
  260. StartUndo();
  261. if (index >= 0 && index < list.Count)
  262. list.RemoveAt(index);
  263. numElements = list.Count;
  264. EndUndo();
  265. }
  266. /// <inheritdoc/>
  267. protected internal override void CloneElement(int index)
  268. {
  269. StartUndo();
  270. SerializableList serializableList = property.GetList();
  271. if (index >= 0 && index < list.Count)
  272. list.Add(SerializableUtility.Clone(serializableList.GetProperty(index).GetValue<object>()));
  273. numElements = list.Count;
  274. EndUndo();
  275. }
  276. /// <inheritdoc/>
  277. protected internal override void MoveUpElement(int index)
  278. {
  279. StartUndo();
  280. if ((index - 1) >= 0)
  281. {
  282. object previousEntry = list[index - 1];
  283. list[index - 1] = list[index];
  284. list[index] = previousEntry;
  285. }
  286. EndUndo();
  287. }
  288. /// <inheritdoc/>
  289. protected internal override void MoveDownElement(int index)
  290. {
  291. StartUndo();
  292. if ((index + 1) < list.Count)
  293. {
  294. object nextEntry = list[index + 1];
  295. list[index + 1] = list[index];
  296. list[index] = nextEntry;
  297. }
  298. EndUndo();
  299. }
  300. /// <summary>
  301. /// Notifies the system to start recording a new undo command. Any changes to the field after this is called
  302. /// will be recorded in the command. User must call <see cref="EndUndo"/> after field is done being changed.
  303. /// </summary>
  304. protected void StartUndo()
  305. {
  306. if (context.Component != null)
  307. GameObjectUndo.RecordComponent(context.Component, path);
  308. }
  309. /// <summary>
  310. /// Finishes recording an undo command started via <see cref="StartUndo"/>. If any changes are detected on the
  311. /// field an undo command is recorded onto the undo-redo stack, otherwise nothing is done.
  312. /// </summary>
  313. protected void EndUndo()
  314. {
  315. GameObjectUndo.ResolveDiffs();
  316. }
  317. }
  318. /// <summary>
  319. /// Contains GUI elements for a single entry in the list.
  320. /// </summary>
  321. private class InspectableListGUIRow : GUIListFieldRow
  322. {
  323. /// <summary>
  324. /// Inspectable field displayed on the list row.
  325. /// </summary>
  326. public InspectableField Field { get; private set; }
  327. /// <inheritdoc/>
  328. protected override GUILayoutX CreateGUI(GUILayoutY layout)
  329. {
  330. InspectableListGUI listParent = (InspectableListGUI)parent;
  331. SerializableProperty property = GetValue<SerializableProperty>();
  332. string entryPath = listParent.Path + "[" + SeqIndex + "]";
  333. Field = CreateField(listParent.Context, SeqIndex + ".", entryPath, 0, Depth + 1,
  334. new InspectableFieldLayout(layout), property, new InspectableFieldStyleInfo());
  335. return Field.GetTitleLayout();
  336. }
  337. /// <inheritdoc/>
  338. protected internal override InspectableState Refresh(bool force)
  339. {
  340. Field.Property = GetValue<SerializableProperty>();
  341. return Field.Refresh(0, force);
  342. }
  343. }
  344. }
  345. /** @} */
  346. }