Geen omschrijving
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SearcherWindow.cs 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using JetBrains.Annotations;
  5. using UnityEngine;
  6. using UnityEngine.UIElements;
  7. namespace UnityEditor.Searcher
  8. {
  9. [PublicAPI]
  10. public class SearcherWindow : EditorWindow
  11. {
  12. [PublicAPI]
  13. public struct Alignment
  14. {
  15. [PublicAPI]
  16. public enum Horizontal { Left = 0, Center, Right }
  17. [PublicAPI]
  18. public enum Vertical { Top = 0, Center, Bottom }
  19. public readonly Vertical vertical;
  20. public readonly Horizontal horizontal;
  21. public Alignment(Vertical v, Horizontal h)
  22. {
  23. vertical = v;
  24. horizontal = h;
  25. }
  26. }
  27. const string k_DatabaseDirectory = "/../Library/Searcher";
  28. static readonly float k_SearcherDefaultWidth = 300;
  29. static readonly float k_DetailsDefaultWidth = 200;
  30. static readonly float k_DefaultHeight = 300;
  31. static readonly Vector2 k_MinSize = new Vector2(300, 150);
  32. static Vector2 s_Size = Vector2.zero;
  33. static IEnumerable<SearcherItem> s_Items;
  34. static Searcher s_Searcher;
  35. static Func<SearcherItem, bool> s_ItemSelectedDelegate;
  36. Action<Searcher.AnalyticsEvent> m_AnalyticsDataDelegate;
  37. SearcherControl m_SearcherControl;
  38. Vector2 m_OriginalMousePos;
  39. Rect m_OriginalWindowPos;
  40. Rect m_NewWindowPos;
  41. bool m_IsMouseDownOnResizer;
  42. bool m_IsMouseDownOnTitle;
  43. Focusable m_FocusedBefore;
  44. static Vector2 Size
  45. {
  46. get
  47. {
  48. if (s_Size == Vector2.zero)
  49. {
  50. s_Size = s_Searcher != null && s_Searcher.Adapter.HasDetailsPanel
  51. ? new Vector2(k_SearcherDefaultWidth + k_DetailsDefaultWidth, k_DefaultHeight)
  52. : new Vector2(k_SearcherDefaultWidth, k_DefaultHeight);
  53. }
  54. return s_Size;
  55. }
  56. set => s_Size = value;
  57. }
  58. public static void Show(
  59. EditorWindow host,
  60. IList<SearcherItem> items,
  61. string title,
  62. Func<SearcherItem, bool> itemSelectedDelegate,
  63. Vector2 displayPosition,
  64. Alignment align = default)
  65. {
  66. Show(host, items, title, Application.dataPath + k_DatabaseDirectory, itemSelectedDelegate, displayPosition, align);
  67. }
  68. public static void Show(
  69. EditorWindow host,
  70. IList<SearcherItem> items,
  71. ISearcherAdapter adapter,
  72. Func<SearcherItem, bool> itemSelectedDelegate,
  73. Vector2 displayPosition,
  74. Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
  75. Alignment align = default)
  76. {
  77. Show(host, items, adapter, Application.dataPath + k_DatabaseDirectory, itemSelectedDelegate,
  78. displayPosition, analyticsDataDelegate, align);
  79. }
  80. public static void Show(
  81. EditorWindow host,
  82. IList<SearcherItem> items,
  83. string title,
  84. string directoryPath,
  85. Func<SearcherItem, bool> itemSelectedDelegate,
  86. Vector2 displayPosition,
  87. Alignment align = default)
  88. {
  89. s_Items = items;
  90. var databaseDir = directoryPath;
  91. var database = SearcherDatabase.Create(s_Items.ToList(), databaseDir);
  92. s_Searcher = new Searcher(database, title);
  93. Show(host, s_Searcher, itemSelectedDelegate, displayPosition, null, align);
  94. }
  95. public static void Show(
  96. EditorWindow host,
  97. IEnumerable<SearcherItem> items,
  98. ISearcherAdapter adapter,
  99. string directoryPath,
  100. Func<SearcherItem, bool> itemSelectedDelegate,
  101. Vector2 displayPosition,
  102. Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
  103. Alignment align = default)
  104. {
  105. s_Items = items;
  106. var databaseDir = directoryPath;
  107. var database = SearcherDatabase.Create(s_Items.ToList(), databaseDir);
  108. s_Searcher = new Searcher(database, adapter);
  109. Show(host, s_Searcher, itemSelectedDelegate, displayPosition, analyticsDataDelegate, align);
  110. }
  111. public static void Show(
  112. EditorWindow host,
  113. Searcher searcher,
  114. Func<SearcherItem, bool> itemSelectedDelegate,
  115. Vector2 displayPosition,
  116. Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
  117. Alignment align = default)
  118. {
  119. var position = GetPosition(host, displayPosition, align);
  120. var rect = new Rect(GetPositionWithAlignment(position + host.position.position, Size, align), Size);
  121. Show(host, searcher, itemSelectedDelegate, analyticsDataDelegate, rect);
  122. }
  123. public static void Show(
  124. EditorWindow host,
  125. Searcher searcher,
  126. Func<SearcherItem, bool> itemSelectedDelegate,
  127. Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
  128. Rect rect)
  129. {
  130. s_Searcher = searcher;
  131. s_ItemSelectedDelegate = itemSelectedDelegate;
  132. var window = CreateInstance<SearcherWindow>();
  133. window.m_AnalyticsDataDelegate = analyticsDataDelegate;
  134. window.position = rect;
  135. window.ShowPopup();
  136. window.Focus();
  137. }
  138. public static Vector2 GetPositionWithAlignment(Vector2 pos, Vector2 size, Alignment align)
  139. {
  140. var x = pos.x;
  141. var y = pos.y;
  142. switch (align.horizontal)
  143. {
  144. case Alignment.Horizontal.Center:
  145. x -= size.x / 2;
  146. break;
  147. case Alignment.Horizontal.Right:
  148. x -= size.x;
  149. break;
  150. }
  151. switch (align.vertical)
  152. {
  153. case Alignment.Vertical.Center:
  154. y -= size.y / 2;
  155. break;
  156. case Alignment.Vertical.Bottom:
  157. y -= size.y;
  158. break;
  159. }
  160. return new Vector2(x, y);
  161. }
  162. static Vector2 GetPosition(EditorWindow host, Vector2 displayPosition, Alignment align)
  163. {
  164. var x = displayPosition.x;
  165. var y = displayPosition.y;
  166. // Searcher overlaps with the right boundary.
  167. if (x + Size.x >= host.position.size.x)
  168. {
  169. switch (align.horizontal)
  170. {
  171. case Alignment.Horizontal.Center:
  172. x -= Size.x / 2;
  173. break;
  174. case Alignment.Horizontal.Right:
  175. x -= Size.x;
  176. break;
  177. }
  178. }
  179. // The displayPosition should be in window world space but the
  180. // EditorWindow.position is actually the rootVisualElement
  181. // rectangle, not including the tabs area. So we need to do a
  182. // small correction here.
  183. y -= host.rootVisualElement.resolvedStyle.top;
  184. // Searcher overlaps with the bottom boundary.
  185. if (y + Size.y >= host.position.size.y)
  186. {
  187. switch (align.vertical)
  188. {
  189. case Alignment.Vertical.Center:
  190. y -= Size.y / 2;
  191. break;
  192. case Alignment.Vertical.Bottom:
  193. y -= Size.y;
  194. break;
  195. }
  196. }
  197. return new Vector2(x, y);
  198. }
  199. void OnEnable()
  200. {
  201. m_SearcherControl = new SearcherControl();
  202. m_SearcherControl.Setup(s_Searcher, SelectionCallback, OnAnalyticsDataCallback, s_Searcher.Adapter.OnSearchResultsFilter);
  203. m_SearcherControl.TitleLabel.RegisterCallback<MouseDownEvent>(OnTitleMouseDown);
  204. m_SearcherControl.TitleLabel.RegisterCallback<MouseUpEvent>(OnTitleMouseUp);
  205. m_SearcherControl.Resizer.RegisterCallback<MouseDownEvent>(OnResizerMouseDown);
  206. m_SearcherControl.Resizer.RegisterCallback<MouseUpEvent>(OnResizerMouseUp);
  207. var root = rootVisualElement;
  208. root.style.flexGrow = 1;
  209. root.Add(m_SearcherControl);
  210. }
  211. void OnDisable()
  212. {
  213. m_SearcherControl.TitleLabel.UnregisterCallback<MouseDownEvent>(OnTitleMouseDown);
  214. m_SearcherControl.TitleLabel.UnregisterCallback<MouseUpEvent>(OnTitleMouseUp);
  215. m_SearcherControl.Resizer.UnregisterCallback<MouseDownEvent>(OnResizerMouseDown);
  216. m_SearcherControl.Resizer.UnregisterCallback<MouseUpEvent>(OnResizerMouseUp);
  217. }
  218. void OnTitleMouseDown(MouseDownEvent evt)
  219. {
  220. if (evt.button != (int)MouseButton.LeftMouse)
  221. return;
  222. m_IsMouseDownOnTitle = true;
  223. m_NewWindowPos = position;
  224. m_OriginalWindowPos = position;
  225. m_OriginalMousePos = evt.mousePosition;
  226. m_FocusedBefore = rootVisualElement.panel.focusController.focusedElement;
  227. m_SearcherControl.TitleLabel.RegisterCallback<MouseMoveEvent>(OnTitleMouseMove);
  228. m_SearcherControl.TitleLabel.RegisterCallback<KeyDownEvent>(OnSearcherKeyDown);
  229. m_SearcherControl.TitleLabel.CaptureMouse();
  230. }
  231. void OnTitleMouseUp(MouseUpEvent evt)
  232. {
  233. if (evt.button != (int)MouseButton.LeftMouse)
  234. return;
  235. if (!m_SearcherControl.TitleLabel.HasMouseCapture())
  236. return;
  237. FinishMove();
  238. }
  239. void FinishMove()
  240. {
  241. m_SearcherControl.TitleLabel.UnregisterCallback<MouseMoveEvent>(OnTitleMouseMove);
  242. m_SearcherControl.TitleLabel.UnregisterCallback<KeyDownEvent>(OnSearcherKeyDown);
  243. m_SearcherControl.TitleLabel.ReleaseMouse();
  244. m_FocusedBefore?.Focus();
  245. m_IsMouseDownOnTitle = false;
  246. }
  247. void OnTitleMouseMove(MouseMoveEvent evt)
  248. {
  249. var delta = evt.mousePosition - m_OriginalMousePos;
  250. // TODO Temporary fix for Visual Scripting 1st drop. Find why position.position is 0,0 on MacOs in MouseMoveEvent
  251. // Bug occurs with Unity 2019.2.0a13
  252. #if UNITY_EDITOR_OSX
  253. m_NewWindowPos = new Rect(m_NewWindowPos.position + delta, position.size);
  254. #else
  255. m_NewWindowPos = new Rect(position.position + delta, position.size);
  256. #endif
  257. Repaint();
  258. }
  259. void OnResizerMouseDown(MouseDownEvent evt)
  260. {
  261. if (evt.button != (int)MouseButton.LeftMouse)
  262. return;
  263. m_IsMouseDownOnResizer = true;
  264. m_NewWindowPos = position;
  265. m_OriginalWindowPos = position;
  266. m_OriginalMousePos = evt.mousePosition;
  267. m_FocusedBefore = rootVisualElement.panel.focusController.focusedElement;
  268. m_SearcherControl.Resizer.RegisterCallback<MouseMoveEvent>(OnResizerMouseMove);
  269. m_SearcherControl.Resizer.RegisterCallback<KeyDownEvent>(OnSearcherKeyDown);
  270. m_SearcherControl.Resizer.CaptureMouse();
  271. }
  272. void OnResizerMouseUp(MouseUpEvent evt)
  273. {
  274. if (evt.button != (int)MouseButton.LeftMouse)
  275. return;
  276. if (!m_SearcherControl.Resizer.HasMouseCapture())
  277. return;
  278. FinishResize();
  279. }
  280. void FinishResize()
  281. {
  282. m_SearcherControl.Resizer.UnregisterCallback<MouseMoveEvent>(OnResizerMouseMove);
  283. m_SearcherControl.Resizer.UnregisterCallback<KeyDownEvent>(OnSearcherKeyDown);
  284. m_SearcherControl.Resizer.ReleaseMouse();
  285. m_FocusedBefore?.Focus();
  286. m_IsMouseDownOnResizer = false;
  287. }
  288. void OnResizerMouseMove(MouseMoveEvent evt)
  289. {
  290. var delta = evt.mousePosition - m_OriginalMousePos;
  291. Size = m_OriginalWindowPos.size + delta;
  292. Size = new Vector2(Math.Max(k_MinSize.x, Size.x), Math.Max(k_MinSize.y, Size.y));
  293. // TODO Temporary fix for Visual Scripting 1st drop. Find why position.position is 0,0 on MacOs in MouseMoveEvent
  294. // Bug occurs with Unity 2019.2.0a13
  295. #if UNITY_EDITOR_OSX
  296. m_NewWindowPos = new Rect(m_NewWindowPos.position, Size);
  297. #else
  298. m_NewWindowPos = new Rect(position.position, Size);
  299. #endif
  300. Repaint();
  301. }
  302. void OnSearcherKeyDown(KeyDownEvent evt)
  303. {
  304. if (evt.keyCode == KeyCode.Escape)
  305. {
  306. if (m_IsMouseDownOnTitle)
  307. {
  308. FinishMove();
  309. position = m_OriginalWindowPos;
  310. }
  311. else if (m_IsMouseDownOnResizer)
  312. {
  313. FinishResize();
  314. position = m_OriginalWindowPos;
  315. }
  316. }
  317. }
  318. void OnGUI()
  319. {
  320. if ((m_IsMouseDownOnTitle || m_IsMouseDownOnResizer) && Event.current.type == EventType.Layout)
  321. position = m_NewWindowPos;
  322. }
  323. void SelectionCallback(SearcherItem item)
  324. {
  325. // Don't close the window if a category is selected (only categories/titles have children, node entries are leaf elements)
  326. // We want to prevent collapsing the window due to accidental double-clicks on a title entry, for instance
  327. if (item != null && item.HasChildren)
  328. return;
  329. if (s_ItemSelectedDelegate == null || s_ItemSelectedDelegate(item))
  330. Close();
  331. }
  332. void OnAnalyticsDataCallback(Searcher.AnalyticsEvent item)
  333. {
  334. m_AnalyticsDataDelegate?.Invoke(item);
  335. }
  336. void OnLostFocus()
  337. {
  338. if (m_IsMouseDownOnTitle)
  339. {
  340. FinishMove();
  341. }
  342. else if (m_IsMouseDownOnResizer)
  343. {
  344. FinishResize();
  345. }
  346. // TODO: HACK - ListView's scroll view steals focus using the scheduler.
  347. EditorApplication.update += HackDueToCloseOnLostFocusCrashing;
  348. }
  349. // See: https://fogbugz.unity3d.com/f/cases/1004504/
  350. void HackDueToCloseOnLostFocusCrashing()
  351. {
  352. // Notify user that the searcher action was cancelled.
  353. s_ItemSelectedDelegate?.Invoke(null);
  354. Close();
  355. // ReSharper disable once DelegateSubtraction
  356. EditorApplication.update -= HackDueToCloseOnLostFocusCrashing;
  357. }
  358. }
  359. }