Skip to content

Commit b699145

Browse files
committed
Use Unity built-in search window.
1 parent fa1e8c1 commit b699145

File tree

3 files changed

+136
-110
lines changed

3 files changed

+136
-110
lines changed

Editor/UInspectorPlus/InspectorPlus.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ internal class InspectorPlus: EditorWindow, IHasCustomMenu {
1212
"Unless you know exactly what you are doing, do not use this plugin " +
1313
"or you may likely to corrupt your project or even crashes the editor!";
1414
private readonly List<InspectorDrawer[]> drawers = new List<InspectorDrawer[]>();
15-
private readonly TypeMatcher typeMatcher = new TypeMatcher();
15+
private TypeMatcher typeMatcher;
1616
private static readonly string[] searchModes = new[] { "Selected Component Members", "Types" };
1717
private static readonly string[] titles = new[] { "Inspector+", "Type Search" };
1818
private string searchText;
@@ -33,13 +33,14 @@ private void OnEnable() {
3333
titleContent = new GUIContent(titles[searchMode], EditorGUIUtility.FindTexture("UnityEditor.InspectorWindow"));
3434
Initialize();
3535
OnFocus();
36+
typeMatcher = CreateInstance<TypeMatcher>();
3637
typeMatcher.OnRequestRedraw += Repaint;
3738
typeMatcher.OnSelected += TypeSelected;
3839
}
3940

4041
private void OnDisable() => typeMatcher.OnRequestRedraw -= Repaint;
4142

42-
private void OnDestroy() => typeMatcher.Dispose();
43+
private void OnDestroy() => DestroyImmediate(typeMatcher);
4344

4445
private static void TypeSelected(Type type) => InspectorChildWindow.OpenStatic(type, true, true, true, true, false, null);
4546

Editor/UInspectorPlus/MethodPropertyDrawer.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using UnityEngine;
22
using UnityEditor;
3+
using UnityEditor.Experimental.GraphView;
34
using System;
45
using System.Collections.Generic;
56
using System.Linq;
@@ -686,9 +687,9 @@ private void DrawDirectField(bool readOnly, Rect? rect) {
686687
} else
687688
buttonRect = Rect.zero;
688689
if (GUI.Button(rect2, value is Type t ? $"T: {t.FullName}" : "<Null>", EditorStyles.textField) && !readOnly) {
689-
var typeMatcherPopup = new TypeMatcherPopup(null);
690-
typeMatcherPopup.OnSelected += type => rawValue = type;
691-
PopupWindow.Show(rect2, typeMatcherPopup);
690+
var typeMatcher = ScriptableObject.CreateInstance<TypeMatcher>();
691+
typeMatcher.OnSelected += type => rawValue = type;
692+
SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(Event.current.mousePosition)), typeMatcher);
692693
}
693694
EditorGUI.EndDisabledGroup();
694695
if (rect.HasValue)

Editor/UInspectorPlus/TypeMatcher.cs

Lines changed: 129 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,96 @@
44
using System.Linq;
55
using UnityEngine;
66
using UnityEditor;
7+
using UnityEditor.Experimental.GraphView;
78
using System.Threading;
89

910
namespace JLChnToZ.EditorExtensions.UInspectorPlus {
10-
internal class TypeMatcher: IDisposable {
11+
internal class NamespacedType {
12+
private static bool isRefreshing = true;
13+
private static AppDomain currentDomain;
14+
public static readonly NamespacedType root = new NamespacedType("global::");
15+
private static readonly HashSet<Type> allTypes = new HashSet<Type>();
16+
private static readonly List<Assembly> pendingAssemblies = new List<Assembly>();
17+
private Dictionary<string, NamespacedType> subNamespaces;
18+
private List<Type> types;
19+
20+
public readonly string namespaceName;
21+
22+
public static ICollection<Type> AllTypes => allTypes;
23+
24+
public ICollection<NamespacedType> SubNamespaces => subNamespaces.Values;
25+
26+
public ICollection<Type> Types => types.AsReadOnly();
27+
28+
static NamespacedType() {
29+
new Thread(Init) { IsBackground = true }.Start();
30+
}
31+
32+
private NamespacedType(string name) {
33+
namespaceName = name;
34+
subNamespaces = new Dictionary<string, NamespacedType>();
35+
types = new List<Type>();
36+
}
37+
38+
private static void Init() {
39+
if (currentDomain == null) {
40+
currentDomain = AppDomain.CurrentDomain;
41+
AddTypes(
42+
from assembly in currentDomain.GetAssemblies()
43+
from type in assembly.LooseGetTypes()
44+
select type
45+
);
46+
currentDomain.AssemblyLoad += OnAssemblyLoad;
47+
currentDomain.DomainUnload += OnAppDomainUnload;
48+
}
49+
Refresh();
50+
}
51+
52+
private static void Refresh() {
53+
if (pendingAssemblies.Count > 0) {
54+
var buffer = pendingAssemblies.ToArray();
55+
pendingAssemblies.Clear();
56+
AddTypes(
57+
from assembly in buffer
58+
from type in assembly.LooseGetTypes()
59+
select type
60+
);
61+
}
62+
isRefreshing = false;
63+
}
64+
65+
private static void AddTypes(IEnumerable<Type> types) {
66+
foreach (var type in types) {
67+
if (!allTypes.Add(type)) continue;
68+
var current = root;
69+
var ns = type.Namespace;
70+
if (!string.IsNullOrEmpty(ns))
71+
foreach (var path in ns.Split('.')) {
72+
if (!current.subNamespaces.TryGetValue(path, out var child))
73+
current.subNamespaces.Add(path, child = new NamespacedType(path));
74+
current = child;
75+
}
76+
current.types.Add(type);
77+
}
78+
}
79+
80+
private static void OnAssemblyLoad(object sender, AssemblyLoadEventArgs e) {
81+
pendingAssemblies.Add(e.LoadedAssembly);
82+
if (!isRefreshing) {
83+
isRefreshing = true;
84+
new Thread(Refresh) { IsBackground = true }.Start();
85+
}
86+
}
87+
88+
private static void OnAppDomainUnload(object sender, EventArgs e) {
89+
currentDomain = null;
90+
}
91+
}
92+
93+
internal class TypeMatcher: ScriptableObject, ISearchWindowProvider {
1194
public Thread bgWorker;
1295
public event Action OnRequestRedraw;
1396
public event Action<Type> OnSelected;
14-
private readonly HashSet<Type> searchedTypes = new HashSet<Type>();
15-
private readonly List<Assembly> pendingAssemblies = new List<Assembly>();
16-
private AppDomain currentDomain;
1797
private string searchText = string.Empty;
1898
private Type[] searchTypeResult = null;
1999
public Type genericType;
@@ -41,14 +121,6 @@ public void Draw() {
41121
if (searchTypeResult == null || searchTypeResult.Length == 0) return;
42122
GUIContent temp = new GUIContent();
43123
GUILayout.BeginVertical();
44-
/*
45-
GUILayout.Space(8);
46-
GUILayout.Label(
47-
$"Type Search Result ({searchTypeResult.Length}):",
48-
EditorStyles.boldLabel
49-
);
50-
GUILayout.Space(8);
51-
*/
52124
for (int i = 0; i < Math.Min(searchTypeResult.Length, 500); i++) {
53125
Type type = searchTypeResult[i];
54126
temp.text = type.FullName;
@@ -65,48 +137,16 @@ public void Draw() {
65137
GUILayout.Space(8);
66138
GUILayout.EndVertical();
67139
}
68-
69-
private void InitSearch() {
70-
if (currentDomain == null) {
71-
currentDomain = AppDomain.CurrentDomain;
72-
searchedTypes.UnionWith(
73-
from assembly in currentDomain.GetAssemblies()
74-
from type in assembly.LooseGetTypes()
75-
select type
76-
);
77-
currentDomain.AssemblyLoad += OnAssemblyLoad;
78-
currentDomain.DomainUnload += OnAppDomainUnload;
79-
}
80-
if (pendingAssemblies.Count > 0) {
81-
var buffer = pendingAssemblies.ToArray();
82-
pendingAssemblies.Clear();
83-
searchedTypes.UnionWith(
84-
from assembly in buffer
85-
from type in assembly.LooseGetTypes()
86-
select type
87-
);
88-
}
89-
}
90-
91-
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs e) {
92-
pendingAssemblies.Add(e.LoadedAssembly);
93-
}
94-
95-
private void OnAppDomainUnload(object sender, EventArgs e) {
96-
currentDomain = null;
97-
}
98-
99-
~TypeMatcher() => Dispose();
140+
100141

101142
private void DoSearch() {
102143
try {
103-
InitSearch();
104144
var searchText = this.searchText;
105145
while (true) {
106146
Thread.Sleep(100);
107147
List<Type> searchTypeResult = new List<Type>();
108148
if (!string.IsNullOrEmpty(searchText))
109-
foreach (Type type in searchedTypes) {
149+
foreach (Type type in NamespacedType.AllTypes) {
110150
if (searchText != this.searchText) break;
111151
if (type.AssemblyQualifiedName.IndexOf(searchText, StringComparison.OrdinalIgnoreCase) >= 0 && Helper.IsTypeRseolvable(genericType, type))
112152
searchTypeResult.Add(type);
@@ -131,16 +171,7 @@ private void RequestRedraw() {
131171
if (OnRequestRedraw != null) OnRequestRedraw.Invoke();
132172
}
133173

134-
public void Dispose() {
135-
try {
136-
if (currentDomain != null) {
137-
currentDomain.AssemblyLoad -= OnAssemblyLoad;
138-
currentDomain.DomainUnload -= OnAppDomainUnload;
139-
}
140-
} catch {
141-
} finally {
142-
currentDomain = null;
143-
}
174+
private void OnDestroy() {
144175
try {
145176
if (bgWorker != null && bgWorker.IsAlive)
146177
bgWorker.Abort();
@@ -149,57 +180,49 @@ public void Dispose() {
149180
bgWorker = null;
150181
}
151182
}
152-
}
153-
154-
internal class TypeMatcherPopup: PopupWindowContent {
155-
readonly Type genericType;
156-
TypeMatcher typeMatcher;
157-
Vector2 scrollPos;
158-
159-
public event Action OnRequestRedraw;
160-
161-
public event Action<Type> OnSelected;
162-
163-
public TypeMatcherPopup(Type genericType) {
164-
this.genericType = genericType;
165-
}
166-
167-
public override Vector2 GetWindowSize() => new Vector2(
168-
Mathf.Max(500, typeMatcher?.Width ?? 0 + 25),
169-
EditorGUIUtility.singleLineHeight * Mathf.Clamp((typeMatcher?.SearchResultCount ?? 0) + 3, 5, 20)
170-
);
171-
172-
public override void OnGUI(Rect rect) {
173-
if (typeMatcher == null) return;
174-
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
175-
typeMatcher.SearchText = Helper.ToolbarSearchField(typeMatcher.SearchText ?? string.Empty);
176-
EditorGUILayout.EndHorizontal();
177-
scrollPos = EditorGUILayout.BeginScrollView(scrollPos);
178-
typeMatcher.Draw();
179-
EditorGUILayout.EndScrollView();
180-
}
181183

182-
public override void OnOpen() {
183-
if (typeMatcher == null) {
184-
typeMatcher = new TypeMatcher {
185-
genericType = genericType,
186-
};
187-
if (OnRequestRedraw != null) typeMatcher.OnRequestRedraw += OnRequestRedraw;
188-
typeMatcher.OnSelected += Selected;
189-
scrollPos = Vector2.zero;
184+
public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context) {
185+
var searchTreeEntries = new List<SearchTreeEntry> {
186+
new SearchTreeGroupEntry(new GUIContent("Types")),
187+
};
188+
var typeIconContent = EditorGUIUtility.IconContent("Assembly Icon");
189+
if (NamespacedType.root.Types.Count > 0) {
190+
searchTreeEntries.Add(new SearchTreeGroupEntry(new GUIContent(NamespacedType.root.namespaceName), 1));
191+
foreach (var type in NamespacedType.root.Types)
192+
searchTreeEntries.Add(new SearchTreeEntry(new GUIContent(type.Name)) {
193+
level = 2,
194+
userData = type,
195+
});
190196
}
191-
}
192-
193-
public override void OnClose() {
194-
if (typeMatcher != null) {
195-
typeMatcher.Dispose();
196-
typeMatcher = null;
197+
var stack = new Stack<(Queue<NamespacedType>, ICollection<Type>)>();
198+
stack.Push((new Queue<NamespacedType>(NamespacedType.root.SubNamespaces), null));
199+
while (stack.Count > 0) {
200+
var (pendingChildren, types) = stack.Peek();
201+
if (pendingChildren.Count > 0) {
202+
var child = pendingChildren.Dequeue();
203+
searchTreeEntries.Add(new SearchTreeGroupEntry(new GUIContent(child.namespaceName), stack.Count));
204+
stack.Push((new Queue<NamespacedType>(child.SubNamespaces), child.Types));
205+
continue;
206+
}
207+
if (types != null) {
208+
foreach (var type in types) {
209+
if (!Helper.IsTypeRseolvable(genericType, type)) continue;
210+
var objContent = type.IsSubclassOf(typeof(UnityEngine.Object)) ? EditorGUIUtility.ObjectContent(null, type) : typeIconContent;
211+
var name = type.Name;
212+
searchTreeEntries.Add(new SearchTreeEntry(new GUIContent(objContent) { text = name.Substring(name.LastIndexOf('.') + 1) }) {
213+
level = stack.Count,
214+
userData = type,
215+
});
216+
}
217+
}
218+
stack.Pop();
197219
}
220+
return searchTreeEntries;
198221
}
199222

200-
void Selected(Type type) {
201-
OnSelected?.Invoke(type);
202-
editorWindow.Close();
223+
public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context) {
224+
OnSelected?.Invoke(entry.userData as Type);
225+
return true;
203226
}
204227
}
205228

@@ -246,12 +269,13 @@ public void Draw() {
246269
rect = EditorGUI.PrefixLabel(rect, new GUIContent(constraints[i].Name));
247270
if (GUI.Button(rect, resolvedTypes[i] != null ? $"T: {resolvedTypes[i].FullName}" : "", EditorStyles.textField)) {
248271
int index = i;
249-
var typeMatcherPopup = new TypeMatcherPopup(constraints[i]);
250-
typeMatcherPopup.OnSelected += type => {
272+
var typeMatcher = ScriptableObject.CreateInstance<TypeMatcher>();
273+
typeMatcher.genericType = constraints[i];
274+
typeMatcher.OnSelected += type => {
251275
resolvedTypes[index] = type;
252276
subGUI[index] = null;
253277
};
254-
PopupWindow.Show(rect, typeMatcherPopup);
278+
SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(Event.current.mousePosition)), typeMatcher);
255279
}
256280
if (resolvedTypes[i] != null && resolvedTypes[i].ContainsGenericParameters) {
257281
if (subGUI[i] == null) subGUI[i] = new TypeResolverGUI(resolvedTypes[i]);

0 commit comments

Comments
 (0)