GUIListField.cs 36 KB

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