FloatingDialogue.cs 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. using System;
  2. using Unity.Cloud.Collaborate.Assets;
  3. using Unity.Cloud.Collaborate.UserInterface;
  4. using Unity.Cloud.Collaborate.Utilities;
  5. using UnityEditor;
  6. using UnityEngine;
  7. using UnityEngine.UIElements;
  8. namespace Unity.Cloud.Collaborate.Components.Menus
  9. {
  10. /// <summary>
  11. /// Global element that is used to display dialogues throughout the window.
  12. /// </summary>
  13. internal class FloatingDialogue : VisualElement
  14. {
  15. /// <summary>
  16. /// USS class name of elements of this type.
  17. /// </summary>
  18. public const string UssClassName = "unity-floating-dialogue";
  19. /// <summary>
  20. /// Location the uss file for this element is stored.
  21. /// </summary>
  22. static readonly string k_StylePath = $"{CollaborateWindow.StylePath}/{nameof(FloatingDialogue)}.uss";
  23. /// <summary>
  24. /// Backing field for the singleton.
  25. /// </summary>
  26. static FloatingDialogue s_Instance;
  27. /// <summary>
  28. /// Singleton for accessing the global FloatingDialogue
  29. /// </summary>
  30. public static FloatingDialogue Instance => s_Instance ?? (s_Instance = new FloatingDialogue());
  31. /// <summary>
  32. /// Constructor used by the singleton.
  33. /// </summary>
  34. FloatingDialogue()
  35. {
  36. AddToClassList(UssClassName);
  37. AddToClassList(UiConstants.ussHidden);
  38. styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>(k_StylePath));
  39. }
  40. /// <summary>
  41. /// Allows focus to be set when the window opens. Allows for the options to be next up for tab.
  42. /// </summary>
  43. public override bool canGrabFocus => true;
  44. /// <summary>
  45. /// Set the position of the dialogue.
  46. /// </summary>
  47. /// <param name="x">World x coordinate.</param>
  48. /// <param name="y">World y coordinate.</param>
  49. /// <param name="content">Content of the window.</param>
  50. /// <param name="openDirection">Direction to open the dialogue towards.</param>
  51. void SetPosition(float x, float y, VisualElement content, MenuUtilities.OpenDirection openDirection)
  52. {
  53. // Correct for the top left corner of the element based on the direction it opens.
  54. switch (openDirection)
  55. {
  56. case MenuUtilities.OpenDirection.UpLeft:
  57. x -= content.worldBound.width;
  58. y -= content.worldBound.height;
  59. break;
  60. case MenuUtilities.OpenDirection.UpRight:
  61. y -= content.worldBound.height;
  62. break;
  63. case MenuUtilities.OpenDirection.DownLeft:
  64. x -= content.worldBound.width;
  65. break;
  66. case MenuUtilities.OpenDirection.DownRight:
  67. break;
  68. default:
  69. throw new ArgumentOutOfRangeException(nameof(openDirection), openDirection, null);
  70. }
  71. // Set the position.
  72. style.top = y - parent.worldBound.yMin;
  73. style.left = x - parent.worldBound.xMin;
  74. style.right = new StyleLength(StyleKeyword.Auto);
  75. style.bottom = new StyleLength(StyleKeyword.Auto);
  76. }
  77. /// <summary>
  78. /// Open the dialogue.
  79. /// </summary>
  80. /// <param name="x">World x coordinate.</param>
  81. /// <param name="y">World y coordinate.</param>
  82. /// <param name="content">Content to display.</param>
  83. /// <param name="openDirection">Direction to open the dialogue towards.</param>
  84. internal void Open(float x, float y, VisualElement content, MenuUtilities.OpenDirection openDirection = MenuUtilities.OpenDirection.DownLeft)
  85. {
  86. // Set new content
  87. Clear();
  88. Add(content);
  89. // Set visible and give focus
  90. RemoveFromClassList(UiConstants.ussHidden);
  91. Focus();
  92. BringToFront();
  93. // Catch scrolling to avoid menu becoming detached.
  94. parent.RegisterCallback<WheelEvent>(OnScroll, TrickleDown.TrickleDown);
  95. // Catch clicks outside of the dialogue and close it.
  96. parent.RegisterCallback<MouseDownEvent>(OnMouseDown, TrickleDown.TrickleDown);
  97. // Catch window size changes so that the menu position can be fixed.
  98. parent.RegisterCallback<GeometryChangedEvent>(OnGeometryChange, TrickleDown.TrickleDown);
  99. content.RegisterCallback<GeometryChangedEvent>(SizeKnownCallback);
  100. void SizeKnownCallback(GeometryChangedEvent _)
  101. {
  102. // Unregister now that we know it has a size.
  103. content.UnregisterCallback<GeometryChangedEvent>(SizeKnownCallback);
  104. // Set the position in the window
  105. SetPosition(x, y, content, openDirection);
  106. }
  107. }
  108. /// <summary>
  109. /// Close the dialogue.
  110. /// </summary>
  111. internal void Close()
  112. {
  113. AddToClassList(UiConstants.ussHidden);
  114. Clear();
  115. // Avoid wasting extra cycles when closed.
  116. parent.UnregisterCallback<WheelEvent>(OnScroll, TrickleDown.TrickleDown);
  117. parent.UnregisterCallback<MouseDownEvent>(OnMouseDown, TrickleDown.TrickleDown);
  118. }
  119. /// <summary>
  120. /// On scroll event.
  121. /// </summary>
  122. /// <param name="evt">Event data.</param>
  123. void OnScroll(WheelEvent evt)
  124. {
  125. // Close the window if the user scrolls outside the dialogue.
  126. if (!worldBound.Contains(evt.mousePosition))
  127. {
  128. Close();
  129. }
  130. }
  131. /// <summary>
  132. /// Mouse down event.
  133. /// </summary>
  134. /// <param name="evt">Event data.</param>
  135. void OnMouseDown(MouseDownEvent evt)
  136. {
  137. // Close the window if the user clicks outside the dialogue.
  138. if (!worldBound.Contains(evt.mousePosition))
  139. {
  140. Close();
  141. }
  142. }
  143. /// <summary>
  144. /// Geometry changed event.
  145. /// </summary>
  146. /// <param name="evt">Event data.</param>
  147. void OnGeometryChange(GeometryChangedEvent evt)
  148. {
  149. // Close to avoid the dialogue getting detached.
  150. Close();
  151. }
  152. }
  153. }