Last active
October 25, 2024 08:16
-
-
Save anatawa12/f64acfa804d285f5640dca79536d9937 to your computer and use it in GitHub Desktop.
Typing file name in project window will select the file starts with the name.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Quick search with typing file name in project window. | |
* https://gist.github.com/anatawa12/f64acfa804d285f5640dca79536d9937 | |
* | |
* Typing file name in project window will select the file starts with the name. | |
* This is very similar to finder's quick search. | |
* | |
* MIT License | |
* | |
* Copyright (c) 2023 anatawa12 | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy | |
* of this software and associated documentation files (the "Software"), to deal | |
* in the Software without restriction, including without limitation the rights | |
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
* copies of the Software, and to permit persons to whom the Software is | |
* furnished to do so, subject to the following conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all | |
* copies or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
* SOFTWARE. | |
*/ | |
#if UNITY_EDITOR && (!ANATAWA12_GISTS_VPM_PACKAGE || GIST_f64acfa804d285f5640dca79536d9937) | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Reflection; | |
using UnityEditor; | |
using UnityEngine; | |
namespace anatawa12.gists | |
{ | |
[InitializeOnLoad] | |
internal static class ProjectWindowTypeToSelect | |
{ | |
static ProjectWindowTypeToSelect() => EditorApplication.delayCall += Initialize; | |
static void Initialize() | |
{ | |
foreach (var projectBrowserWrap in ProjectBrowserWrap.GetAllProjectBrowsers()) | |
{ | |
void Function() | |
{ | |
var listArea = projectBrowserWrap.ListArea; | |
if (listArea != null) | |
{ | |
var keyboardActionContext = new KeyboardActionContext(listArea); | |
listArea.OnKeyboardCallback += keyboardActionContext.KeyboardAction; | |
EditorApplication.update -= Function; | |
} | |
} | |
EditorApplication.update += Function; | |
} | |
} | |
class KeyboardActionContext | |
{ | |
private readonly ObjectListAreaWrap _listAreaWrap; | |
public KeyboardActionContext(ObjectListAreaWrap listAreaWrap) => _listAreaWrap = listAreaWrap; | |
private string _searchText; | |
private DateTime _lastKeyTime; | |
private void UpdateSearchText(Func<string, string> update) | |
{ | |
if (DateTime.Now - _lastKeyTime > TimeSpan.FromSeconds(1)) | |
_searchText = ""; | |
_lastKeyTime = DateTime.Now; | |
_searchText = update(_searchText); | |
// Debug.Log($"SearchText: {_searchText}"); | |
int instanceId = 0; | |
if (_searchText != "") | |
{ | |
var results = _listAreaWrap.LocalAssets.FilteredHierarchy.Results; | |
for (var i = 0; i < results.Length; i++) | |
{ | |
if (results[i].Name.StartsWith(_searchText, StringComparison.OrdinalIgnoreCase)) | |
{ | |
Debug.Log($"selecting {results[i].Name}"); | |
instanceId = results[i].InstanceID; | |
break; | |
} | |
} | |
if (instanceId != 0) | |
{ | |
_listAreaWrap.SetSelection(new [] { instanceId }, false); | |
var selectedIdx = _listAreaWrap.GetSelectedAssetIdx(); | |
_listAreaWrap.ScrollToPosition( | |
ObjectListAreaWrap.AdjustRectForFraming( | |
_listAreaWrap.LocalAssets.Grid.CalcRect(selectedIdx, 0.0f))); | |
} | |
} | |
} | |
public void KeyboardAction() | |
{ | |
if (Event.current.type != EventType.KeyDown) return; | |
if (Event.current.character != 0) | |
{ | |
Event.current.Use(); | |
UpdateSearchText(s => s + char.ToLower(Event.current.character)); | |
} | |
else | |
{ | |
switch (Event.current.keyCode) | |
{ | |
case KeyCode.UpArrow: | |
case KeyCode.DownArrow: | |
case KeyCode.LeftArrow: | |
case KeyCode.RightArrow: | |
case KeyCode.Return: | |
case KeyCode.KeypadEnter: | |
_searchText = ""; | |
break; | |
} | |
} | |
} | |
} | |
#region Wrappers | |
class ProjectBrowserWrap | |
{ | |
private static readonly Type Type = Type.GetType("UnityEditor.ProjectBrowser, UnityEditor")!; | |
private static readonly MethodInfo GetAllProjectBrowsersMethod = | |
Type.GetMethod("GetAllProjectBrowsers", BindingFlags.Public | BindingFlags.Static)!; | |
private static readonly FieldInfo ListAreaField = | |
Type.GetField("m_ListArea", BindingFlags.NonPublic | BindingFlags.Instance)!; | |
private EditorWindow _real; | |
public ProjectBrowserWrap(EditorWindow real) | |
{ | |
if (real.GetType() != Type) throw new ArgumentException("EditorWindow is not a ProjectBrowser", nameof(real)); | |
_real = real; | |
} | |
public static List<ProjectBrowserWrap> GetAllProjectBrowsers() => | |
((IEnumerable<EditorWindow>)GetAllProjectBrowsersMethod.Invoke(null, null)) | |
.Select(window => new ProjectBrowserWrap(window)) | |
.ToList(); | |
public ObjectListAreaWrap? ListArea => ListAreaField.GetValue(_real) is { } listArea | |
? new ObjectListAreaWrap(listArea) | |
: null; | |
} | |
class ObjectListAreaWrap | |
{ | |
private static readonly Type Type = Type.GetType("UnityEditor.ObjectListArea, UnityEditor")!; | |
private static readonly PropertyInfo KeyboardCallbackProperty = | |
Type.GetProperty("keyboardCallback", BindingFlags.Public | BindingFlags.Instance)!; | |
private static readonly MethodInfo GetSelectedAssetIdxMethod = | |
Type.GetMethod("GetSelectedAssetIdx", BindingFlags.NonPublic | BindingFlags.Instance)!; | |
private static readonly FieldInfo LocalAssetsField = | |
Type.GetField("m_LocalAssets", BindingFlags.NonPublic | BindingFlags.Instance)!; | |
public static readonly FieldInfo SelectionOffsetField = | |
Type.GetField("m_SelectionOffset", BindingFlags.NonPublic | BindingFlags.Instance)!; | |
public static readonly MethodInfo SetSelectionMethod = | |
Type.GetMethod("SetSelection", BindingFlags.NonPublic | BindingFlags.Instance)!; | |
public static readonly MethodInfo ScrollToPositionMethod = | |
Type.GetMethod("ScrollToPosition", BindingFlags.NonPublic | BindingFlags.Instance)!; | |
public static readonly MethodInfo AdjustRectForFramingMethod = | |
Type.GetMethod("AdjustRectForFraming", BindingFlags.NonPublic | BindingFlags.Static)!; | |
private object _real; | |
public ObjectListAreaWrap(object real) | |
{ | |
if (real.GetType() != Type) throw new ArgumentException("Object is not an ObjectListArea", nameof(real)); | |
_real = real; | |
} | |
public Action OnKeyboardCallback | |
{ | |
get => (Action)KeyboardCallbackProperty.GetValue(_real)!; | |
set => KeyboardCallbackProperty.SetValue(_real, value); | |
} | |
public int GetSelectedAssetIdx() => (int)GetSelectedAssetIdxMethod.Invoke(_real, null); | |
public LocalGroupWrap LocalAssets => new LocalGroupWrap(LocalAssetsField.GetValue(_real)!); | |
public int SelectionOffset | |
{ | |
get => (int)SelectionOffsetField.GetValue(_real); | |
set => SelectionOffsetField.SetValue(_real, value); | |
} | |
public void SetSelection(int[] selectedInstanceIDs, bool doubleClicked) | |
{ | |
SetSelectionMethod.Invoke(_real, new object[] {selectedInstanceIDs, doubleClicked}); | |
} | |
public void ScrollToPosition(Rect rect) | |
{ | |
ScrollToPositionMethod.Invoke(_real, new object[] {rect}); | |
} | |
public static Rect AdjustRectForFraming(Rect rect) => | |
(Rect)AdjustRectForFramingMethod.Invoke(null, new object[] {rect}); | |
public class LocalGroupWrap | |
{ | |
private static readonly Type Type = Type.GetType("UnityEditor.ObjectListArea+LocalGroup, UnityEditor")!; | |
private static readonly FieldInfo FilteredHierarchyField = | |
Type.GetField("m_FilteredHierarchy", BindingFlags.NonPublic | BindingFlags.Instance)!; | |
public static readonly FieldInfo GridField = | |
Type.GetField("m_Grid", BindingFlags.Public | BindingFlags.Instance)!; | |
private object _real; | |
public LocalGroupWrap(object real) | |
{ | |
if (real.GetType() != Type) throw new ArgumentException("Object is not a LocalGroup", nameof(real)); | |
_real = real; | |
} | |
public FilteredHierarchyWrap FilteredHierarchy => new FilteredHierarchyWrap(FilteredHierarchyField.GetValue(_real)!); | |
public VerticalGridWrap Grid => new VerticalGridWrap(GridField.GetValue(_real)!); | |
} | |
} | |
class FilteredHierarchyWrap | |
{ | |
private static readonly Type Type = Type.GetType("UnityEditor.FilteredHierarchy, UnityEditor")!; | |
private static readonly FieldInfo resultsField = | |
Type.GetField("m_Results", BindingFlags.NonPublic | BindingFlags.Instance)!; | |
private object _real; | |
public FilteredHierarchyWrap(object real) | |
{ | |
if (real.GetType() != Type) throw new ArgumentException("Object is not a FilteredHierarchy", nameof(real)); | |
_real = real; | |
} | |
public FilterResultWrap[] Results => | |
Array.ConvertAll((object[])resultsField.GetValue(_real)!, o => new FilterResultWrap(o)); | |
public class FilterResultWrap | |
{ | |
private static readonly Type Type = Type.GetType("UnityEditor.FilteredHierarchy+FilterResult, UnityEditor")!; | |
private static readonly FieldInfo InstanceIDField = | |
Type.GetField("instanceID", BindingFlags.Public | BindingFlags.Instance)!; | |
private static readonly FieldInfo NameField = | |
Type.GetField("name", BindingFlags.Public | BindingFlags.Instance)!; | |
private object _real; | |
public FilterResultWrap(object real) | |
{ | |
if (real.GetType() != Type) throw new ArgumentException("Object is not a FilterResult", nameof(real)); | |
_real = real; | |
} | |
public int InstanceID => (int)InstanceIDField.GetValue(_real); | |
public string Name => (string)NameField.GetValue(_real); | |
} | |
} | |
class VerticalGridWrap | |
{ | |
private static readonly Type Type = Type.GetType("UnityEditor.VerticalGrid, UnityEditor")!; | |
private static readonly MethodInfo CalcRectMethod = | |
Type.GetMethod("CalcRect", BindingFlags.Public | BindingFlags.Instance)!; | |
private object _real; | |
public VerticalGridWrap(object real) | |
{ | |
if (real.GetType() != Type) throw new ArgumentException("Object is not a VerticalGrid", nameof(real)); | |
_real = real; | |
} | |
public Rect CalcRect(int index, float scroll) | |
{ | |
return (Rect)CalcRectMethod.Invoke(_real, new object[] {index, scroll}); | |
} | |
} | |
#endregion | |
} | |
} | |
#endif |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment