AlertBox.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. using System;
  2. using System.Collections.Generic;
  3. using JetBrains.Annotations;
  4. using Unity.Cloud.Collaborate.Assets;
  5. using Unity.Cloud.Collaborate.UserInterface;
  6. using UnityEditor;
  7. using UnityEngine.UIElements;
  8. namespace Unity.Cloud.Collaborate.Components
  9. {
  10. [UsedImplicitly]
  11. internal class AlertBox : VisualElement
  12. {
  13. /// <summary>
  14. /// Describes the severity of the alert. Used to set the icon.
  15. /// </summary>
  16. public enum AlertLevel
  17. {
  18. Info,
  19. Warning,
  20. Alert
  21. }
  22. public const string UssClassName = "alert-box";
  23. public const string IconUssClassName = UssClassName + "__icon";
  24. public const string TextUssClassName = UssClassName + "__text";
  25. public const string ButtonUssClassName = UssClassName + "__button";
  26. static readonly string k_LayoutPath = $"{CollaborateWindow.LayoutPath}/{nameof(AlertBox)}.uxml";
  27. static readonly string k_StylePath = $"{CollaborateWindow.StylePath}/{nameof(AlertBox)}.uss";
  28. readonly Button m_Button;
  29. readonly VisualElement m_Icon;
  30. readonly TextElement m_Text;
  31. // Uss classes to set which icon is displayed.
  32. const string k_UssIconInfo = "icon-info";
  33. const string k_UssIconWarning = "icon-warning";
  34. const string k_UssIconAlert = "icon-alert";
  35. /// <summary>
  36. /// Queue of alerts to be displayed.
  37. /// </summary>
  38. readonly SortedSet<AlertEntry> m_AlertEntryList;
  39. /// <summary>
  40. /// Initialize the box and hide it.
  41. /// </summary>
  42. public AlertBox()
  43. {
  44. // Get the layout
  45. AddToClassList(UssClassName);
  46. AssetDatabase.LoadAssetAtPath<VisualTreeAsset>(k_LayoutPath).CloneTree(this);
  47. styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(k_StylePath));
  48. // Initialise fields
  49. m_Icon = this.Q<VisualElement>(className: IconUssClassName);
  50. m_Text = this.Q<TextElement>(className: TextUssClassName);
  51. m_Button = this.Q<Button>(className: ButtonUssClassName);
  52. m_AlertEntryList = new SortedSet<AlertEntry>();
  53. // No alerts to display, so this hides the box.
  54. UpdateAlertBox();
  55. }
  56. /// <summary>
  57. /// Queue an alert to be displayed. Displayed immediately if there is currently none. If there is an existing
  58. /// alert with the same name, it will be replaced with the latest one.
  59. /// </summary>
  60. /// <param name="id">Unique ID for the queued alert.</param>
  61. /// <param name="level">Level of important of the alert.</param>
  62. /// <param name="message">Message to be displayed.</param>
  63. /// <param name="button">Info to populate optional button.</param>
  64. public void QueueAlert([NotNull] string id, AlertLevel level, [NotNull] string message, (string text, Action action)? button = null)
  65. {
  66. // Search for existing alert.
  67. var oldActive = m_AlertEntryList.Count == 0 ? (AlertEntry?)null : m_AlertEntryList.Max;
  68. // Create new alert entry.
  69. var entry = new AlertEntry(id, level, message, button == null
  70. ? (AlertEntry.AlertButton?)null
  71. : new AlertEntry.AlertButton { Text = button.Value.text, Action = button.Value.action });
  72. m_AlertEntryList.Add(entry);
  73. UpdateAlertBox(oldActive?.Button?.Action);
  74. }
  75. /// <summary>
  76. /// Remove existing alert. If current active one, switch to next one, or hide if none queued.
  77. /// </summary>
  78. /// <param name="id">Unique ID for the alert.</param>
  79. public void DequeueAlert([NotNull] string id)
  80. {
  81. var oldAlert = m_AlertEntryList.Max;
  82. m_AlertEntryList.RemoveWhere(e => e.Id == id);
  83. UpdateAlertBox(oldAlert.Button?.Action);
  84. }
  85. /// <summary>
  86. /// Display alert at the front of the queue, or hide if there are none.
  87. /// </summary>
  88. void UpdateAlertBox(Action previousButtonAction = null)
  89. {
  90. // Remove old event if it exists.
  91. if (previousButtonAction != null)
  92. {
  93. m_Button.clickable.clicked -= previousButtonAction;
  94. }
  95. if (m_AlertEntryList.Count == 0)
  96. {
  97. m_Button.text = string.Empty;
  98. m_Button.AddToClassList(UiConstants.ussHidden);
  99. AddToClassList(UiConstants.ussHidden);
  100. }
  101. else
  102. {
  103. var activeAlert = m_AlertEntryList.Max;
  104. m_Text.text = activeAlert.Message;
  105. // Update state of optional button
  106. if (activeAlert.Button?.Action != null)
  107. {
  108. m_Button.clickable.clicked += activeAlert.Button.Value.Action;
  109. m_Button.text = activeAlert.Button.Value.Text;
  110. m_Button.RemoveFromClassList(UiConstants.ussHidden);
  111. }
  112. else
  113. {
  114. m_Button.text = string.Empty;
  115. m_Button.AddToClassList(UiConstants.ussHidden);
  116. }
  117. SetAlertLevel(activeAlert.Level);
  118. RemoveFromClassList(UiConstants.ussHidden);
  119. }
  120. }
  121. /// <summary>
  122. /// Set the icon to the given severity level.
  123. /// </summary>
  124. /// <param name="level">Level of severity to make the icon.</param>
  125. void SetAlertLevel(AlertLevel level)
  126. {
  127. // Remove old level
  128. m_Icon.RemoveFromClassList(k_UssIconInfo);
  129. m_Icon.RemoveFromClassList(k_UssIconWarning);
  130. m_Icon.RemoveFromClassList(k_UssIconAlert);
  131. // Set new one
  132. switch (level)
  133. {
  134. case AlertLevel.Info:
  135. m_Icon.AddToClassList(k_UssIconInfo);
  136. break;
  137. case AlertLevel.Warning:
  138. m_Icon.AddToClassList(k_UssIconWarning);
  139. break;
  140. case AlertLevel.Alert:
  141. m_Icon.AddToClassList(k_UssIconAlert);
  142. break;
  143. default:
  144. throw new ArgumentOutOfRangeException(nameof(level), level, null);
  145. }
  146. }
  147. struct AlertEntry : IComparable<AlertEntry>
  148. {
  149. public readonly string Id;
  150. public readonly AlertLevel Level;
  151. public readonly string Message;
  152. public AlertButton? Button;
  153. public AlertEntry(string id, AlertLevel level, string message, AlertButton? button)
  154. {
  155. Id = id;
  156. Level = level;
  157. Message = message;
  158. Button = button;
  159. }
  160. public struct AlertButton
  161. {
  162. public string Text;
  163. public Action Action;
  164. }
  165. public override int GetHashCode()
  166. {
  167. return Id.GetHashCode();
  168. }
  169. public override bool Equals(object obj)
  170. {
  171. return obj is AlertEntry other && Id == other.Id;
  172. }
  173. public int CompareTo(AlertEntry other)
  174. {
  175. var value = Level.CompareTo(other.Level);
  176. // If same level, compare by id.
  177. return value != 0
  178. ? value
  179. : string.Compare(Id, other.Id, StringComparison.Ordinal);
  180. }
  181. }
  182. [UsedImplicitly]
  183. public new class UxmlFactory : UxmlFactory<AlertBox> { }
  184. }
  185. }