ChangesTabPageView.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using System;
  2. using System.Collections.Generic;
  3. using JetBrains.Annotations;
  4. using Unity.Cloud.Collaborate.Assets;
  5. using Unity.Cloud.Collaborate.Views.Adapters.ListAdapters;
  6. using Unity.Cloud.Collaborate.Components;
  7. using Unity.Cloud.Collaborate.Models.Structures;
  8. using Unity.Cloud.Collaborate.Presenters;
  9. using Unity.Cloud.Collaborate.UserInterface;
  10. using UnityEditor;
  11. using UnityEngine;
  12. using UnityEngine.Assertions;
  13. using UnityEngine.UIElements;
  14. namespace Unity.Cloud.Collaborate.Views
  15. {
  16. [UsedImplicitly]
  17. internal class ChangesTabPageView : TabPageComponent, IChangesView
  18. {
  19. [CanBeNull]
  20. IChangesPresenter m_Presenter;
  21. public const string UssClassName = "changes-tab-page-view";
  22. public const string SearchBarUssClassName = UssClassName + "__search-bar";
  23. public const string EntryGroupsUssClassName = UssClassName + "__entry-groups";
  24. public const string PublishButtonUssClassName = UssClassName + "__publish-button";
  25. public const string TextFieldUssClassName = UssClassName + "__text-field";
  26. public const string ListViewUssClassName = UssClassName + "__list-view";
  27. static readonly string k_LayoutPath = $"{CollaborateWindow.LayoutPath}/{nameof(ChangesTabPageView)}.uxml";
  28. static readonly string k_StylePath = $"{CollaborateWindow.StylePath}/{nameof(ChangesTabPageView)}.uss";
  29. readonly IconTextButton m_PublishButton;
  30. readonly BetterTextField m_RevisionSummaryBox;
  31. readonly SearchBar m_SearchBar;
  32. readonly VisualElement m_EntryGroupsContainer;
  33. bool m_Active;
  34. [CanBeNull]
  35. ConflictedChangeListAdapter m_ConflictedChangeListAdapter;
  36. [CanBeNull]
  37. ToggleableChangeListAdapter m_ToggleableChangeListAdapter;
  38. [CanBeNull]
  39. ChangeEntryGroup m_EntryToggleableGroup;
  40. [CanBeNull]
  41. ChangeEntryGroup m_EntryConflictsGroup;
  42. [CanBeNull]
  43. VisualElement m_ActiveGroup;
  44. public ChangesTabPageView()
  45. {
  46. AddToClassList(UssClassName);
  47. AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(k_LayoutPath).CloneTree(this);
  48. styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(k_StylePath));
  49. // Get the components defined in the style / layout files.
  50. m_SearchBar = this.Q<SearchBar>(className: SearchBarUssClassName);
  51. m_RevisionSummaryBox = this.Q<BetterTextField>(className: TextFieldUssClassName);
  52. m_PublishButton = this.Q<IconTextButton>(className: PublishButtonUssClassName);
  53. m_EntryGroupsContainer = this.Q<VisualElement>(className: EntryGroupsUssClassName);
  54. // Initialize the text strings.
  55. m_PublishButton.Text = StringAssets.publishButton;
  56. m_RevisionSummaryBox.Placeholder = StringAssets.publishSummaryPlaceholder;
  57. }
  58. /// <inheritdoc />
  59. public IChangesPresenter Presenter
  60. {
  61. set
  62. {
  63. m_Presenter = value;
  64. SetupEvents();
  65. // If tab active before presenter has been added, call start once we have it.
  66. if (Active)
  67. {
  68. value.Start();
  69. }
  70. }
  71. }
  72. /// <summary>
  73. /// Setup events to communicate with the presenter. Must be called after presenter is set.
  74. /// </summary>
  75. void SetupEvents()
  76. {
  77. Assert.IsNotNull(m_Presenter, "Invalid changes page state.");
  78. // Set up publish invocation.
  79. m_PublishButton.Clicked += m_Presenter.RequestPublish;
  80. // Send text values to the presenter.
  81. m_SearchBar.Search += m_Presenter.SetSearchQuery;
  82. m_RevisionSummaryBox.OnValueChangedHandler += s => m_Presenter.SetRevisionSummary(s);
  83. }
  84. /// <inheritdoc />
  85. public void SetBusyStatus(bool busy)
  86. {
  87. m_EntryGroupsContainer.SetEnabled(!busy);
  88. m_RevisionSummaryBox.SetEnabled(!busy);
  89. }
  90. /// <inheritdoc />
  91. protected override void SetActive()
  92. {
  93. Assert.IsFalse(m_Active, "The view is already active.");
  94. m_Active = true;
  95. m_Presenter?.Start();
  96. }
  97. /// <inheritdoc />
  98. protected override void SetInactive()
  99. {
  100. Assert.IsTrue(m_Active, "The view is already inactive.");
  101. m_Active = false;
  102. m_Presenter?.Stop();
  103. }
  104. /// <inheritdoc />
  105. public void SetSearchQuery(string query)
  106. {
  107. Assert.IsNotNull(m_Presenter, "Invalid state when setting search query.");
  108. m_SearchBar.SetValueWithoutNotify(query);
  109. var isSearching = m_Presenter.Searching;
  110. if (m_EntryConflictsGroup != null) m_EntryConflictsGroup.Searching = isSearching;
  111. if (m_EntryToggleableGroup != null) m_EntryToggleableGroup.Searching = isSearching;
  112. }
  113. /// <inheritdoc />
  114. public void SetRevisionSummary(string message)
  115. {
  116. m_RevisionSummaryBox.SetValueWithoutNotify(message);
  117. }
  118. /// <inheritdoc />
  119. public void SetConflicts(IReadOnlyList<IChangeEntryData> list)
  120. {
  121. Assert.IsNotNull(m_Presenter, "Invalid state while creating conflict list.");
  122. // Initialise conflicts group
  123. if (m_EntryConflictsGroup == null)
  124. {
  125. var conflictsList = new AdapterListView { name = StringAssets.changeListConflictedList, SelectionType = SelectionType.None };
  126. conflictsList.AddToClassList(ListViewUssClassName);
  127. m_ConflictedChangeListAdapter = new ConflictedChangeListAdapter(m_Presenter);
  128. conflictsList.SetAdapter(m_ConflictedChangeListAdapter);
  129. m_EntryConflictsGroup = new ChangeEntryGroup(conflictsList) { Title = StringAssets.changeListConflictedHeader };
  130. m_EntryConflictsGroup.SetOverflowCallback(m_Presenter.OnClickConflictGroupOverflow);
  131. m_EntryConflictsGroup.Searching = m_Presenter.Searching;
  132. }
  133. Assert.IsTrue(m_ConflictedChangeListAdapter != null && m_EntryConflictsGroup != null, "Invalid state while setting conflicted list.");
  134. // Ensure conflict list is displayed
  135. if (m_ActiveGroup != m_EntryConflictsGroup)
  136. {
  137. m_ActiveGroup?.RemoveFromHierarchy();
  138. m_EntryGroupsContainer.Add(m_EntryConflictsGroup);
  139. m_ActiveGroup = m_EntryConflictsGroup;
  140. }
  141. m_ConflictedChangeListAdapter.List = list;
  142. var count = m_Presenter.ConflictedCount;
  143. m_EntryConflictsGroup.NumberMenuItems = m_Presenter.ConflictGroupOverflowEntryCount;
  144. m_EntryConflictsGroup.SelectedEntryCount = count;
  145. m_EntryConflictsGroup.EntryCount = count;
  146. }
  147. /// <inheritdoc />
  148. public void SetSelectedChanges()
  149. {
  150. Assert.IsNotNull(m_Presenter, "Invalid state while setting selected items from toggleable list.");
  151. if(m_ToggleableChangeListAdapter == null)
  152. {
  153. // we might be Selecting partial changes before the view loads the first time,
  154. // so we just ignore it ....
  155. return;
  156. }
  157. Assert.IsTrue(m_ToggleableChangeListAdapter != null && m_EntryToggleableGroup != null, "Invalid state while setting selected items in toggleable list");
  158. var scrollToIndex = m_ToggleableChangeListAdapter.GetFirstToggledIndex();
  159. m_ToggleableChangeListAdapter.NotifyDataSetChanged();
  160. if (scrollToIndex != -1)
  161. {
  162. scrollToIndex = Math.Min(scrollToIndex, m_ToggleableChangeListAdapter.GetEntryCount() - 1);
  163. m_EntryToggleableGroup.ScrollTo(scrollToIndex);
  164. if(m_ToggleableChangeListAdapter.GetLastBoundElementIndex() < scrollToIndex + 3)
  165. {
  166. // the pool of the list is 14 elements .. but the list actually shows only 12 ..
  167. // so the normal scrollTo call of the list view may stop 1 element short of the selected
  168. // index if the scrolled to index is greater than the currently selected index.
  169. m_EntryToggleableGroup.ScrollTo(scrollToIndex + 3);
  170. }
  171. }
  172. }
  173. /// <inheritdoc />
  174. public void SetChanges(IReadOnlyList<IChangeEntryData> list)
  175. {
  176. Assert.IsNotNull(m_Presenter, "Invalid state while creating toggleable list.");
  177. // Initialise the toggleable list if not already initialised.
  178. if (m_EntryToggleableGroup == null)
  179. {
  180. var toggleableListView = new AdapterListView { SelectionType = SelectionType.None };
  181. toggleableListView.AddToClassList(ListViewUssClassName);
  182. m_ToggleableChangeListAdapter = new ToggleableChangeListAdapter(m_Presenter);
  183. toggleableListView.SetAdapter(m_ToggleableChangeListAdapter);
  184. m_EntryToggleableGroup = new ChangeEntryGroup(toggleableListView)
  185. { Title = StringAssets.changeListFullHeader };
  186. m_EntryToggleableGroup.SetOverflowCallback(m_Presenter.OnClickGroupOverflow);
  187. m_EntryToggleableGroup.Searching = m_Presenter.Searching;
  188. }
  189. Assert.IsTrue(m_ToggleableChangeListAdapter != null && m_EntryToggleableGroup != null, "Invalid state while setting toggleable list");
  190. // Ensure single list is displayed
  191. if (m_ActiveGroup != m_EntryToggleableGroup)
  192. {
  193. m_ActiveGroup?.RemoveFromHierarchy();
  194. m_EntryGroupsContainer.Add(m_EntryToggleableGroup);
  195. m_ActiveGroup = m_EntryToggleableGroup;
  196. }
  197. // Can use list.Count here since searching hides "All".
  198. m_EntryToggleableGroup.EntryCount = m_Presenter.Searching ? list.Count : m_Presenter.TotalCount;
  199. m_ToggleableChangeListAdapter.List = list;
  200. m_EntryToggleableGroup.NumberMenuItems = m_Presenter.GroupOverflowEntryCount;
  201. m_EntryToggleableGroup.SelectedEntryCount = m_Presenter.ToggledCount;
  202. }
  203. /// <inheritdoc />
  204. public void SetToggledCount(int count)
  205. {
  206. if (m_EntryToggleableGroup != null)
  207. {
  208. m_EntryToggleableGroup.SelectedEntryCount = count;
  209. }
  210. }
  211. /// <inheritdoc />
  212. public void SetPublishEnabled(bool enabled, string reason = null)
  213. {
  214. m_PublishButton.SetEnabled(enabled);
  215. // Disabled elements cannot have a tooltip so apply to a empty/dummy parent instead.
  216. m_PublishButton.parent.tooltip = reason;
  217. }
  218. /// <inheritdoc />
  219. public bool DisplayDialogue(string title, string message, string affirmative)
  220. {
  221. return EditorUtility.DisplayDialog(title, message, affirmative);
  222. }
  223. /// <inheritdoc />
  224. public bool DisplayDialogue(string title, string message, string affirmative, string negative)
  225. {
  226. return EditorUtility.DisplayDialog(title, message, affirmative, negative);
  227. }
  228. [UsedImplicitly]
  229. public new class UxmlFactory : UxmlFactory<ChangesTabPageView> { }
  230. }
  231. }