GUIListField.cs 37 KB

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