123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using JetBrains.Annotations;
- using UnityEngine;
- using UnityEngine.UIElements;
-
- namespace UnityEditor.Searcher
- {
- [PublicAPI]
- public class SearcherWindow : EditorWindow
- {
- [PublicAPI]
- public struct Alignment
- {
- [PublicAPI]
- public enum Horizontal { Left = 0, Center, Right }
- [PublicAPI]
- public enum Vertical { Top = 0, Center, Bottom }
-
- public readonly Vertical vertical;
- public readonly Horizontal horizontal;
-
- public Alignment(Vertical v, Horizontal h)
- {
- vertical = v;
- horizontal = h;
- }
- }
-
- const string k_DatabaseDirectory = "/../Library/Searcher";
-
- static readonly float k_SearcherDefaultWidth = 300;
- static readonly float k_DetailsDefaultWidth = 200;
- static readonly float k_DefaultHeight = 300;
- static readonly Vector2 k_MinSize = new Vector2(300, 150);
-
- static Vector2 s_Size = Vector2.zero;
- static IEnumerable<SearcherItem> s_Items;
- static Searcher s_Searcher;
- static Func<SearcherItem, bool> s_ItemSelectedDelegate;
-
- Action<Searcher.AnalyticsEvent> m_AnalyticsDataDelegate;
- SearcherControl m_SearcherControl;
- Vector2 m_OriginalMousePos;
- Rect m_OriginalWindowPos;
- Rect m_NewWindowPos;
- bool m_IsMouseDownOnResizer;
- bool m_IsMouseDownOnTitle;
- Focusable m_FocusedBefore;
-
- static Vector2 Size
- {
- get
- {
- if (s_Size == Vector2.zero)
- {
- s_Size = s_Searcher != null && s_Searcher.Adapter.HasDetailsPanel
- ? new Vector2(k_SearcherDefaultWidth + k_DetailsDefaultWidth, k_DefaultHeight)
- : new Vector2(k_SearcherDefaultWidth, k_DefaultHeight);
- }
-
- return s_Size;
- }
- set => s_Size = value;
- }
-
- public static void Show(
- EditorWindow host,
- IList<SearcherItem> items,
- string title,
- Func<SearcherItem, bool> itemSelectedDelegate,
- Vector2 displayPosition,
- Alignment align = default)
- {
- Show(host, items, title, Application.dataPath + k_DatabaseDirectory, itemSelectedDelegate, displayPosition, align);
- }
-
- public static void Show(
- EditorWindow host,
- IList<SearcherItem> items,
- ISearcherAdapter adapter,
- Func<SearcherItem, bool> itemSelectedDelegate,
- Vector2 displayPosition,
- Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
- Alignment align = default)
- {
- Show(host, items, adapter, Application.dataPath + k_DatabaseDirectory, itemSelectedDelegate,
- displayPosition, analyticsDataDelegate, align);
- }
-
- public static void Show(
- EditorWindow host,
- IList<SearcherItem> items,
- string title,
- string directoryPath,
- Func<SearcherItem, bool> itemSelectedDelegate,
- Vector2 displayPosition,
- Alignment align = default)
- {
- s_Items = items;
- var databaseDir = directoryPath;
- var database = SearcherDatabase.Create(s_Items.ToList(), databaseDir);
- s_Searcher = new Searcher(database, title);
-
- Show(host, s_Searcher, itemSelectedDelegate, displayPosition, null, align);
- }
-
- public static void Show(
- EditorWindow host,
- IEnumerable<SearcherItem> items,
- ISearcherAdapter adapter,
- string directoryPath,
- Func<SearcherItem, bool> itemSelectedDelegate,
- Vector2 displayPosition,
- Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
- Alignment align = default)
- {
- s_Items = items;
- var databaseDir = directoryPath;
- var database = SearcherDatabase.Create(s_Items.ToList(), databaseDir);
- s_Searcher = new Searcher(database, adapter);
-
- Show(host, s_Searcher, itemSelectedDelegate, displayPosition, analyticsDataDelegate, align);
- }
-
- public static void Show(
- EditorWindow host,
- Searcher searcher,
- Func<SearcherItem, bool> itemSelectedDelegate,
- Vector2 displayPosition,
- Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
- Alignment align = default)
- {
- var position = GetPosition(host, displayPosition, align);
- var rect = new Rect(GetPositionWithAlignment(position + host.position.position, Size, align), Size);
-
- Show(host, searcher, itemSelectedDelegate, analyticsDataDelegate, rect);
- }
- public static void Show(
- EditorWindow host,
- Searcher searcher,
- Func<SearcherItem, bool> itemSelectedDelegate,
- Action<Searcher.AnalyticsEvent> analyticsDataDelegate,
- Rect rect)
- {
- s_Searcher = searcher;
- s_ItemSelectedDelegate = itemSelectedDelegate;
-
- var window = CreateInstance<SearcherWindow>();
- window.m_AnalyticsDataDelegate = analyticsDataDelegate;
- window.position = rect;
- window.ShowPopup();
- window.Focus();
- }
-
- public static Vector2 GetPositionWithAlignment(Vector2 pos, Vector2 size, Alignment align)
- {
- var x = pos.x;
- var y = pos.y;
-
- switch (align.horizontal)
- {
- case Alignment.Horizontal.Center:
- x -= size.x / 2;
- break;
-
- case Alignment.Horizontal.Right:
- x -= size.x;
- break;
- }
-
- switch (align.vertical)
- {
- case Alignment.Vertical.Center:
- y -= size.y / 2;
- break;
-
- case Alignment.Vertical.Bottom:
- y -= size.y;
- break;
- }
-
- return new Vector2(x, y);
- }
-
- static Vector2 GetPosition(EditorWindow host, Vector2 displayPosition, Alignment align)
- {
- var x = displayPosition.x;
- var y = displayPosition.y;
-
- // Searcher overlaps with the right boundary.
- if (x + Size.x >= host.position.size.x)
- {
- switch (align.horizontal)
- {
- case Alignment.Horizontal.Center:
- x -= Size.x / 2;
- break;
-
- case Alignment.Horizontal.Right:
- x -= Size.x;
- break;
- }
- }
-
- // The displayPosition should be in window world space but the
- // EditorWindow.position is actually the rootVisualElement
- // rectangle, not including the tabs area. So we need to do a
- // small correction here.
- y -= host.rootVisualElement.resolvedStyle.top;
-
- // Searcher overlaps with the bottom boundary.
- if (y + Size.y >= host.position.size.y)
- {
- switch (align.vertical)
- {
- case Alignment.Vertical.Center:
- y -= Size.y / 2;
- break;
-
- case Alignment.Vertical.Bottom:
- y -= Size.y;
- break;
- }
- }
-
- return new Vector2(x, y);
- }
-
- void OnEnable()
- {
- m_SearcherControl = new SearcherControl();
- m_SearcherControl.Setup(s_Searcher, SelectionCallback, OnAnalyticsDataCallback, s_Searcher.Adapter.OnSearchResultsFilter);
-
- m_SearcherControl.TitleLabel.RegisterCallback<MouseDownEvent>(OnTitleMouseDown);
- m_SearcherControl.TitleLabel.RegisterCallback<MouseUpEvent>(OnTitleMouseUp);
-
- m_SearcherControl.Resizer.RegisterCallback<MouseDownEvent>(OnResizerMouseDown);
- m_SearcherControl.Resizer.RegisterCallback<MouseUpEvent>(OnResizerMouseUp);
-
- var root = rootVisualElement;
- root.style.flexGrow = 1;
- root.Add(m_SearcherControl);
- }
-
- void OnDisable()
- {
- m_SearcherControl.TitleLabel.UnregisterCallback<MouseDownEvent>(OnTitleMouseDown);
- m_SearcherControl.TitleLabel.UnregisterCallback<MouseUpEvent>(OnTitleMouseUp);
-
- m_SearcherControl.Resizer.UnregisterCallback<MouseDownEvent>(OnResizerMouseDown);
- m_SearcherControl.Resizer.UnregisterCallback<MouseUpEvent>(OnResizerMouseUp);
- }
-
- void OnTitleMouseDown(MouseDownEvent evt)
- {
- if (evt.button != (int)MouseButton.LeftMouse)
- return;
-
- m_IsMouseDownOnTitle = true;
-
- m_NewWindowPos = position;
- m_OriginalWindowPos = position;
- m_OriginalMousePos = evt.mousePosition;
-
- m_FocusedBefore = rootVisualElement.panel.focusController.focusedElement;
-
- m_SearcherControl.TitleLabel.RegisterCallback<MouseMoveEvent>(OnTitleMouseMove);
- m_SearcherControl.TitleLabel.RegisterCallback<KeyDownEvent>(OnSearcherKeyDown);
- m_SearcherControl.TitleLabel.CaptureMouse();
- }
-
- void OnTitleMouseUp(MouseUpEvent evt)
- {
- if (evt.button != (int)MouseButton.LeftMouse)
- return;
-
- if (!m_SearcherControl.TitleLabel.HasMouseCapture())
- return;
-
- FinishMove();
- }
-
- void FinishMove()
- {
- m_SearcherControl.TitleLabel.UnregisterCallback<MouseMoveEvent>(OnTitleMouseMove);
- m_SearcherControl.TitleLabel.UnregisterCallback<KeyDownEvent>(OnSearcherKeyDown);
- m_SearcherControl.TitleLabel.ReleaseMouse();
- m_FocusedBefore?.Focus();
- m_IsMouseDownOnTitle = false;
- }
-
- void OnTitleMouseMove(MouseMoveEvent evt)
- {
- var delta = evt.mousePosition - m_OriginalMousePos;
-
- // TODO Temporary fix for Visual Scripting 1st drop. Find why position.position is 0,0 on MacOs in MouseMoveEvent
- // Bug occurs with Unity 2019.2.0a13
- #if UNITY_EDITOR_OSX
- m_NewWindowPos = new Rect(m_NewWindowPos.position + delta, position.size);
- #else
- m_NewWindowPos = new Rect(position.position + delta, position.size);
- #endif
- Repaint();
- }
-
- void OnResizerMouseDown(MouseDownEvent evt)
- {
- if (evt.button != (int)MouseButton.LeftMouse)
- return;
-
- m_IsMouseDownOnResizer = true;
-
- m_NewWindowPos = position;
- m_OriginalWindowPos = position;
- m_OriginalMousePos = evt.mousePosition;
-
- m_FocusedBefore = rootVisualElement.panel.focusController.focusedElement;
-
- m_SearcherControl.Resizer.RegisterCallback<MouseMoveEvent>(OnResizerMouseMove);
- m_SearcherControl.Resizer.RegisterCallback<KeyDownEvent>(OnSearcherKeyDown);
- m_SearcherControl.Resizer.CaptureMouse();
- }
-
- void OnResizerMouseUp(MouseUpEvent evt)
- {
- if (evt.button != (int)MouseButton.LeftMouse)
- return;
-
- if (!m_SearcherControl.Resizer.HasMouseCapture())
- return;
-
- FinishResize();
- }
-
- void FinishResize()
- {
- m_SearcherControl.Resizer.UnregisterCallback<MouseMoveEvent>(OnResizerMouseMove);
- m_SearcherControl.Resizer.UnregisterCallback<KeyDownEvent>(OnSearcherKeyDown);
- m_SearcherControl.Resizer.ReleaseMouse();
- m_FocusedBefore?.Focus();
- m_IsMouseDownOnResizer = false;
- }
-
- void OnResizerMouseMove(MouseMoveEvent evt)
- {
- var delta = evt.mousePosition - m_OriginalMousePos;
- Size = m_OriginalWindowPos.size + delta;
- Size = new Vector2(Math.Max(k_MinSize.x, Size.x), Math.Max(k_MinSize.y, Size.y));
-
- // TODO Temporary fix for Visual Scripting 1st drop. Find why position.position is 0,0 on MacOs in MouseMoveEvent
- // Bug occurs with Unity 2019.2.0a13
- #if UNITY_EDITOR_OSX
- m_NewWindowPos = new Rect(m_NewWindowPos.position, Size);
- #else
- m_NewWindowPos = new Rect(position.position, Size);
- #endif
- Repaint();
- }
-
- void OnSearcherKeyDown(KeyDownEvent evt)
- {
- if (evt.keyCode == KeyCode.Escape)
- {
- if (m_IsMouseDownOnTitle)
- {
- FinishMove();
- position = m_OriginalWindowPos;
- }
- else if (m_IsMouseDownOnResizer)
- {
- FinishResize();
- position = m_OriginalWindowPos;
- }
- }
- }
-
- void OnGUI()
- {
- if ((m_IsMouseDownOnTitle || m_IsMouseDownOnResizer) && Event.current.type == EventType.Layout)
- position = m_NewWindowPos;
- }
-
- void SelectionCallback(SearcherItem item)
- {
- // Don't close the window if a category is selected (only categories/titles have children, node entries are leaf elements)
- // We want to prevent collapsing the window due to accidental double-clicks on a title entry, for instance
- if (item != null && item.HasChildren)
- return;
-
- if (s_ItemSelectedDelegate == null || s_ItemSelectedDelegate(item))
- Close();
- }
-
- void OnAnalyticsDataCallback(Searcher.AnalyticsEvent item)
- {
- m_AnalyticsDataDelegate?.Invoke(item);
- }
-
- void OnLostFocus()
- {
- if (m_IsMouseDownOnTitle)
- {
- FinishMove();
- }
- else if (m_IsMouseDownOnResizer)
- {
- FinishResize();
- }
-
- // TODO: HACK - ListView's scroll view steals focus using the scheduler.
- EditorApplication.update += HackDueToCloseOnLostFocusCrashing;
- }
-
- // See: https://fogbugz.unity3d.com/f/cases/1004504/
- void HackDueToCloseOnLostFocusCrashing()
- {
- // Notify user that the searcher action was cancelled.
- s_ItemSelectedDelegate?.Invoke(null);
-
- Close();
-
- // ReSharper disable once DelegateSubtraction
- EditorApplication.update -= HackDueToCloseOnLostFocusCrashing;
- }
- }
- }
|