GUIListField.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using BansheeEngine;
  5. namespace BansheeEditor
  6. {
  7. /// <summary>
  8. /// Base class for objects that display GUI for a modifyable list of elements. Elements can be added, removed and moved.
  9. /// </summary>
  10. public abstract class GUIListFieldBase
  11. {
  12. private const int IndentAmount = 5;
  13. protected List<GUIListFieldRow> rows = new List<GUIListFieldRow>();
  14. protected GUILayoutY guiLayout;
  15. protected GUIIntField guiSizeField;
  16. protected GUILayoutX guiChildLayout;
  17. protected GUILayoutX guiTitleLayout;
  18. protected GUILayoutY guiContentLayout;
  19. protected bool isExpanded;
  20. protected int depth;
  21. protected LocString title;
  22. private State state;
  23. /// <summary>
  24. /// Constructs a new GUI list.
  25. /// </summary>
  26. /// <param name="title">Label to display on the list GUI title.</param>
  27. /// <param name="layout">Layout to which to append the array GUI elements to.</param>
  28. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  29. /// nested containers whose backgrounds are overlaping. Also determines background style,
  30. /// depths divisible by two will use an alternate style.</param>
  31. protected GUIListFieldBase(LocString title, GUILayout layout, int depth)
  32. {
  33. this.title = title;
  34. this.depth = depth;
  35. this.guiLayout = layout.AddLayoutY();
  36. }
  37. /// <summary>
  38. /// Builds the list GUI elements. Must be called at least once in order for the contents to be populated.
  39. /// </summary>
  40. protected void BuildGUI()
  41. {
  42. UpdateHeaderGUI(true);
  43. if (!IsNull())
  44. {
  45. // Hidden dependency: BuildGUI must be called after all elements are
  46. // in the dictionary so we do it in two steps
  47. int numRows = GetNumRows();
  48. for (int i = 0; i < numRows; i++)
  49. {
  50. GUIListFieldRow newRow = CreateRow();
  51. rows.Add(newRow);
  52. }
  53. for (int i = 0; i < numRows; i++)
  54. rows[i].BuildGUI(this, guiContentLayout, i, depth + 1);
  55. }
  56. }
  57. /// <summary>
  58. /// Rebuilds the GUI list header if needed.
  59. /// </summary>
  60. /// <param name="forceRebuild">Forces the header to be rebuilt.</param>
  61. protected void UpdateHeaderGUI(bool forceRebuild)
  62. {
  63. Action BuildEmptyGUI = () =>
  64. {
  65. guiTitleLayout = guiLayout.AddLayoutX();
  66. guiTitleLayout.AddElement(new GUILabel(title));
  67. guiTitleLayout.AddElement(new GUILabel("Empty", GUIOption.FixedWidth(100)));
  68. GUIContent createIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Create));
  69. GUIButton createBtn = new GUIButton(createIcon, GUIOption.FixedWidth(30));
  70. createBtn.OnClick += CreateList;
  71. guiTitleLayout.AddElement(createBtn);
  72. };
  73. Action BuildFilledGUI = () =>
  74. {
  75. GUIToggle guiFoldout = new GUIToggle(title, EditorStyles.Foldout);
  76. guiFoldout.Value = isExpanded;
  77. guiFoldout.OnToggled += OnFoldoutToggled;
  78. guiSizeField = new GUIIntField("", GUIOption.FixedWidth(50));
  79. guiSizeField.SetRange(0, int.MaxValue);
  80. GUIContent resizeIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Resize));
  81. GUIButton guiResizeBtn = new GUIButton(resizeIcon, GUIOption.FixedWidth(30));
  82. guiResizeBtn.OnClick += OnResizeButtonClicked;
  83. GUIContent clearIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clear));
  84. GUIButton guiClearBtn = new GUIButton(clearIcon, GUIOption.FixedWidth(30));
  85. guiClearBtn.OnClick += OnClearButtonClicked;
  86. guiTitleLayout = guiLayout.AddLayoutX();
  87. guiTitleLayout.AddElement(guiFoldout);
  88. guiTitleLayout.AddElement(guiSizeField);
  89. guiTitleLayout.AddElement(guiResizeBtn);
  90. guiTitleLayout.AddElement(guiClearBtn);
  91. guiSizeField.Value = GetNumRows();
  92. guiChildLayout = guiLayout.AddLayoutX();
  93. guiChildLayout.AddSpace(IndentAmount);
  94. guiChildLayout.Active = isExpanded;
  95. GUIPanel guiContentPanel = guiChildLayout.AddPanel();
  96. GUILayoutX guiIndentLayoutX = guiContentPanel.AddLayoutX();
  97. guiIndentLayoutX.AddSpace(IndentAmount);
  98. GUILayoutY guiIndentLayoutY = guiIndentLayoutX.AddLayoutY();
  99. guiIndentLayoutY.AddSpace(IndentAmount);
  100. guiContentLayout = guiIndentLayoutY.AddLayoutY();
  101. guiIndentLayoutY.AddSpace(IndentAmount);
  102. guiIndentLayoutX.AddSpace(IndentAmount);
  103. guiChildLayout.AddSpace(IndentAmount);
  104. short backgroundDepth = (short)(Inspector.START_BACKGROUND_DEPTH - depth - 1);
  105. string bgPanelStyle = depth % 2 == 0
  106. ? EditorStyles.InspectorContentBgAlternate
  107. : EditorStyles.InspectorContentBg;
  108. GUIPanel backgroundPanel = guiContentPanel.AddPanel(backgroundDepth);
  109. GUITexture inspectorContentBg = new GUITexture(null, bgPanelStyle);
  110. backgroundPanel.AddElement(inspectorContentBg);
  111. };
  112. if (forceRebuild)
  113. {
  114. if (state != State.None)
  115. guiTitleLayout.Destroy();
  116. if (state == State.Filled)
  117. guiChildLayout.Destroy();
  118. state = State.None;
  119. }
  120. if (state == State.None)
  121. {
  122. if (!IsNull())
  123. {
  124. BuildFilledGUI();
  125. state = State.Filled;
  126. }
  127. else
  128. {
  129. BuildEmptyGUI();
  130. state = State.Empty;
  131. }
  132. }
  133. else if (state == State.Empty)
  134. {
  135. if (!IsNull())
  136. {
  137. guiTitleLayout.Destroy();
  138. BuildFilledGUI();
  139. state = State.Filled;
  140. }
  141. }
  142. else if (state == State.Filled)
  143. {
  144. if (IsNull())
  145. {
  146. guiTitleLayout.Destroy();
  147. guiChildLayout.Destroy();
  148. BuildEmptyGUI();
  149. state = State.Empty;
  150. }
  151. }
  152. }
  153. /// <summary>
  154. /// Rebuilds GUI for all existing list rows.
  155. /// </summary>
  156. private void BuildRows()
  157. {
  158. foreach (var row in rows)
  159. row.Destroy();
  160. if (!IsNull())
  161. {
  162. for (int i = 0; i < rows.Count; i++)
  163. rows[i].BuildGUI(this, guiContentLayout, i, depth + 1);
  164. }
  165. else
  166. {
  167. rows.Clear();
  168. }
  169. }
  170. /// <summary>
  171. /// Returns the layout that is used for positioning the elements in the title bar.
  172. /// </summary>
  173. /// <returns>Horizontal layout for positioning the title bar elements.</returns>
  174. public GUILayoutX GetTitleLayout()
  175. {
  176. return guiTitleLayout;
  177. }
  178. /// <summary>
  179. /// Refreshes contents of all list rows and checks if anything was modified.
  180. /// </summary>
  181. public void Refresh()
  182. {
  183. for (int i = 0; i < rows.Count; i++)
  184. {
  185. if (rows[i].Refresh())
  186. rows[i].BuildGUI(this, guiContentLayout, i, depth);
  187. }
  188. }
  189. /// <summary>
  190. /// Destroys the GUI elements.
  191. /// </summary>
  192. public void Destroy()
  193. {
  194. if (guiTitleLayout != null)
  195. {
  196. guiTitleLayout.Destroy();
  197. guiTitleLayout = null;
  198. }
  199. if (guiChildLayout != null)
  200. {
  201. guiChildLayout.Destroy();
  202. guiChildLayout = null;
  203. }
  204. for (int i = 0; i < rows.Count; i++)
  205. rows[i].Destroy();
  206. }
  207. /// <summary>
  208. /// Creates a new list row GUI.
  209. /// </summary>
  210. /// <returns>Object containing the list row GUI.</returns>
  211. protected abstract GUIListFieldRow CreateRow();
  212. /// <summary>
  213. /// Checks is the list instance not assigned.
  214. /// </summary>
  215. /// <returns>True if there is not a list instance.</returns>
  216. protected abstract bool IsNull();
  217. /// <summary>
  218. /// Returns the number of rows in the list.
  219. /// </summary>
  220. /// <returns>Number of rows in the list.</returns>
  221. protected abstract int GetNumRows();
  222. /// <summary>
  223. /// Gets a value of an element at the specified index in the list.
  224. /// </summary>
  225. /// <param name="seqIndex">Sequential index of the element whose value to retrieve.</param>
  226. /// <returns>Value of the list element at the specified index.</returns>
  227. protected internal abstract object GetValue(int seqIndex);
  228. /// <summary>
  229. /// Sets a value of an element at the specified index in the list.
  230. /// </summary>
  231. /// <param name="seqIndex">Sequential index of the element whose value to set.</param>
  232. /// <param name="value">Value to assign to the element. Caller must ensure it is of valid type.</param>
  233. protected internal abstract void SetValue(int seqIndex, object value);
  234. /// <summary>
  235. /// Triggered when the user clicks on the expand/collapse toggle in the title bar.
  236. /// </summary>
  237. /// <param name="expanded">Determines whether the contents were expanded or collapsed.</param>
  238. private void OnFoldoutToggled(bool expanded)
  239. {
  240. isExpanded = expanded;
  241. if (guiChildLayout != null)
  242. guiChildLayout.Active = isExpanded;
  243. }
  244. /// <summary>
  245. /// Triggered when the user clicks on the create button on the title bar. Creates a brand new list with zero
  246. /// elements in the place of the current list.
  247. /// </summary>
  248. protected void OnCreateButtonClicked()
  249. {
  250. CreateList();
  251. UpdateHeaderGUI(false);
  252. }
  253. /// <summary>
  254. /// Triggered when the user clicks on the resize button on the title bar. Changes the size of the list while
  255. /// preserving existing contents.
  256. /// </summary>
  257. protected void OnResizeButtonClicked()
  258. {
  259. ResizeList();
  260. BuildRows();
  261. }
  262. /// <summary>
  263. /// Triggered when the user clicks on the clear button on the title bar. Deletes the current list object.
  264. /// </summary>
  265. protected void OnClearButtonClicked()
  266. {
  267. ClearList();
  268. UpdateHeaderGUI(false);
  269. BuildRows();
  270. }
  271. /// <summary>
  272. /// Triggered when the user clicks on the delete button next to a list entry. Deletes an element in the list.
  273. /// </summary>
  274. /// <param name="index">Sequential index of the element in the list to remove.</param>
  275. protected internal void OnDeleteButtonClicked(int index)
  276. {
  277. DeleteElement(index);
  278. if (rows.Count > 0)
  279. {
  280. rows[rows.Count - 1].Destroy();
  281. rows.RemoveAt(rows.Count - 1);
  282. }
  283. guiSizeField.Value = GetNumRows();
  284. BuildRows();
  285. }
  286. /// <summary>
  287. /// Triggered when the user clicks on the clone button next to a list entry. Clones the element and adds the clone
  288. /// to the back of the list.
  289. /// </summary>
  290. /// <param name="index">Sequential index of the element in the list to clone.</param>
  291. protected internal void OnCloneButtonClicked(int index)
  292. {
  293. CloneElement(index);
  294. GUIListFieldRow row = CreateRow();
  295. rows.Add(row);
  296. guiSizeField.Value = GetNumRows();
  297. BuildRows();
  298. }
  299. /// <summary>
  300. /// Triggered when the user clicks on the move up button next to a list entry. Moves an element from the current
  301. /// list index to the one right before it, if not at zero.
  302. /// </summary>
  303. /// <param name="index">Sequential index of the element in the list to move.</param>
  304. protected internal void OnMoveUpButtonClicked(int index)
  305. {
  306. MoveUpElement(index);
  307. BuildRows();
  308. }
  309. /// <summary>
  310. /// Triggered when the user clicks on the move down button next to a list entry. Moves an element from the current
  311. /// list index to the one right after it, if the element isn't already the last element.
  312. /// </summary>
  313. /// <param name="index">Sequential index of the element in the list to move.</param>
  314. protected internal void OnMoveDownButtonClicked(int index)
  315. {
  316. MoveDownElement(index);
  317. BuildRows();
  318. }
  319. /// <summary>
  320. /// Creates a brand new list with zero elements in the place of the current list.
  321. /// </summary>
  322. protected abstract void CreateList();
  323. /// <summary>
  324. /// Changes the size of the list while preserving existing contents.
  325. /// </summary>
  326. protected abstract void ResizeList();
  327. /// <summary>
  328. /// Deletes the current list object.
  329. /// </summary>
  330. protected abstract void ClearList();
  331. /// <summary>
  332. /// Deletes an element in the list.
  333. /// </summary>
  334. /// <param name="index">Sequential index of the element in the list to remove.</param>
  335. protected internal abstract void DeleteElement(int index);
  336. /// <summary>
  337. /// Clones the element and adds the clone to the back of the list.
  338. /// </summary>
  339. /// <param name="index">Sequential index of the element in the list to clone.</param>
  340. protected internal abstract void CloneElement(int index);
  341. /// <summary>
  342. /// Moves an element from the current list index to the one right before it, if not at zero.
  343. /// </summary>
  344. /// <param name="index">Sequential index of the element in the list to move.</param>
  345. protected internal abstract void MoveUpElement(int index);
  346. /// <summary>
  347. /// Moves an element from the current list index to the one right after it, if the element isn't already the last
  348. /// element.
  349. /// </summary>
  350. /// <param name="index">Sequential index of the element in the list to move.</param>
  351. protected internal abstract void MoveDownElement(int index);
  352. /// <summary>
  353. /// Possible states list GUI can be in.
  354. /// </summary>
  355. private enum State
  356. {
  357. None,
  358. Empty,
  359. Filled
  360. }
  361. }
  362. /// <summary>
  363. /// Creates GUI elements that allow viewing and manipulation of a <see cref="System.Array"/>. When constructing the
  364. /// object user can provide a custom type that manages GUI for individual array elements.
  365. /// </summary>
  366. /// <typeparam name="ElementType">Type of elements stored in the array.</typeparam>
  367. /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual array elements.</typeparam>
  368. public class GUIArrayField<ElementType, RowType> : GUIListFieldBase where RowType : GUIListFieldRow, new()
  369. {
  370. /// <summary>
  371. /// Triggered when the reference array has been changed. This does not include changes that only happen to its
  372. /// internal elements.
  373. /// </summary>
  374. public Action<ElementType[]> OnChanged;
  375. /// <summary>
  376. /// Triggered when an element in the array has been changed.
  377. /// </summary>
  378. public Action OnValueChanged;
  379. /// <summary>
  380. /// Array object whose contents are displayed.
  381. /// </summary>
  382. public ElementType[] Array { get { return array; } }
  383. protected ElementType[] array;
  384. /// <summary>
  385. /// Constructs a new GUI array field.
  386. /// </summary>
  387. /// <param name="title">Label to display on the array GUI title.</param>
  388. /// <param name="array">Object containing the array data. Can be null.</param>
  389. /// <param name="layout">Layout to which to append the array GUI elements to.</param>
  390. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  391. /// nested containers whose backgrounds are overlaping. Also determines background style,
  392. /// depths divisible by two will use an alternate style.</param>
  393. protected GUIArrayField(LocString title, ElementType[] array, GUILayout layout, int depth = 0)
  394. :base(title, layout, depth)
  395. {
  396. this.array = array;
  397. }
  398. /// <summary>
  399. /// Creates a array GUI field containing GUI elements for displaying an array.
  400. /// </summary>
  401. /// <param name="title">Label to display on the array GUI title.</param>
  402. /// <param name="array">Object containing the array data. Can be null.</param>
  403. /// <param name="layout">Layout to which to append the array GUI elements to.</param>
  404. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  405. /// nested containers whose backgrounds are overlaping. Also determines background style,
  406. /// depths divisible by two will use an alternate style.</param>
  407. /// <returns>New instance of an array GUI field.</returns>
  408. public static GUIArrayField<ElementType, RowType> Create(LocString title, ElementType[] array, GUILayout layout,
  409. int depth = 0)
  410. {
  411. GUIArrayField<ElementType, RowType> guiArray = new GUIArrayField<ElementType, RowType>(title, array, layout,
  412. depth);
  413. guiArray.BuildGUI();
  414. return guiArray;
  415. }
  416. /// <inheritdoc/>
  417. protected override GUIListFieldRow CreateRow()
  418. {
  419. return new RowType();
  420. }
  421. /// <inheritdoc/>
  422. protected override bool IsNull()
  423. {
  424. return array == null;
  425. }
  426. /// <inheritdoc/>
  427. protected override int GetNumRows()
  428. {
  429. if (array != null)
  430. return array.GetLength(0);
  431. return 0;
  432. }
  433. /// <inheritdoc/>
  434. protected internal override object GetValue(int seqIndex)
  435. {
  436. return array.GetValue(seqIndex);
  437. }
  438. /// <inheritdoc/>
  439. protected internal override void SetValue(int seqIndex, object value)
  440. {
  441. array.SetValue(value, seqIndex);
  442. if (OnValueChanged != null)
  443. OnValueChanged();
  444. }
  445. /// <inheritdoc/>
  446. protected override void CreateList()
  447. {
  448. array = new ElementType[0];
  449. if (OnChanged != null)
  450. OnChanged(array);
  451. }
  452. /// <inheritdoc/>
  453. protected override void ResizeList()
  454. {
  455. int size = guiSizeField.Value;
  456. ElementType[] newArray = new ElementType[size];
  457. int maxSize = MathEx.Min(size, array.GetLength(0));
  458. for (int i = 0; i < maxSize; i++)
  459. newArray.SetValue(array.GetValue(i), i);
  460. array = newArray;
  461. if(OnChanged != null)
  462. OnChanged(array);
  463. }
  464. /// <inheritdoc/>
  465. protected override void ClearList()
  466. {
  467. array = null;
  468. if (OnChanged != null)
  469. OnChanged(array);
  470. }
  471. /// <inheritdoc/>
  472. protected internal override void DeleteElement(int index)
  473. {
  474. int size = MathEx.Max(0, array.GetLength(0) - 1);
  475. ElementType[] newArray = new ElementType[size];
  476. int destIdx = 0;
  477. for (int i = 0; i < array.GetLength(0); i++)
  478. {
  479. if (i == index)
  480. continue;
  481. newArray.SetValue(array.GetValue(i), destIdx);
  482. destIdx++;
  483. }
  484. array = newArray;
  485. if (OnChanged != null)
  486. OnChanged(array);
  487. }
  488. /// <inheritdoc/>
  489. protected internal override void CloneElement(int index)
  490. {
  491. int size = array.GetLength(0) + 1;
  492. ElementType[] newArray = new ElementType[size];
  493. object clonedEntry = null;
  494. for (int i = 0; i < array.GetLength(0); i++)
  495. {
  496. object value = array.GetValue(i);
  497. newArray.SetValue(value, i);
  498. if (i == index)
  499. {
  500. if (value == null)
  501. clonedEntry = null;
  502. else
  503. clonedEntry = SerializableUtility.Clone(value);
  504. }
  505. }
  506. newArray.SetValue(clonedEntry, size - 1);
  507. array = newArray;
  508. if (OnChanged != null)
  509. OnChanged(array);
  510. }
  511. /// <inheritdoc/>
  512. protected internal override void MoveUpElement(int index)
  513. {
  514. if ((index - 1) >= 0)
  515. {
  516. object previousEntry = array.GetValue(index - 1);
  517. array.SetValue(array.GetValue(index), index - 1);
  518. array.SetValue(previousEntry, index);
  519. if (OnValueChanged != null)
  520. OnValueChanged();
  521. }
  522. }
  523. /// <inheritdoc/>
  524. protected internal override void MoveDownElement(int index)
  525. {
  526. if ((index + 1) < array.GetLength(0))
  527. {
  528. object nextEntry = array.GetValue(index + 1);
  529. array.SetValue(array.GetValue(index), index + 1);
  530. array.SetValue(nextEntry, index);
  531. if (OnValueChanged != null)
  532. OnValueChanged();
  533. }
  534. }
  535. }
  536. /// <summary>
  537. /// Creates GUI elements that allow viewing and manipulation of a <see cref="List{T}"/>. When constructing the
  538. /// object user can provide a custom type that manages GUI for individual list elements.
  539. /// </summary>
  540. /// <typeparam name="ElementType">Type of elements stored in the list.</typeparam>
  541. /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual list elements.</typeparam>
  542. public class GUIListField<ElementType, RowType> : GUIListFieldBase where RowType : GUIListFieldRow, new()
  543. {
  544. /// <summary>
  545. /// Triggered when the reference list has been changed. This does not include changes that only happen to its
  546. /// internal elements.
  547. /// </summary>
  548. public Action<List<ElementType>> OnChanged;
  549. /// <summary>
  550. /// Triggered when an element in the list has been changed.
  551. /// </summary>
  552. public Action OnValueChanged;
  553. /// <summary>
  554. /// List object whose contents are displayed.
  555. /// </summary>
  556. public List<ElementType> List { get { return list; } }
  557. protected List<ElementType> list;
  558. /// <summary>
  559. /// Constructs a new GUI list field.
  560. /// </summary>
  561. /// <param name="title">Label to display on the list GUI title.</param>
  562. /// <param name="list">Object containing the list data. Can be null.</param>
  563. /// <param name="layout">Layout to which to append the list GUI elements to.</param>
  564. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  565. /// nested containers whose backgrounds are overlaping. Also determines background style,
  566. /// depths divisible by two will use an alternate style.</param>
  567. protected GUIListField(LocString title, List<ElementType> list, GUILayout layout, int depth = 0)
  568. : base(title, layout, depth)
  569. {
  570. this.list = list;
  571. }
  572. /// <summary>
  573. /// Creates a list GUI field containing GUI elements for displaying a list.
  574. /// </summary>
  575. /// <param name="title">Label to display on the list GUI title.</param>
  576. /// <param name="list">Object containing the list data. Can be null.</param>
  577. /// <param name="layout">Layout to which to append the list GUI elements to.</param>
  578. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  579. /// nested containers whose backgrounds are overlaping. Also determines background style,
  580. /// depths divisible by two will use an alternate style.</param>
  581. /// <returns>New instance of a list GUI field.</returns>
  582. public static GUIListField<ElementType, RowType> Create(LocString title, List<ElementType> list, GUILayout layout,
  583. int depth = 0)
  584. {
  585. GUIListField<ElementType, RowType> guiList = new GUIListField<ElementType, RowType>(title, list, layout, depth);
  586. guiList.BuildGUI();
  587. return guiList;
  588. }
  589. /// <inheritdoc/>
  590. protected override GUIListFieldRow CreateRow()
  591. {
  592. return new RowType();
  593. }
  594. /// <inheritdoc/>
  595. protected override bool IsNull()
  596. {
  597. return list == null;
  598. }
  599. /// <inheritdoc/>
  600. protected override int GetNumRows()
  601. {
  602. if (list != null)
  603. return list.Count;
  604. return 0;
  605. }
  606. /// <inheritdoc/>
  607. protected internal override object GetValue(int seqIndex)
  608. {
  609. return list[seqIndex];
  610. }
  611. /// <inheritdoc/>
  612. protected internal override void SetValue(int seqIndex, object value)
  613. {
  614. list[seqIndex] = (ElementType)value;
  615. if (OnValueChanged != null)
  616. OnValueChanged();
  617. }
  618. /// <inheritdoc/>
  619. protected override void CreateList()
  620. {
  621. list = new List<ElementType>();
  622. if (OnChanged != null)
  623. OnChanged(list);
  624. }
  625. /// <inheritdoc/>
  626. protected override void ResizeList()
  627. {
  628. int size = guiSizeField.Value;
  629. if(size == list.Count)
  630. return;
  631. if (size < list.Count)
  632. list.RemoveRange(size, list.Count - size);
  633. else
  634. {
  635. ElementType[] extraElements = new ElementType[size - list.Count];
  636. list.AddRange(extraElements);
  637. }
  638. if (OnValueChanged != null)
  639. OnValueChanged();
  640. }
  641. /// <inheritdoc/>
  642. protected override void ClearList()
  643. {
  644. list = null;
  645. if (OnChanged != null)
  646. OnChanged(list);
  647. }
  648. /// <inheritdoc/>
  649. protected internal override void DeleteElement(int index)
  650. {
  651. list.RemoveAt(index);
  652. if (OnValueChanged != null)
  653. OnValueChanged();
  654. }
  655. /// <inheritdoc/>
  656. protected internal override void CloneElement(int index)
  657. {
  658. object clonedEntry = null;
  659. if (list[index] != null)
  660. clonedEntry = SerializableUtility.Clone(list[index]);
  661. list.Add((ElementType)clonedEntry);
  662. if (OnValueChanged != null)
  663. OnValueChanged();
  664. }
  665. /// <inheritdoc/>
  666. protected internal override void MoveUpElement(int index)
  667. {
  668. if ((index - 1) >= 0)
  669. {
  670. ElementType previousEntry = list[index - 1];
  671. list[index - 1] = list[index];
  672. list[index] = previousEntry;
  673. if (OnValueChanged != null)
  674. OnValueChanged();
  675. }
  676. }
  677. /// <inheritdoc/>
  678. protected internal override void MoveDownElement(int index)
  679. {
  680. if ((index + 1) < list.Count)
  681. {
  682. ElementType nextEntry = list[index + 1];
  683. list[index + 1] = list[index];
  684. list[index] = nextEntry;
  685. if (OnValueChanged != null)
  686. OnValueChanged();
  687. }
  688. }
  689. }
  690. /// <summary>
  691. /// Contains GUI elements for a single entry in a list.
  692. /// </summary>
  693. public abstract class GUIListFieldRow
  694. {
  695. private GUILayoutX rowLayout;
  696. private GUILayoutY contentLayout;
  697. private GUILayoutX titleLayout;
  698. private bool localTitleLayout;
  699. private GUIListFieldBase parent;
  700. protected int seqIndex;
  701. protected int depth;
  702. /// <summary>
  703. /// Constructs a new list row object.
  704. /// </summary>
  705. protected GUIListFieldRow()
  706. {
  707. }
  708. /// <summary>
  709. /// (Re)creates all row GUI elements.
  710. /// </summary>
  711. /// <param name="parent">Parent array GUI object that the entry is contained in.</param>
  712. /// <param name="parentLayout">Parent layout that row GUI elements will be added to.</param>
  713. /// <param name="seqIndex">Sequential index of the array entry.</param>
  714. /// <param name="depth">Determines the depth at which the element is rendered.</param>
  715. internal void BuildGUI(GUIListFieldBase parent, GUILayout parentLayout, int seqIndex, int depth)
  716. {
  717. if (rowLayout != null)
  718. rowLayout.Destroy();
  719. this.parent = parent;
  720. this.seqIndex = seqIndex;
  721. this.depth = depth;
  722. rowLayout = parentLayout.AddLayoutX();
  723. contentLayout = rowLayout.AddLayoutY();
  724. GUILayoutX externalTitleLayout = CreateGUI(contentLayout);
  725. if (localTitleLayout || (titleLayout != null && titleLayout == externalTitleLayout))
  726. return;
  727. if (externalTitleLayout != null)
  728. {
  729. localTitleLayout = false;
  730. titleLayout = externalTitleLayout;
  731. }
  732. else
  733. {
  734. GUILayoutY buttonCenter = rowLayout.AddLayoutY();
  735. buttonCenter.AddFlexibleSpace();
  736. titleLayout = buttonCenter.AddLayoutX();
  737. buttonCenter.AddFlexibleSpace();
  738. localTitleLayout = true;
  739. }
  740. GUIContent cloneIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clone));
  741. GUIContent deleteIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Delete));
  742. GUIContent moveUp = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.MoveUp));
  743. GUIContent moveDown = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.MoveDown));
  744. GUIButton cloneBtn = new GUIButton(cloneIcon, GUIOption.FixedWidth(30));
  745. GUIButton deleteBtn = new GUIButton(deleteIcon, GUIOption.FixedWidth(30));
  746. GUIButton moveUpBtn = new GUIButton(moveUp, GUIOption.FixedWidth(30));
  747. GUIButton moveDownBtn = new GUIButton(moveDown, GUIOption.FixedWidth(30));
  748. cloneBtn.OnClick += () => parent.OnCloneButtonClicked(seqIndex);
  749. deleteBtn.OnClick += () => parent.OnDeleteButtonClicked(seqIndex);
  750. moveUpBtn.OnClick += () => parent.OnMoveUpButtonClicked(seqIndex);
  751. moveDownBtn.OnClick += () => parent.OnMoveDownButtonClicked(seqIndex);
  752. titleLayout.AddElement(cloneBtn);
  753. titleLayout.AddElement(deleteBtn);
  754. titleLayout.AddElement(moveUpBtn);
  755. titleLayout.AddElement(moveDownBtn);
  756. }
  757. /// <summary>
  758. /// Creates GUI elements specific to type in the array row.
  759. /// </summary>
  760. /// <param name="layout">Layout to insert the row GUI elements to.</param>
  761. /// <returns>An optional title bar layout that the standard array buttons will be appended to.</returns>
  762. protected abstract GUILayoutX CreateGUI(GUILayoutY layout);
  763. /// <summary>
  764. /// Refreshes the GUI for the list row and checks if anything was modified.
  765. /// </summary>
  766. /// <returns>True if any modifications were made, false otherwise.</returns>
  767. internal protected virtual bool Refresh()
  768. {
  769. return false;
  770. }
  771. /// <summary>
  772. /// Gets the value contained in this list row.
  773. /// </summary>
  774. /// <typeparam name="T">Type of the value. Must match the list's element type.</typeparam>
  775. /// <returns>Value in this list row.</returns>
  776. protected T GetValue<T>()
  777. {
  778. return (T)parent.GetValue(seqIndex);
  779. }
  780. /// <summary>
  781. /// Sets the value contained in this list row.
  782. /// </summary>
  783. /// <typeparam name="T">Type of the value. Must match the list's element type.</typeparam>
  784. /// <param name="value">Value to set.</param>
  785. protected void SetValue<T>(T value)
  786. {
  787. parent.SetValue(seqIndex, value);
  788. }
  789. /// <summary>
  790. /// Destroys all row GUI elements.
  791. /// </summary>
  792. public void Destroy()
  793. {
  794. if (rowLayout != null)
  795. {
  796. rowLayout.Destroy();
  797. rowLayout = null;
  798. }
  799. contentLayout = null;
  800. titleLayout = null;
  801. localTitleLayout = false;
  802. }
  803. }
  804. }