GUIListField.cs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971
  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. bool requiresRebuild = false;
  184. for (int i = 0; i < rows.Count; i++)
  185. requiresRebuild |= rows[i].Refresh();
  186. // Note: I could just rebuild the individual rows but I'd have to remember their layout positions
  187. if (requiresRebuild)
  188. BuildRows();
  189. }
  190. /// <summary>
  191. /// Destroys the GUI elements.
  192. /// </summary>
  193. public void Destroy()
  194. {
  195. if (guiTitleLayout != null)
  196. {
  197. guiTitleLayout.Destroy();
  198. guiTitleLayout = null;
  199. }
  200. if (guiChildLayout != null)
  201. {
  202. guiChildLayout.Destroy();
  203. guiChildLayout = null;
  204. }
  205. for (int i = 0; i < rows.Count; i++)
  206. rows[i].Destroy();
  207. }
  208. /// <summary>
  209. /// Creates a new list row GUI.
  210. /// </summary>
  211. /// <returns>Object containing the list row GUI.</returns>
  212. protected abstract GUIListFieldRow CreateRow();
  213. /// <summary>
  214. /// Checks is the list instance not assigned.
  215. /// </summary>
  216. /// <returns>True if there is not a list instance.</returns>
  217. protected abstract bool IsNull();
  218. /// <summary>
  219. /// Returns the number of rows in the list.
  220. /// </summary>
  221. /// <returns>Number of rows in the list.</returns>
  222. protected abstract int GetNumRows();
  223. /// <summary>
  224. /// Gets a value of an element at the specified index in the list.
  225. /// </summary>
  226. /// <param name="seqIndex">Sequential index of the element whose value to retrieve.</param>
  227. /// <returns>Value of the list element at the specified index.</returns>
  228. protected internal abstract object GetValue(int seqIndex);
  229. /// <summary>
  230. /// Sets a value of an element at the specified index in the list.
  231. /// </summary>
  232. /// <param name="seqIndex">Sequential index of the element whose value to set.</param>
  233. /// <param name="value">Value to assign to the element. Caller must ensure it is of valid type.</param>
  234. protected internal abstract void SetValue(int seqIndex, object value);
  235. /// <summary>
  236. /// Triggered when the user clicks on the expand/collapse toggle in the title bar.
  237. /// </summary>
  238. /// <param name="expanded">Determines whether the contents were expanded or collapsed.</param>
  239. private void OnFoldoutToggled(bool expanded)
  240. {
  241. isExpanded = expanded;
  242. if (guiChildLayout != null)
  243. guiChildLayout.Active = isExpanded;
  244. }
  245. /// <summary>
  246. /// Triggered when the user clicks on the create button on the title bar. Creates a brand new list with zero
  247. /// elements in the place of the current list.
  248. /// </summary>
  249. protected void OnCreateButtonClicked()
  250. {
  251. CreateList();
  252. UpdateHeaderGUI(false);
  253. }
  254. /// <summary>
  255. /// Triggered when the user clicks on the resize button on the title bar. Changes the size of the list while
  256. /// preserving existing contents.
  257. /// </summary>
  258. protected void OnResizeButtonClicked()
  259. {
  260. ResizeList();
  261. int numRows = GetNumRows();
  262. for (int i = numRows; i < rows.Count;)
  263. {
  264. rows[i].Destroy();
  265. rows.RemoveAt(i);
  266. }
  267. for (int i = rows.Count; i < numRows; i++)
  268. rows.Add(CreateRow());
  269. BuildRows();
  270. }
  271. /// <summary>
  272. /// Triggered when the user clicks on the clear button on the title bar. Deletes the current list object.
  273. /// </summary>
  274. protected void OnClearButtonClicked()
  275. {
  276. ClearList();
  277. UpdateHeaderGUI(false);
  278. BuildRows();
  279. }
  280. /// <summary>
  281. /// Triggered when the user clicks on the delete button next to a list entry. Deletes an element in the list.
  282. /// </summary>
  283. /// <param name="index">Sequential index of the element in the list to remove.</param>
  284. protected internal void OnDeleteButtonClicked(int index)
  285. {
  286. DeleteElement(index);
  287. if (rows.Count > 0)
  288. {
  289. rows[rows.Count - 1].Destroy();
  290. rows.RemoveAt(rows.Count - 1);
  291. }
  292. guiSizeField.Value = GetNumRows();
  293. BuildRows();
  294. }
  295. /// <summary>
  296. /// Triggered when the user clicks on the clone button next to a list entry. Clones the element and adds the clone
  297. /// to the back of the list.
  298. /// </summary>
  299. /// <param name="index">Sequential index of the element in the list to clone.</param>
  300. protected internal void OnCloneButtonClicked(int index)
  301. {
  302. CloneElement(index);
  303. GUIListFieldRow row = CreateRow();
  304. rows.Add(row);
  305. guiSizeField.Value = GetNumRows();
  306. BuildRows();
  307. }
  308. /// <summary>
  309. /// Triggered when the user clicks on the move up button next to a list entry. Moves an element from the current
  310. /// list index to the one right before it, if not at zero.
  311. /// </summary>
  312. /// <param name="index">Sequential index of the element in the list to move.</param>
  313. protected internal void OnMoveUpButtonClicked(int index)
  314. {
  315. MoveUpElement(index);
  316. BuildRows();
  317. }
  318. /// <summary>
  319. /// Triggered when the user clicks on the move down button next to a list entry. Moves an element from the current
  320. /// list index to the one right after it, if the element isn't already the last element.
  321. /// </summary>
  322. /// <param name="index">Sequential index of the element in the list to move.</param>
  323. protected internal void OnMoveDownButtonClicked(int index)
  324. {
  325. MoveDownElement(index);
  326. BuildRows();
  327. }
  328. /// <summary>
  329. /// Creates a brand new list with zero elements in the place of the current list.
  330. /// </summary>
  331. protected abstract void CreateList();
  332. /// <summary>
  333. /// Changes the size of the list while preserving existing contents.
  334. /// </summary>
  335. protected abstract void ResizeList();
  336. /// <summary>
  337. /// Deletes the current list object.
  338. /// </summary>
  339. protected abstract void ClearList();
  340. /// <summary>
  341. /// Deletes an element in the list.
  342. /// </summary>
  343. /// <param name="index">Sequential index of the element in the list to remove.</param>
  344. protected internal abstract void DeleteElement(int index);
  345. /// <summary>
  346. /// Clones the element and adds the clone to the back of the list.
  347. /// </summary>
  348. /// <param name="index">Sequential index of the element in the list to clone.</param>
  349. protected internal abstract void CloneElement(int index);
  350. /// <summary>
  351. /// Moves an element from the current list index to the one right before it, if not at zero.
  352. /// </summary>
  353. /// <param name="index">Sequential index of the element in the list to move.</param>
  354. protected internal abstract void MoveUpElement(int index);
  355. /// <summary>
  356. /// Moves an element from the current list index to the one right after it, if the element isn't already the last
  357. /// element.
  358. /// </summary>
  359. /// <param name="index">Sequential index of the element in the list to move.</param>
  360. protected internal abstract void MoveDownElement(int index);
  361. /// <summary>
  362. /// Possible states list GUI can be in.
  363. /// </summary>
  364. private enum State
  365. {
  366. None,
  367. Empty,
  368. Filled
  369. }
  370. }
  371. /// <summary>
  372. /// Creates GUI elements that allow viewing and manipulation of a <see cref="System.Array"/>. When constructing the
  373. /// object user can provide a custom type that manages GUI for individual array elements.
  374. /// </summary>
  375. /// <typeparam name="ElementType">Type of elements stored in the array.</typeparam>
  376. /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual array elements.</typeparam>
  377. public class GUIArrayField<ElementType, RowType> : GUIListFieldBase where RowType : GUIListFieldRow, new()
  378. {
  379. /// <summary>
  380. /// Triggered when the reference array has been changed. This does not include changes that only happen to its
  381. /// internal elements.
  382. /// </summary>
  383. public Action<ElementType[]> OnChanged;
  384. /// <summary>
  385. /// Triggered when an element in the array has been changed.
  386. /// </summary>
  387. public Action OnValueChanged;
  388. /// <summary>
  389. /// Array object whose contents are displayed.
  390. /// </summary>
  391. public ElementType[] Array { get { return array; } }
  392. protected ElementType[] array;
  393. /// <summary>
  394. /// Constructs a new GUI array field.
  395. /// </summary>
  396. /// <param name="title">Label to display on the array GUI title.</param>
  397. /// <param name="array">Object containing the array data. Can be null.</param>
  398. /// <param name="layout">Layout to which to append the array GUI elements to.</param>
  399. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  400. /// nested containers whose backgrounds are overlaping. Also determines background style,
  401. /// depths divisible by two will use an alternate style.</param>
  402. protected GUIArrayField(LocString title, ElementType[] array, GUILayout layout, int depth = 0)
  403. :base(title, layout, depth)
  404. {
  405. this.array = array;
  406. }
  407. /// <summary>
  408. /// Creates a array GUI field containing GUI elements for displaying an array.
  409. /// </summary>
  410. /// <param name="title">Label to display on the array GUI title.</param>
  411. /// <param name="array">Object containing the array data. Can be null.</param>
  412. /// <param name="layout">Layout to which to append the array GUI elements to.</param>
  413. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  414. /// nested containers whose backgrounds are overlaping. Also determines background style,
  415. /// depths divisible by two will use an alternate style.</param>
  416. /// <returns>New instance of an array GUI field.</returns>
  417. public static GUIArrayField<ElementType, RowType> Create(LocString title, ElementType[] array, GUILayout layout,
  418. int depth = 0)
  419. {
  420. GUIArrayField<ElementType, RowType> guiArray = new GUIArrayField<ElementType, RowType>(title, array, layout,
  421. depth);
  422. guiArray.BuildGUI();
  423. return guiArray;
  424. }
  425. /// <inheritdoc/>
  426. protected override GUIListFieldRow CreateRow()
  427. {
  428. return new RowType();
  429. }
  430. /// <inheritdoc/>
  431. protected override bool IsNull()
  432. {
  433. return array == null;
  434. }
  435. /// <inheritdoc/>
  436. protected override int GetNumRows()
  437. {
  438. if (array != null)
  439. return array.GetLength(0);
  440. return 0;
  441. }
  442. /// <inheritdoc/>
  443. protected internal override object GetValue(int seqIndex)
  444. {
  445. return array.GetValue(seqIndex);
  446. }
  447. /// <inheritdoc/>
  448. protected internal override void SetValue(int seqIndex, object value)
  449. {
  450. array.SetValue(value, seqIndex);
  451. if (OnValueChanged != null)
  452. OnValueChanged();
  453. }
  454. /// <inheritdoc/>
  455. protected override void CreateList()
  456. {
  457. array = new ElementType[0];
  458. if (OnChanged != null)
  459. OnChanged(array);
  460. }
  461. /// <inheritdoc/>
  462. protected override void ResizeList()
  463. {
  464. int size = guiSizeField.Value;
  465. ElementType[] newArray = new ElementType[size];
  466. int maxSize = MathEx.Min(size, array.GetLength(0));
  467. for (int i = 0; i < maxSize; i++)
  468. newArray.SetValue(array.GetValue(i), i);
  469. array = newArray;
  470. if(OnChanged != null)
  471. OnChanged(array);
  472. }
  473. /// <inheritdoc/>
  474. protected override void ClearList()
  475. {
  476. array = null;
  477. if (OnChanged != null)
  478. OnChanged(array);
  479. }
  480. /// <inheritdoc/>
  481. protected internal override void DeleteElement(int index)
  482. {
  483. int size = MathEx.Max(0, array.GetLength(0) - 1);
  484. ElementType[] newArray = new ElementType[size];
  485. int destIdx = 0;
  486. for (int i = 0; i < array.GetLength(0); i++)
  487. {
  488. if (i == index)
  489. continue;
  490. newArray.SetValue(array.GetValue(i), destIdx);
  491. destIdx++;
  492. }
  493. array = newArray;
  494. if (OnChanged != null)
  495. OnChanged(array);
  496. }
  497. /// <inheritdoc/>
  498. protected internal override void CloneElement(int index)
  499. {
  500. int size = array.GetLength(0) + 1;
  501. ElementType[] newArray = new ElementType[size];
  502. object clonedEntry = null;
  503. for (int i = 0; i < array.GetLength(0); i++)
  504. {
  505. object value = array.GetValue(i);
  506. newArray.SetValue(value, i);
  507. if (i == index)
  508. {
  509. if (value == null)
  510. clonedEntry = null;
  511. else
  512. clonedEntry = SerializableUtility.Clone(value);
  513. }
  514. }
  515. newArray.SetValue(clonedEntry, size - 1);
  516. array = newArray;
  517. if (OnChanged != null)
  518. OnChanged(array);
  519. }
  520. /// <inheritdoc/>
  521. protected internal override void MoveUpElement(int index)
  522. {
  523. if ((index - 1) >= 0)
  524. {
  525. object previousEntry = array.GetValue(index - 1);
  526. array.SetValue(array.GetValue(index), index - 1);
  527. array.SetValue(previousEntry, index);
  528. if (OnValueChanged != null)
  529. OnValueChanged();
  530. }
  531. }
  532. /// <inheritdoc/>
  533. protected internal override void MoveDownElement(int index)
  534. {
  535. if ((index + 1) < array.GetLength(0))
  536. {
  537. object nextEntry = array.GetValue(index + 1);
  538. array.SetValue(array.GetValue(index), index + 1);
  539. array.SetValue(nextEntry, index);
  540. if (OnValueChanged != null)
  541. OnValueChanged();
  542. }
  543. }
  544. }
  545. /// <summary>
  546. /// Creates GUI elements that allow viewing and manipulation of a <see cref="List{T}"/>. When constructing the
  547. /// object user can provide a custom type that manages GUI for individual list elements.
  548. /// </summary>
  549. /// <typeparam name="ElementType">Type of elements stored in the list.</typeparam>
  550. /// <typeparam name="RowType">Type of rows that are used to handle GUI for individual list elements.</typeparam>
  551. public class GUIListField<ElementType, RowType> : GUIListFieldBase where RowType : GUIListFieldRow, new()
  552. {
  553. /// <summary>
  554. /// Triggered when the reference list has been changed. This does not include changes that only happen to its
  555. /// internal elements.
  556. /// </summary>
  557. public Action<List<ElementType>> OnChanged;
  558. /// <summary>
  559. /// Triggered when an element in the list has been changed.
  560. /// </summary>
  561. public Action OnValueChanged;
  562. /// <summary>
  563. /// List object whose contents are displayed.
  564. /// </summary>
  565. public List<ElementType> List { get { return list; } }
  566. protected List<ElementType> list;
  567. /// <summary>
  568. /// Constructs a new GUI list field.
  569. /// </summary>
  570. /// <param name="title">Label to display on the list GUI title.</param>
  571. /// <param name="list">Object containing the list data. Can be null.</param>
  572. /// <param name="layout">Layout to which to append the list GUI elements to.</param>
  573. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  574. /// nested containers whose backgrounds are overlaping. Also determines background style,
  575. /// depths divisible by two will use an alternate style.</param>
  576. protected GUIListField(LocString title, List<ElementType> list, GUILayout layout, int depth = 0)
  577. : base(title, layout, depth)
  578. {
  579. this.list = list;
  580. }
  581. /// <summary>
  582. /// Creates a list GUI field containing GUI elements for displaying a list.
  583. /// </summary>
  584. /// <param name="title">Label to display on the list GUI title.</param>
  585. /// <param name="list">Object containing the list data. Can be null.</param>
  586. /// <param name="layout">Layout to which to append the list GUI elements to.</param>
  587. /// <param name="depth">Determines at which depth to render the background. Useful when you have multiple
  588. /// nested containers whose backgrounds are overlaping. Also determines background style,
  589. /// depths divisible by two will use an alternate style.</param>
  590. /// <returns>New instance of a list GUI field.</returns>
  591. public static GUIListField<ElementType, RowType> Create(LocString title, List<ElementType> list, GUILayout layout,
  592. int depth = 0)
  593. {
  594. GUIListField<ElementType, RowType> guiList = new GUIListField<ElementType, RowType>(title, list, layout, depth);
  595. guiList.BuildGUI();
  596. return guiList;
  597. }
  598. /// <inheritdoc/>
  599. protected override GUIListFieldRow CreateRow()
  600. {
  601. return new RowType();
  602. }
  603. /// <inheritdoc/>
  604. protected override bool IsNull()
  605. {
  606. return list == null;
  607. }
  608. /// <inheritdoc/>
  609. protected override int GetNumRows()
  610. {
  611. if (list != null)
  612. return list.Count;
  613. return 0;
  614. }
  615. /// <inheritdoc/>
  616. protected internal override object GetValue(int seqIndex)
  617. {
  618. return list[seqIndex];
  619. }
  620. /// <inheritdoc/>
  621. protected internal override void SetValue(int seqIndex, object value)
  622. {
  623. list[seqIndex] = (ElementType)value;
  624. if (OnValueChanged != null)
  625. OnValueChanged();
  626. }
  627. /// <inheritdoc/>
  628. protected override void CreateList()
  629. {
  630. list = new List<ElementType>();
  631. if (OnChanged != null)
  632. OnChanged(list);
  633. }
  634. /// <inheritdoc/>
  635. protected override void ResizeList()
  636. {
  637. int size = guiSizeField.Value;
  638. if(size == list.Count)
  639. return;
  640. if (size < list.Count)
  641. list.RemoveRange(size, list.Count - size);
  642. else
  643. {
  644. ElementType[] extraElements = new ElementType[size - list.Count];
  645. list.AddRange(extraElements);
  646. }
  647. if (OnValueChanged != null)
  648. OnValueChanged();
  649. }
  650. /// <inheritdoc/>
  651. protected override void ClearList()
  652. {
  653. list = null;
  654. if (OnChanged != null)
  655. OnChanged(list);
  656. }
  657. /// <inheritdoc/>
  658. protected internal override void DeleteElement(int index)
  659. {
  660. list.RemoveAt(index);
  661. if (OnValueChanged != null)
  662. OnValueChanged();
  663. }
  664. /// <inheritdoc/>
  665. protected internal override void CloneElement(int index)
  666. {
  667. object clonedEntry = null;
  668. if (list[index] != null)
  669. clonedEntry = SerializableUtility.Clone(list[index]);
  670. list.Add((ElementType)clonedEntry);
  671. if (OnValueChanged != null)
  672. OnValueChanged();
  673. }
  674. /// <inheritdoc/>
  675. protected internal override void MoveUpElement(int index)
  676. {
  677. if ((index - 1) >= 0)
  678. {
  679. ElementType previousEntry = list[index - 1];
  680. list[index - 1] = list[index];
  681. list[index] = previousEntry;
  682. if (OnValueChanged != null)
  683. OnValueChanged();
  684. }
  685. }
  686. /// <inheritdoc/>
  687. protected internal override void MoveDownElement(int index)
  688. {
  689. if ((index + 1) < list.Count)
  690. {
  691. ElementType nextEntry = list[index + 1];
  692. list[index + 1] = list[index];
  693. list[index] = nextEntry;
  694. if (OnValueChanged != null)
  695. OnValueChanged();
  696. }
  697. }
  698. }
  699. /// <summary>
  700. /// Contains GUI elements for a single entry in a list.
  701. /// </summary>
  702. public abstract class GUIListFieldRow
  703. {
  704. private GUILayoutX rowLayout;
  705. private GUILayoutY contentLayout;
  706. private GUILayoutX titleLayout;
  707. private bool localTitleLayout;
  708. private GUIListFieldBase parent;
  709. protected int seqIndex;
  710. protected int depth;
  711. /// <summary>
  712. /// Constructs a new list row object.
  713. /// </summary>
  714. protected GUIListFieldRow()
  715. {
  716. }
  717. /// <summary>
  718. /// (Re)creates all row GUI elements.
  719. /// </summary>
  720. /// <param name="parent">Parent array GUI object that the entry is contained in.</param>
  721. /// <param name="parentLayout">Parent layout that row GUI elements will be added to.</param>
  722. /// <param name="seqIndex">Sequential index of the array entry.</param>
  723. /// <param name="depth">Determines the depth at which the element is rendered.</param>
  724. internal void BuildGUI(GUIListFieldBase parent, GUILayout parentLayout, int seqIndex, int depth)
  725. {
  726. if (rowLayout != null)
  727. rowLayout.Destroy();
  728. this.parent = parent;
  729. this.seqIndex = seqIndex;
  730. this.depth = depth;
  731. rowLayout = parentLayout.AddLayoutX();
  732. contentLayout = rowLayout.AddLayoutY();
  733. GUILayoutX externalTitleLayout = CreateGUI(contentLayout);
  734. if (localTitleLayout || (titleLayout != null && titleLayout == externalTitleLayout))
  735. return;
  736. if (externalTitleLayout != null)
  737. {
  738. localTitleLayout = false;
  739. titleLayout = externalTitleLayout;
  740. }
  741. else
  742. {
  743. GUILayoutY buttonCenter = rowLayout.AddLayoutY();
  744. buttonCenter.AddFlexibleSpace();
  745. titleLayout = buttonCenter.AddLayoutX();
  746. buttonCenter.AddFlexibleSpace();
  747. localTitleLayout = true;
  748. }
  749. GUIContent cloneIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Clone));
  750. GUIContent deleteIcon = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.Delete));
  751. GUIContent moveUp = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.MoveUp));
  752. GUIContent moveDown = new GUIContent(EditorBuiltin.GetInspectorWindowIcon(InspectorWindowIcon.MoveDown));
  753. GUIButton cloneBtn = new GUIButton(cloneIcon, GUIOption.FixedWidth(30));
  754. GUIButton deleteBtn = new GUIButton(deleteIcon, GUIOption.FixedWidth(30));
  755. GUIButton moveUpBtn = new GUIButton(moveUp, GUIOption.FixedWidth(30));
  756. GUIButton moveDownBtn = new GUIButton(moveDown, GUIOption.FixedWidth(30));
  757. cloneBtn.OnClick += () => parent.OnCloneButtonClicked(seqIndex);
  758. deleteBtn.OnClick += () => parent.OnDeleteButtonClicked(seqIndex);
  759. moveUpBtn.OnClick += () => parent.OnMoveUpButtonClicked(seqIndex);
  760. moveDownBtn.OnClick += () => parent.OnMoveDownButtonClicked(seqIndex);
  761. titleLayout.AddElement(cloneBtn);
  762. titleLayout.AddElement(deleteBtn);
  763. titleLayout.AddElement(moveUpBtn);
  764. titleLayout.AddElement(moveDownBtn);
  765. }
  766. /// <summary>
  767. /// Creates GUI elements specific to type in the array row.
  768. /// </summary>
  769. /// <param name="layout">Layout to insert the row GUI elements to.</param>
  770. /// <returns>An optional title bar layout that the standard array buttons will be appended to.</returns>
  771. protected abstract GUILayoutX CreateGUI(GUILayoutY layout);
  772. /// <summary>
  773. /// Refreshes the GUI for the list row and checks if anything was modified.
  774. /// </summary>
  775. /// <returns>True if the row requires a rebuild, false otherwise.</returns>
  776. internal protected virtual bool Refresh()
  777. {
  778. return false;
  779. }
  780. /// <summary>
  781. /// Gets 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. /// <returns>Value in this list row.</returns>
  785. protected T GetValue<T>()
  786. {
  787. return (T)parent.GetValue(seqIndex);
  788. }
  789. /// <summary>
  790. /// Sets the value contained in this list row.
  791. /// </summary>
  792. /// <typeparam name="T">Type of the value. Must match the list's element type.</typeparam>
  793. /// <param name="value">Value to set.</param>
  794. protected void SetValue<T>(T value)
  795. {
  796. parent.SetValue(seqIndex, value);
  797. }
  798. /// <summary>
  799. /// Destroys all row GUI elements.
  800. /// </summary>
  801. public void Destroy()
  802. {
  803. if (rowLayout != null)
  804. {
  805. rowLayout.Destroy();
  806. rowLayout = null;
  807. }
  808. contentLayout = null;
  809. titleLayout = null;
  810. localTitleLayout = false;
  811. }
  812. }
  813. }