UnityGame/Library/PackageCache/com.unity.searcher/Editor/Searcher/SearcherWindow.cs

429 lines
14 KiB
C#
Raw Permalink Normal View History

2024-10-27 10:53:47 +03:00
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;
}
}
}