GUIListField.cs 37 KB

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