Skip to content

Commit d1dafa0

Browse files
committed
feat: Search and select menu item
1 parent 3ed04ad commit d1dafa0

File tree

4 files changed

+371
-8
lines changed

4 files changed

+371
-8
lines changed

Editor/Scripts/AssetQuickAccessWindow.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ private void CreateGUI()
276276
};
277277
toolbarMenu.menu.AppendAction("Add External File", AddExternalFile);
278278
toolbarMenu.menu.AppendAction("Add External Folder", AddExternalFolder);
279+
toolbarMenu.menu.AppendAction("Add Menu Item", AddMenuItemEditor);
279280
toolbarMenu.menu.AppendAction("Add URL", AddUrlEditor);
280281
toolbarMenu.menu.AppendSeparator("");
281282
toolbarMenu.menu.AppendAction("Remove All Items", _ => RemoveAllItems());
@@ -456,11 +457,23 @@ private void AddExternalFolder(DropdownMenuAction action)
456457
}
457458
}
458459

460+
private void AddMenuItemEditor(DropdownMenuAction action)
461+
{
462+
Vector2 upperCenter = position.center;
463+
upperCenter.y = position.yMin + 50;
464+
MenuItemSelectWindow.Open(upperCenter, AddMenuItem);
465+
}
466+
459467
private void AddUrlEditor(DropdownMenuAction action)
460468
{
461-
Vector2 center = position.center;
462-
center.y = position.yMin + 100;
463-
UrlEditWindow.Open(center, AddUrl);
469+
Vector2 upperCenter = position.center;
470+
upperCenter.y = position.yMin + 100;
471+
UrlEditWindow.Open(upperCenter, AddUrl);
472+
}
473+
474+
private void AddMenuItem(string menuPath, string title)
475+
{
476+
Debug.LogError("TODO: AddMenuItem");
464477
}
465478

466479
private void AddUrl(string url, string title)
Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,339 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using UnityEditor;
6+
using UnityEngine;
7+
using UnityEngine.UIElements;
8+
using static UnityEditor.PlayerSettings;
9+
10+
namespace GBG.AssetQuickAccess.Editor
11+
{
12+
//[Serializable]
13+
//public class MenuItemInfo
14+
//{
15+
// public string Path;
16+
// public bool HasValidation;
17+
// public int Priority;
18+
//}
19+
20+
internal class MenuItemSelectWindow : EditorWindow
21+
{
22+
public delegate void SubmitHandler(string menuPath, string title);
23+
24+
public static MenuItemSelectWindow Open(Vector2 upperCenterPosition, SubmitHandler onSubmit, bool showAsDropdown = true)
25+
{
26+
MenuItemSelectWindow window = CreateInstance<MenuItemSelectWindow>();
27+
window._onSubmit += onSubmit;
28+
29+
float width = 400;
30+
float height = 300;
31+
if (showAsDropdown)
32+
{
33+
Rect position = new Rect(upperCenterPosition - new Vector2(width / 2, 0), default);
34+
window.ShowAsDropDown(position, new Vector2(width, height));
35+
window.position = position;
36+
}
37+
else
38+
{
39+
window.Show();
40+
}
41+
42+
return window;
43+
}
44+
45+
public static List<string> GetAllMenuItemPaths()
46+
{
47+
List<string> menuPaths = TypeCache.GetMethodsWithAttribute<MenuItem>()
48+
.SelectMany(method => method.GetCustomAttributes<MenuItem>())
49+
.Where(attr => attr.validate == false)
50+
.Select(attr => RemoveShortcut(attr.menuItem))
51+
.Distinct()
52+
.ToList();
53+
//string[] menuPaths = AppDomain.CurrentDomain.GetAssemblies()
54+
// .SelectMany(asm => asm.GetTypes())
55+
// .SelectMany(type => type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
56+
// .SelectMany(method => method.GetCustomAttributes<MenuItem>())
57+
// .Where(attr => attr.validate == false)
58+
// .Select(attr => attr.menuItem)
59+
// .Distinct()
60+
// .ToArray();
61+
62+
return menuPaths;
63+
}
64+
65+
// TODO: 移除快捷键符号!!
66+
public static string RemoveShortcut(string menuPath)
67+
{
68+
// https://docs.unity3d.com/ScriptReference/MenuItem.html
69+
//%: Represents Ctrl on Windows and Linux. Cmd on macOS.
70+
//^: Represents Ctrl on Windows, Linux, and macOS.
71+
//#: Represents Shift.
72+
//&: Represents Alt.
73+
74+
Debug.LogError("TODO: RemoveShortcut");
75+
76+
return menuPath;
77+
}
78+
79+
//public static bool ExecuteMenuItem(MethodInfo menuMethodInfo, bool validate)
80+
//{
81+
// if (menuMethodInfo.GetParameters().Length == 0)
82+
// {
83+
// var result = menuMethodInfo.Invoke(null, new object[0]);
84+
// return !validate || (bool)result;
85+
// }
86+
//
87+
// if (menuMethodInfo.GetParameters()[0].ParameterType == typeof(MenuCommand))
88+
// {
89+
// var result = menuMethodInfo.Invoke(null, new[] { new MenuCommand(null) });
90+
// return !validate || (bool)result;
91+
// }
92+
//
93+
// return false;
94+
//}
95+
96+
97+
private SubmitHandler _onSubmit;
98+
private TextField _menuPathField;
99+
private ListView _menuPathListView;
100+
private Label _statusLabel;
101+
private Button _addButton;
102+
103+
private List<string> _allMenuPaths = new List<string>();
104+
private List<string> _filteredMenuPaths = new List<string>();
105+
106+
107+
private void OnEnable()
108+
{
109+
_allMenuPaths = GetAllMenuItemPaths();
110+
_allMenuPaths.Sort();
111+
_filteredMenuPaths.Clear();
112+
_filteredMenuPaths.AddRange(_allMenuPaths);
113+
}
114+
115+
private void CreateGUI()
116+
{
117+
rootVisualElement.style.paddingLeft = 4;
118+
rootVisualElement.style.paddingRight = 4;
119+
rootVisualElement.style.paddingTop = 4;
120+
rootVisualElement.style.paddingBottom = 4;
121+
rootVisualElement.RegisterCallback<KeyUpEvent>(HandleKeyUp);
122+
123+
// Menu path field
124+
_menuPathField = new TextField
125+
{
126+
name = "MenuPathField",
127+
label = "Menu Item",
128+
};
129+
_menuPathField.Q<Label>().style.minWidth = 70;
130+
_menuPathField.Q<Label>().style.maxWidth = 70;
131+
_menuPathField.RegisterValueChangedCallback(OnMenuPathChanged);
132+
rootVisualElement.Add(_menuPathField);
133+
134+
// Menu path ListView
135+
_menuPathListView = new ListView(_filteredMenuPaths, 28, MakeMenuPathListViewItem, BindMenuPathListViewItem)
136+
{
137+
selectionType = SelectionType.Single,
138+
showAddRemoveFooter = false,
139+
showBorder = false,
140+
showFoldoutHeader = false,
141+
showBoundCollectionSize = false,
142+
showAlternatingRowBackgrounds = AlternatingRowBackground.All,
143+
style =
144+
{
145+
flexGrow = 1,
146+
marginTop = 8,
147+
}
148+
};
149+
rootVisualElement.Add(_menuPathListView);
150+
151+
// Status label
152+
_statusLabel = new Label
153+
{
154+
text = "The current MenuItem path is invalid!",
155+
style =
156+
{
157+
height = 15,
158+
color = (Color)new Color32(200, 0, 0, 255),
159+
}
160+
};
161+
rootVisualElement.Add(_statusLabel);
162+
163+
// Operation buttons
164+
VisualElement horizontal2 = new VisualElement
165+
{
166+
style =
167+
{
168+
flexShrink = 0,
169+
flexDirection = FlexDirection.Row,
170+
}
171+
};
172+
rootVisualElement.Add(horizontal2);
173+
_addButton = new Button(TrySubmit)
174+
{
175+
name = "AddButton",
176+
text = "Add",
177+
style =
178+
{
179+
flexGrow = 1,
180+
}
181+
};
182+
_addButton.SetEnabled(false);
183+
horizontal2.Add(_addButton);
184+
Button cancelButton = new Button(Cancel)
185+
{
186+
name = "CancelButton",
187+
text = "Cancel",
188+
style =
189+
{
190+
maxWidth = 50
191+
}
192+
};
193+
horizontal2.Add(cancelButton);
194+
195+
// Focus
196+
_menuPathField.Focus();
197+
}
198+
199+
200+
private bool TryCorrectMenuPath(ref string menuPath)
201+
{
202+
if (string.IsNullOrEmpty(menuPath))
203+
{
204+
return false;
205+
}
206+
207+
bool isValidPath = false;
208+
for (int i = 0; i < _allMenuPaths.Count; i++)
209+
{
210+
if (_allMenuPaths[i].Equals(menuPath, StringComparison.OrdinalIgnoreCase))
211+
{
212+
menuPath = _allMenuPaths[i];
213+
isValidPath = true;
214+
break;
215+
}
216+
}
217+
218+
return isValidPath;
219+
}
220+
221+
private void OnMenuPathChanged(ChangeEvent<string> evt)
222+
{
223+
#region Validation
224+
225+
string menuPath = evt.newValue;
226+
bool isValidPath = TryCorrectMenuPath(ref menuPath);
227+
228+
_statusLabel.style.display = isValidPath
229+
? DisplayStyle.None
230+
: DisplayStyle.Flex;
231+
232+
_addButton.SetEnabled(isValidPath);
233+
234+
#endregion
235+
236+
237+
#region Filter
238+
239+
_filteredMenuPaths.Clear();
240+
241+
if (string.IsNullOrEmpty(menuPath))
242+
{
243+
_filteredMenuPaths.AddRange(_allMenuPaths);
244+
return;
245+
}
246+
247+
foreach (string tempMenuPath in _allMenuPaths)
248+
{
249+
if (tempMenuPath.Contains(menuPath, StringComparison.OrdinalIgnoreCase))
250+
{
251+
_filteredMenuPaths.Add(tempMenuPath);
252+
}
253+
}
254+
255+
_menuPathListView.Rebuild();
256+
257+
#endregion
258+
}
259+
260+
private VisualElement MakeMenuPathListViewItem()
261+
{
262+
return new Button
263+
{
264+
name = "MenuItemButton",
265+
style =
266+
{
267+
unityTextAlign = TextAnchor.MiddleLeft,
268+
}
269+
};
270+
}
271+
272+
private void BindMenuPathListViewItem(VisualElement element, int index)
273+
{
274+
string menuPath = _filteredMenuPaths[index];
275+
Button button = (Button)element;
276+
button.text = menuPath;
277+
button.tooltip = menuPath;
278+
button.clicked += () => _menuPathField.value = menuPath;
279+
}
280+
281+
282+
private void HandleKeyUp(KeyUpEvent evt)
283+
{
284+
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter)
285+
{
286+
if (evt.target == _menuPathField)
287+
{
288+
evt.StopImmediatePropagation();
289+
}
290+
else
291+
{
292+
evt.StopImmediatePropagation();
293+
TrySubmit();
294+
}
295+
}
296+
else if (evt.keyCode == KeyCode.Escape)
297+
{
298+
evt.StopImmediatePropagation();
299+
Cancel();
300+
}
301+
}
302+
303+
private void Cancel()
304+
{
305+
SubmitMenuItem(null, null);
306+
}
307+
308+
private void TrySubmit()
309+
{
310+
string menuPath = _menuPathField.value;
311+
bool isValidPath = TryCorrectMenuPath(ref menuPath);
312+
if (!isValidPath)
313+
{
314+
return;
315+
}
316+
317+
string menuName = string.Empty;
318+
int slashIndex = menuPath.LastIndexOf('/');
319+
if (slashIndex > -1)
320+
{
321+
menuName = menuPath.Substring(slashIndex);
322+
}
323+
324+
SubmitMenuItem(menuPath, menuName);
325+
}
326+
327+
private void SubmitMenuItem(string menuPath, string title)
328+
{
329+
if (_onSubmit != null)
330+
{
331+
SubmitHandler action = _onSubmit;
332+
_onSubmit = null;
333+
action.Invoke(menuPath, title);
334+
}
335+
336+
Close();
337+
}
338+
}
339+
}

Editor/Scripts/MenuItemSelectWindow.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)