InspectableArray.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Text;
  6. using BansheeEngine;
  7. namespace BansheeEditor
  8. {
  9. /// <summary>
  10. /// Displays GUI for a serializable property containing an array. Array contents are displayed as rows of entries
  11. /// that can be shown, hidden or manipulated.
  12. /// </summary>
  13. public class InspectableArray : InspectableField
  14. {
  15. /// <summary>
  16. /// Contains GUI elements for a single entry in the array.
  17. /// </summary>
  18. private class EntryRow
  19. {
  20. public GUILayoutY contentLayout;
  21. private GUILayoutX rowLayout;
  22. private GUILayoutX titleLayout;
  23. private bool ownsTitleLayout;
  24. /// <summary>
  25. /// Constructs a new entry row object.
  26. /// </summary>
  27. /// <param name="parentLayout">Parent layout that row GUI elements will be added to.</param>
  28. public EntryRow(GUILayout parentLayout)
  29. {
  30. rowLayout = parentLayout.AddLayoutX();
  31. contentLayout = rowLayout.AddLayoutY();
  32. }
  33. /// <summary>
  34. /// Recreates all row GUI elements.
  35. /// </summary>
  36. /// <param name="child">Inspectable field of the array entry.</param>
  37. /// <param name="seqIndex">Sequential index of the array entry.</param>
  38. /// <param name="parent">Parent array object that the entry is contained in.</param>
  39. public void Refresh(InspectableField child, int seqIndex, InspectableArray parent)
  40. {
  41. if (ownsTitleLayout || (titleLayout != null && titleLayout == child.GetTitleLayout()))
  42. return;
  43. titleLayout = child.GetTitleLayout();
  44. if (titleLayout == null)
  45. {
  46. GUILayoutY buttonCenter = rowLayout.AddLayoutY();
  47. buttonCenter.AddFlexibleSpace();
  48. titleLayout = buttonCenter.AddLayoutX();
  49. buttonCenter.AddFlexibleSpace();
  50. ownsTitleLayout = true;
  51. }
  52. GUIButton cloneBtn = new GUIButton("C", GUIOption.FixedWidth(20));
  53. GUIButton deleteBtn = new GUIButton("X", GUIOption.FixedWidth(20));
  54. GUIButton moveUpBtn = new GUIButton("U", GUIOption.FixedWidth(20));
  55. GUIButton moveDownBtn = new GUIButton("D", GUIOption.FixedWidth(20));
  56. cloneBtn.OnClick += () => parent.OnCloneButtonClicked(seqIndex);
  57. deleteBtn.OnClick += () => parent.OnDeleteButtonClicked(seqIndex);
  58. moveUpBtn.OnClick += () => parent.OnMoveUpButtonClicked(seqIndex);
  59. moveDownBtn.OnClick += () => parent.OnMoveDownButtonClicked(seqIndex);
  60. titleLayout.AddElement(cloneBtn);
  61. titleLayout.AddElement(deleteBtn);
  62. titleLayout.AddElement(moveUpBtn);
  63. titleLayout.AddElement(moveDownBtn);
  64. }
  65. /// <summary>
  66. /// Destroys all row GUI elements.
  67. /// </summary>
  68. public void Destroy()
  69. {
  70. rowLayout.Destroy();
  71. }
  72. }
  73. private const int IndentAmount = 5;
  74. private object propertyValue; // TODO - This will unnecessarily hold references to the object
  75. private int numArrayElements;
  76. private List<EntryRow> rows = new List<EntryRow>();
  77. private GUIIntField guiSizeField;
  78. private GUILayoutX guiChildLayout;
  79. private GUILayoutX guiTitleLayout;
  80. private bool isExpanded;
  81. private bool forceUpdate = true;
  82. /// <summary>
  83. /// Creates a new inspectable array GUI for the specified property.
  84. /// </summary>
  85. /// <param name="title">Name of the property, or some other value to set as the title.</param>
  86. /// <param name="depth">Determines how deep within the inspector nesting hierarchy is this field.Some fields may
  87. /// contain other fields, in which case you should increase this value by one.</param>
  88. /// <param name="layout">Parent layout that all the field elements will be added to.</param>
  89. /// <param name="property">Serializable property referencing the array whose contents to display.</param>
  90. public InspectableArray(string title, int depth, InspectableFieldLayout layout, SerializableProperty property)
  91. : base(title, depth, layout, property)
  92. {
  93. }
  94. /// <inheritdoc/>
  95. public override GUILayoutX GetTitleLayout()
  96. {
  97. return guiTitleLayout;
  98. }
  99. /// <inheritdoc/>
  100. protected override bool IsModified()
  101. {
  102. if (forceUpdate)
  103. return true;
  104. object newPropertyValue = property.GetValue<object>();
  105. if (propertyValue == null)
  106. return newPropertyValue != null;
  107. if (newPropertyValue == null)
  108. return propertyValue != null;
  109. SerializableArray array = property.GetArray();
  110. if (array.GetLength() != numArrayElements)
  111. return true;
  112. return base.IsModified();
  113. }
  114. /// <inheritdoc/>
  115. public override bool Refresh(int layoutIndex)
  116. {
  117. bool anythingModified = false;
  118. if (IsModified())
  119. {
  120. Update(layoutIndex);
  121. anythingModified = true;
  122. }
  123. for (int i = 0; i < ChildCount; i++)
  124. {
  125. InspectableField child = GetChild(i);
  126. bool childModified = child.Refresh(0);
  127. if (childModified)
  128. rows[i].Refresh(child, i, this);
  129. anythingModified |= childModified;
  130. }
  131. return anythingModified;
  132. }
  133. /// <inheritdoc/>
  134. protected override void Update(int layoutIndex)
  135. {
  136. base.Update(layoutIndex);
  137. forceUpdate = false;
  138. guiTitleLayout = null;
  139. if (property.Type != SerializableProperty.FieldType.Array || property.InternalType.GetArrayRank() != 1) // We don't support multirank arrays
  140. return;
  141. foreach (var row in rows)
  142. row.Destroy();
  143. rows.Clear();
  144. layout.DestroyElements();
  145. propertyValue = property.GetValue<object>();
  146. if (propertyValue == null)
  147. {
  148. guiChildLayout = null;
  149. guiTitleLayout = layout.AddLayoutX(layoutIndex);
  150. guiTitleLayout.AddElement(new GUILabel(title));
  151. guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
  152. if (!property.IsValueType)
  153. {
  154. GUIButton createBtn = new GUIButton("Cr", GUIOption.FixedWidth(20));
  155. createBtn.OnClick += OnCreateButtonClicked;
  156. guiTitleLayout.AddElement(createBtn);
  157. }
  158. numArrayElements = 0;
  159. }
  160. else
  161. {
  162. GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
  163. guiFoldout.Value = isExpanded;
  164. guiFoldout.OnToggled += OnFoldoutToggled;
  165. guiSizeField = new GUIIntField("", GUIOption.FixedWidth(50));
  166. guiSizeField.SetRange(0, int.MaxValue);
  167. GUIButton guiResizeBtn = new GUIButton("R", GUIOption.FixedWidth(20));
  168. guiResizeBtn.OnClick += OnResizeButtonClicked;
  169. GUIButton guiClearBtn = new GUIButton("Cl", GUIOption.FixedWidth(20));
  170. guiClearBtn.OnClick += OnClearButtonClicked;
  171. guiTitleLayout = layout.AddLayoutX(layoutIndex);
  172. guiTitleLayout.AddElement(guiFoldout);
  173. guiTitleLayout.AddElement(guiSizeField);
  174. guiTitleLayout.AddElement(guiResizeBtn);
  175. guiTitleLayout.AddElement(guiClearBtn);
  176. SerializableArray array = property.GetArray();
  177. numArrayElements = array.GetLength();
  178. guiSizeField.Value = numArrayElements;
  179. if (isExpanded)
  180. {
  181. guiChildLayout = layout.AddLayoutX(layoutIndex);
  182. guiChildLayout.AddSpace(IndentAmount);
  183. GUIPanel guiContentPanel = guiChildLayout.AddPanel();
  184. GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX();
  185. guiIndentLayoutX.AddSpace(IndentAmount);
  186. GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY();
  187. guiIndentLayoutY.AddSpace(IndentAmount);
  188. GUILayoutY guiContentLayout = guiIndentLayoutY.AddLayoutY();
  189. guiIndentLayoutY.AddSpace(IndentAmount);
  190. guiIndentLayoutX.AddSpace(IndentAmount);
  191. guiChildLayout.AddSpace(IndentAmount);
  192. short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1);
  193. string bgPanelStyle = depth % 2 == 0 ? EditorStyles.InspectorContentBgAlternate : EditorStyles.InspectorContentBg;
  194. GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth);
  195. GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
  196. backgroundPanel.AddElement(inspectorContentBg);
  197. for (int i = 0; i < numArrayElements; i++)
  198. {
  199. EntryRow newRow = new EntryRow(guiContentLayout);
  200. rows.Add(newRow);
  201. InspectableField childObj = CreateInspectable(i + ".", depth + 1, new InspectableFieldLayout(newRow.contentLayout), array.GetProperty(i));
  202. AddChild(childObj);
  203. childObj.Refresh(0);
  204. rows[i].Refresh(childObj, i, this);
  205. }
  206. }
  207. else
  208. guiChildLayout = null;
  209. }
  210. }
  211. /// <summary>
  212. /// Triggered when the user clicks on the expand/collapse toggle in the title bar.
  213. /// </summary>
  214. /// <param name="expanded">Determines whether the contents were expanded or collapsed.</param>
  215. private void OnFoldoutToggled(bool expanded)
  216. {
  217. isExpanded = expanded;
  218. forceUpdate = true;
  219. }
  220. /// <summary>
  221. /// Triggered when the user clicks on the resize button on the title bar. Changes the size of the array while
  222. /// preserving existing contents.
  223. /// </summary>
  224. private void OnResizeButtonClicked()
  225. {
  226. int size = guiSizeField.Value; // TODO - Support multi-rank arrays
  227. Array newArray = property.CreateArrayInstance(new int[] {size});
  228. Array array = property.GetValue<Array>();
  229. int maxSize = MathEx.Min(size, array.Length);
  230. for (int i = 0; i < maxSize; i++)
  231. newArray.SetValue(array.GetValue(i), i);
  232. property.SetValue(newArray);
  233. }
  234. /// <summary>
  235. /// Triggered when the user clicks on the delete button next to the array entry. Deletes an element in the array.
  236. /// </summary>
  237. /// <param name="index">Sequential index of the element in the array to remove.</param>
  238. private void OnDeleteButtonClicked(int index)
  239. {
  240. Array array = property.GetValue<Array>();
  241. int size = MathEx.Max(0, array.Length - 1);
  242. Array newArray = property.CreateArrayInstance(new int[] { size });
  243. int destIdx = 0;
  244. for (int i = 0; i < array.Length; i++)
  245. {
  246. if (i == index)
  247. continue;
  248. newArray.SetValue(array.GetValue(i), destIdx);
  249. destIdx++;
  250. }
  251. property.SetValue(newArray);
  252. }
  253. /// <summary>
  254. /// Triggered when the user clicks on the clone button next to the array entry. Clones an element in the array and
  255. /// adds the clone to the back of the array.
  256. /// </summary>
  257. /// <param name="index">Sequential index of the element in the array to clone.</param>
  258. private void OnCloneButtonClicked(int index)
  259. {
  260. SerializableArray array = property.GetArray();
  261. int size = array.GetLength() + 1;
  262. Array newArray = property.CreateArrayInstance(new int[] { size });
  263. object clonedEntry = null;
  264. for (int i = 0; i < array.GetLength(); i++)
  265. {
  266. object value = array.GetProperty(i).GetValue<object>();
  267. newArray.SetValue(value, i);
  268. if (i == index)
  269. {
  270. clonedEntry = array.GetProperty(i).GetValueCopy<object>();
  271. }
  272. }
  273. newArray.SetValue(clonedEntry, size - 1);
  274. property.SetValue(newArray);
  275. }
  276. /// <summary>
  277. /// Triggered when the user clicks on the move up button next to the array entry. Moves an element from the current
  278. /// array index to the one right before it, if not at zero.
  279. /// </summary>
  280. /// <param name="index">Sequential index of the element in the array to move.</param>
  281. private void OnMoveUpButtonClicked(int index)
  282. {
  283. Array array = property.GetValue<Array>();
  284. if ((index - 1) >= 0)
  285. {
  286. object previousEntry = array.GetValue(index - 1);
  287. array.SetValue(array.GetValue(index), index - 1);
  288. array.SetValue(previousEntry, index);
  289. }
  290. }
  291. /// <summary>
  292. /// Triggered when the user clicks on the move down button next to the array entry. Moves an element from the current
  293. /// array index to the one right after it, if the element isn't already the last element.
  294. /// </summary>
  295. /// <param name="index">Sequential index of the element in the array to move.</param>
  296. private void OnMoveDownButtonClicked(int index)
  297. {
  298. Array array = property.GetValue<Array>();
  299. if ((index + 1) < array.Length)
  300. {
  301. object nextEntry = array.GetValue(index + 1);
  302. array.SetValue(array.GetValue(index), index + 1);
  303. array.SetValue(nextEntry, index);
  304. }
  305. }
  306. /// <summary>
  307. /// Triggered when the user clicks on the create button on the title bar. Creates a brand new array with zero
  308. /// elements in the place of the current array.
  309. /// </summary>
  310. private void OnCreateButtonClicked()
  311. {
  312. property.SetValue(property.CreateArrayInstance(new int[1] { 0 }));
  313. }
  314. /// <summary>
  315. /// Triggered when the user clicks on the clear button on the title bar. Deletes the current array and sets
  316. /// the reference to the array in the parent object to null.
  317. /// </summary>
  318. private void OnClearButtonClicked()
  319. {
  320. property.SetValue<object>(null);
  321. }
  322. }
  323. }