InspectableArray.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. using System;
  4. using System.Text;
  5. using bs;
  6. namespace bs.Editor
  7. {
  8. /** @addtogroup Inspector
  9. * @{
  10. */
  11. /// <summary>
  12. /// Displays GUI for a serializable property containing an array. Array contents are displayed as rows of entries
  13. /// that can be shown, hidden or manipulated.
  14. /// </summary>
  15. public class InspectableArray : InspectableField
  16. {
  17. private InspectableArrayGUI arrayGUIField;
  18. private InspectableFieldStyleInfo style;
  19. /// <summary>
  20. /// Style applied to the elements of the array and the array itself.
  21. /// </summary>
  22. internal InspectableFieldStyleInfo Style
  23. {
  24. get { return style; }
  25. }
  26. /// <summary>
  27. /// Creates a new inspectable array GUI for the specified property.
  28. /// </summary>
  29. /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
  30. /// <param name="title">Name of the property, or some other value to set as the title.</param>
  31. /// <param name="path">Full path to this property (includes name of this property and all parent properties).</param>
  32. /// <param name="depth">Determines how deep within the inspector nesting hierarchy is this field. Some fields may
  33. /// contain other fields, in which case you should increase this value by one.</param>
  34. /// <param name="layout">Parent layout that all the field elements will be added to.</param>
  35. /// <param name="property">Serializable property referencing the array whose contents to display.</param>
  36. /// <param name="style">Information that can be used for customizing field rendering and behaviour.</param>
  37. public InspectableArray(InspectableContext context, string title, string path, int depth, InspectableFieldLayout layout,
  38. SerializableProperty property, InspectableFieldStyleInfo style)
  39. : base(context, title, path, SerializableProperty.FieldType.Array, depth, layout, property)
  40. {
  41. this.style = style;
  42. }
  43. /// <inheritdoc/>
  44. public override GUILayoutX GetTitleLayout()
  45. {
  46. return arrayGUIField.GetTitleLayout();
  47. }
  48. /// <inheritdoc/>
  49. public override InspectableState Refresh(int layoutIndex)
  50. {
  51. return arrayGUIField.Refresh();
  52. }
  53. /// <inheritdoc/>
  54. protected internal override void Initialize(int layoutIndex)
  55. {
  56. GUILayout arrayLayout = layout.AddLayoutY(layoutIndex);
  57. arrayGUIField = InspectableArrayGUI.Create(context, title, path, property, arrayLayout, depth, style);
  58. arrayGUIField.IsExpanded = context.Persistent.GetBool(path + "_Expanded");
  59. arrayGUIField.OnExpand += x => context.Persistent.SetBool(path + "_Expanded", x);
  60. }
  61. /// <inheritdoc />
  62. public override InspectableField FindPath(string path)
  63. {
  64. string subPath = GetSubPath(path, depth + 1);
  65. if (string.IsNullOrEmpty(subPath))
  66. return null;
  67. int lastLeftIdx = subPath.LastIndexOf('[');
  68. int lastRightIdx = subPath.LastIndexOf(']', lastLeftIdx);
  69. if (lastLeftIdx == -1 || lastRightIdx == -1)
  70. return null;
  71. int count = lastRightIdx - 1 - lastLeftIdx;
  72. if (count <= 0)
  73. return null;
  74. string arrayIdxStr = subPath.Substring(lastLeftIdx, count);
  75. if (!int.TryParse(arrayIdxStr, out int idx))
  76. return null;
  77. if (idx >= arrayGUIField.NumRows)
  78. return null;
  79. InspectableArrayGUIRow row = arrayGUIField.GetRow(idx);
  80. InspectableField field = row?.Field;
  81. if (field != null)
  82. {
  83. if (field.Path == path)
  84. return field;
  85. return field.FindPath(path);
  86. }
  87. return null;
  88. }
  89. /// <summary>
  90. /// Handles creation of GUI elements for a GUI list field that displays a <see cref="SerializableArray"/> object.
  91. /// </summary>
  92. private class InspectableArrayGUI : GUIListFieldBase
  93. {
  94. private Array array;
  95. private int numElements;
  96. private InspectableContext context;
  97. private SerializableProperty property;
  98. private string path;
  99. private InspectableFieldStyleInfo style;
  100. /// <summary>
  101. /// Context shared by all inspectable fields created by the same parent.
  102. /// </summary>
  103. public InspectableContext Context
  104. {
  105. get { return context; }
  106. }
  107. /// <summary>
  108. /// Returns a property path to the array field (name of the array field and all parent object fields).
  109. /// </summary>
  110. public string Path
  111. {
  112. get { return path; }
  113. }
  114. /// <summary>
  115. /// Style applied to the elements of the array and the array itself.
  116. /// </summary>
  117. public InspectableFieldStyleInfo Style
  118. {
  119. get { return style; }
  120. }
  121. /// <summary>
  122. /// Returns the number of rows in the array.
  123. /// </summary>
  124. public int NumRows
  125. {
  126. get { return GetNumRows(); }
  127. }
  128. /// <summary>
  129. /// Constructs a new inspectable array GUI.
  130. /// </summary>
  131. /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
  132. /// <param name="title">Label to display on the list GUI title.</param>
  133. /// <param name="path">Full path to this property (includes name of this property and all parent properties).
  134. /// </param>
  135. /// <param name="property">Serializable property referencing a single-dimensional array.</param>
  136. /// <param name="layout">Layout to which to append the list GUI elements to.</param>
  137. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  138. /// nested containers whose backgrounds are overlaping. Also determines background style,
  139. /// depths divisible by two will use an alternate style.</param>
  140. /// <param name="style">Information that can be used for customizing field rendering and behaviour.</param>
  141. public InspectableArrayGUI(InspectableContext context, LocString title, string path, SerializableProperty property,
  142. GUILayout layout, int depth, InspectableFieldStyleInfo style)
  143. : base(title, layout, depth)
  144. {
  145. this.property = property;
  146. this.context = context;
  147. this.path = path;
  148. this.style = style;
  149. array = property.GetValue<Array>();
  150. if (array != null)
  151. numElements = array.Length;
  152. }
  153. /// <summary>
  154. /// Creates a new inspectable array GUI object that displays the contents of the provided serializable property.
  155. /// </summary>
  156. /// <param name="context">Context shared by all inspectable fields created by the same parent.</param>
  157. /// <param name="title">Label to display on the list GUI title.</param>
  158. /// <param name="path">Full path to this property (includes name of this property and all parent properties).
  159. /// </param>
  160. /// <param name="property">Serializable property referencing a single-dimensional array.</param>
  161. /// <param name="layout">Layout to which to append the list GUI elements to.</param>
  162. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  163. /// nested containers whose backgrounds are overlaping. Also determines background style,
  164. /// depths divisible by two will use an alternate style.</param>
  165. /// <param name="style">Information that can be used for customizing field rendering and behaviour.</param>
  166. public static InspectableArrayGUI Create(InspectableContext context, LocString title, string path,
  167. SerializableProperty property, GUILayout layout, int depth, InspectableFieldStyleInfo style)
  168. {
  169. InspectableArrayGUI guiArray = new InspectableArrayGUI(context, title, path, property, layout, depth, style);
  170. guiArray.BuildGUI();
  171. return guiArray;
  172. }
  173. /// <summary>
  174. /// Returns an array row at the specified index.
  175. /// </summary>
  176. /// <param name="idx">Index of the row.</param>
  177. /// <returns>Array row representation or null if index is out of range.</returns>
  178. public InspectableArrayGUIRow GetRow(int idx)
  179. {
  180. if (idx < GetNumRows())
  181. return (InspectableArrayGUIRow)rows[idx];
  182. return null;
  183. }
  184. /// <inheritdoc/>
  185. public override InspectableState Refresh()
  186. {
  187. // Check if any modifications to the array were made outside the inspector
  188. Array newArray = property.GetValue<Array>();
  189. if (array == null && newArray != null)
  190. {
  191. array = newArray;
  192. numElements = array.Length;
  193. BuildGUI();
  194. }
  195. else if (newArray == null && array != null)
  196. {
  197. array = null;
  198. numElements = 0;
  199. BuildGUI();
  200. }
  201. else
  202. {
  203. if (array != null)
  204. {
  205. if (numElements != array.Length)
  206. {
  207. numElements = array.Length;
  208. BuildGUI();
  209. }
  210. }
  211. }
  212. return base.Refresh();
  213. }
  214. /// <inheritdoc/>
  215. protected override GUIListFieldRow CreateRow()
  216. {
  217. return new InspectableArrayGUIRow();
  218. }
  219. /// <inheritdoc/>
  220. protected override bool IsNull()
  221. {
  222. return array == null;
  223. }
  224. /// <inheritdoc/>
  225. protected override int GetNumRows()
  226. {
  227. if (array != null)
  228. return array.Length;
  229. return 0;
  230. }
  231. /// <inheritdoc/>
  232. protected internal override object GetValue(int seqIndex)
  233. {
  234. SerializableArray serzArray = property.GetArray();
  235. // Create a property wrapper for native arrays so we get notified when the array values change
  236. if (style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NativeWrapper))
  237. {
  238. SerializableProperty.Getter getter = () =>
  239. {
  240. Array array = property.GetValue<Array>();
  241. if (array != null)
  242. return array.GetValue(seqIndex);
  243. else
  244. return null;
  245. };
  246. SerializableProperty.Setter setter = (object value) =>
  247. {
  248. Array array = property.GetValue<Array>();
  249. if (array != null)
  250. {
  251. array.SetValue(value, seqIndex);
  252. property.SetValue(array);
  253. }
  254. };
  255. return new SerializableProperty(serzArray.ElementPropertyType, serzArray.ElementType, getter, setter);
  256. }
  257. else
  258. return serzArray.GetProperty(seqIndex);
  259. }
  260. /// <inheritdoc/>
  261. protected internal override void SetValue(int seqIndex, object value)
  262. {
  263. // Setting the value should be done through the property
  264. throw new InvalidOperationException();
  265. }
  266. /// <inheritdoc/>
  267. protected override void CreateList()
  268. {
  269. RecordStateForUndo();
  270. array = property.CreateArrayInstance(new int[1] { 0 });
  271. property.SetValue(array);
  272. numElements = 0;
  273. }
  274. /// <inheritdoc/>
  275. protected override void ResizeList()
  276. {
  277. RecordStateForUndo();
  278. int size = guiSizeField.Value; // TODO - Support multi-rank arrays
  279. Array newArray = property.CreateArrayInstance(new int[] { size });
  280. int maxSize = MathEx.Min(size, array.Length);
  281. for (int i = 0; i < maxSize; i++)
  282. newArray.SetValue(array.GetValue(i), i);
  283. property.SetValue(newArray);
  284. array = newArray;
  285. numElements = size;
  286. }
  287. /// <inheritdoc/>
  288. protected override void ClearList()
  289. {
  290. // Native arrays cannot be set to null, so just clear their size to zero instead
  291. if (style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NativeWrapper))
  292. CreateList();
  293. else
  294. {
  295. RecordStateForUndo();
  296. property.SetValue<object>(null);
  297. array = null;
  298. numElements = 0;
  299. }
  300. }
  301. /// <inheritdoc/>
  302. protected internal override void DeleteElement(int index)
  303. {
  304. RecordStateForUndo();
  305. int size = MathEx.Max(0, array.Length - 1);
  306. Array newArray = property.CreateArrayInstance(new int[] { size });
  307. int destIdx = 0;
  308. for (int i = 0; i < array.Length; i++)
  309. {
  310. if (i == index)
  311. continue;
  312. newArray.SetValue(array.GetValue(i), destIdx);
  313. destIdx++;
  314. }
  315. property.SetValue(newArray);
  316. array = newArray;
  317. numElements = array.Length;
  318. }
  319. /// <inheritdoc/>
  320. protected internal override void CloneElement(int index)
  321. {
  322. RecordStateForUndo();
  323. SerializableArray array = property.GetArray();
  324. int size = array.GetLength() + 1;
  325. Array newArray = property.CreateArrayInstance(new int[] { size });
  326. object clonedEntry = null;
  327. for (int i = 0; i < array.GetLength(); i++)
  328. {
  329. object value = array.GetProperty(i).GetValue<object>();
  330. newArray.SetValue(value, i);
  331. if (i == index)
  332. {
  333. clonedEntry = SerializableUtility.Clone(array.GetProperty(i).GetValue<object>());
  334. }
  335. }
  336. newArray.SetValue(clonedEntry, size - 1);
  337. property.SetValue(newArray);
  338. this.array = newArray;
  339. numElements = newArray.Length;
  340. }
  341. /// <inheritdoc/>
  342. protected internal override void MoveUpElement(int index)
  343. {
  344. if ((index - 1) >= 0)
  345. {
  346. RecordStateForUndo();
  347. object previousEntry = array.GetValue(index - 1);
  348. array.SetValue(array.GetValue(index), index - 1);
  349. array.SetValue(previousEntry, index);
  350. // Natively wrapped arrays are passed by copy
  351. if(style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NativeWrapper))
  352. property.SetValue(array);
  353. }
  354. }
  355. /// <inheritdoc/>
  356. protected internal override void MoveDownElement(int index)
  357. {
  358. if ((index + 1) < array.Length)
  359. {
  360. RecordStateForUndo();
  361. object nextEntry = array.GetValue(index + 1);
  362. array.SetValue(array.GetValue(index), index + 1);
  363. array.SetValue(nextEntry, index);
  364. // Natively wrapped arrays are passed by copy
  365. if(style.StyleFlags.HasFlag(InspectableFieldStyleFlags.NativeWrapper))
  366. property.SetValue(array);
  367. }
  368. }
  369. /// <summary>
  370. /// Records the current state of the field for the purposes of undo/redo. Generally this should be called just
  371. /// before making changes to the field value.
  372. /// </summary>
  373. protected void RecordStateForUndo()
  374. {
  375. if (context.Component != null)
  376. UndoRedo.RecordSO(context.Component.SceneObject, false, "Field change: \"" + path + "\"");
  377. }
  378. }
  379. /// <summary>
  380. /// Contains GUI elements for a single entry in the array.
  381. /// </summary>
  382. private class InspectableArrayGUIRow : GUIListFieldRow
  383. {
  384. /// <summary>
  385. /// Inspectable field displayed on the array row.
  386. /// </summary>
  387. public InspectableField Field { get; private set; }
  388. /// <inheritdoc/>
  389. protected override GUILayoutX CreateGUI(GUILayoutY layout)
  390. {
  391. InspectableArrayGUI arrayParent = (InspectableArrayGUI)parent;
  392. SerializableProperty property = GetValue<SerializableProperty>();
  393. InspectableFieldStyleInfo styleInfo = arrayParent.Style.Clone();
  394. styleInfo.StyleFlags &= ~InspectableFieldStyleFlags.NativeWrapper;
  395. string entryPath = arrayParent.Path + "[" + SeqIndex + "]";
  396. Field = CreateField(arrayParent.Context, SeqIndex + ".", entryPath, 0, Depth + 1,
  397. new InspectableFieldLayout(layout), property, styleInfo);
  398. return Field.GetTitleLayout();
  399. }
  400. /// <inheritdoc/>
  401. protected internal override InspectableState Refresh()
  402. {
  403. Field.Property = GetValue<SerializableProperty>();
  404. return Field.Refresh(0);
  405. }
  406. }
  407. }
  408. /** @} */
  409. }