GUIListField.cs 37 KB

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