This script related to feature request:
Double-click expand/collapse feature request:
Made for Unity 3D v4.1.2.
Features:
- “Folders” are sorted separately from “Files”, like in Visual Studio. Sorting works only for Ptoject Window “One Column Layout”.
- Double-click “Folders” expand/collapse, like in Visual Studio.
Note:
This script is unusual and using reflection, because Unity scripting doesn’t support Project Window extending in that way. So i was lucky to be able to find the way of doing it, this also means that in future versions of Unity this script may not work.
Also would be nice to implement and test:
- Sort files by extensions.
v1.0
ProjectWindowExtension.cs
/*
* Project Window Extension v1.0
*
* Copyright (c) 2013 newbprofi
*
* 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.
*/
using System;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Collections;
[InitializeOnLoad]
public static class ProjectWindowExtension
{
static Assembly unityEditorAssembly;
static Type objectBrowserType;
static Type treeViewType;
static Type treeViewDataType;
static Type defaultTreeViewDataSourceType;
static Type nodeType;
static MethodInfo isFolderMethod;
static MethodInfo findNodeByIDMethod;
static MethodInfo isExpandableMethod;
static MethodInfo isExpandedMethod;
static MethodInfo setExpandedWithChildrenMethod;
static MethodInfo setExpandedMethod;
static MethodInfo getInstanceIDFromGUIDMethod;
static FieldInfo objectBrowserField;
static FieldInfo assetTreeField;
static FieldInfo folderTreeField;
static FieldInfo treeDataField;
static FieldInfo visibleRowsField;
static FieldInfo nodeInstanceIDField;
static FieldInfo nodeDepthField;
static object objectBrowser;
static object assetTree;
static object folderTree;
static object treeData;
static int lastVisibleRowsHash;
static ProjectWindowExtension()
{
bool result = (unityEditorAssembly = Assembly.GetAssembly(typeof(Editor))) != null;
if(result)
{
result = (objectBrowserType = unityEditorAssembly.GetType("UnityEditor.ObjectBrowser")) != null;
result = (treeViewType = unityEditorAssembly.GetType("UnityEditor.TreeView")) != null;
result = (treeViewDataType = unityEditorAssembly.GetType("UnityEditor.ITreeViewDataSource")) != null;
result = (defaultTreeViewDataSourceType = unityEditorAssembly.GetType("UnityEditor.DefaultTreeViewDataSource")) != null;
result = (nodeType = unityEditorAssembly.GetType("UnityEditor.TreeView+Node")) != null;
if(result)
{
result = (isFolderMethod = objectBrowserType.GetMethod("IsFolder", BindingFlags.Static | BindingFlags.Public)) != null;
result = (findNodeByIDMethod = treeViewDataType.GetMethod("FindNodeByID", BindingFlags.Instance | BindingFlags.Public)) != null;
result = (isExpandableMethod = treeViewDataType.GetMethod("IsExpandable", BindingFlags.Instance | BindingFlags.Public)) != null;
result = (isExpandedMethod = treeViewDataType.GetMethod("IsExpanded", BindingFlags.Instance | BindingFlags.Public)) != null;
result = (setExpandedWithChildrenMethod = treeViewDataType.GetMethod("SetExpandedWithChildren", BindingFlags.Instance | BindingFlags.Public)) != null;
result = (setExpandedMethod = treeViewDataType.GetMethod("SetExpanded", BindingFlags.Instance | BindingFlags.Public)) != null;
result = (getInstanceIDFromGUIDMethod = typeof(AssetDatabase).GetMethod("GetInstanceIDFromGUID", BindingFlags.Static | BindingFlags.NonPublic)) != null;
result = (objectBrowserField = objectBrowserType.GetField("s_LastInteractedObjectBrowser", BindingFlags.Static | BindingFlags.Public)) != null;
result = (assetTreeField = objectBrowserType.GetField("m_AssetTree", BindingFlags.Instance | BindingFlags.NonPublic)) != null;
result = (folderTreeField = objectBrowserType.GetField("m_FolderTree", BindingFlags.Instance | BindingFlags.NonPublic)) != null;
result = (treeDataField = treeViewType.GetField("m_Data", BindingFlags.Instance | BindingFlags.Public)) != null;
result = (visibleRowsField = defaultTreeViewDataSourceType.GetField("m_VisibleRows", BindingFlags.Instance | BindingFlags.NonPublic)) != null;
result = (nodeInstanceIDField = nodeType.GetField("m_InstanceID", BindingFlags.Instance | BindingFlags.NonPublic)) != null;
result = (nodeDepthField = nodeType.GetField("m_Depth", BindingFlags.Instance | BindingFlags.NonPublic)) != null;
if(result)
{
EditorApplication.projectWindowItemOnGUI += ProjectWindowItem_OnGUI;
EditorApplication.projectWindowChanged += ProjectWindow_Changed;
}
}
}
}
static bool IsFolder(object node)
{
int instanceID = (int)nodeInstanceIDField.GetValue(node);
bool isFolder = (bool)isFolderMethod.Invoke(null, new object[] { instanceID });
return isFolder;
}
static int SortRecursiveByDepth(IList sortedList, IList list, int position, int depth, bool needFolders)
{
int pos = position;
int count = list.Count;
bool lastIsFolder = !needFolders;
while(pos < count sortedList.Count < count)
{
object node = list[pos];
int nodeDepth = (int)nodeDepthField.GetValue(list[pos]);
// sort folders or files only
if(needFolders)
{
if(nodeDepth == depth)
{
lastIsFolder = IsFolder(node);
if(lastIsFolder)
sortedList.Add(node);
pos++;
}
else if(nodeDepth > depth)
{
if(lastIsFolder)
pos = SortRecursiveByDepth(sortedList, list, pos, nodeDepth, true);
else
pos++;
}
else
break;
}
else
{
if(nodeDepth == depth)
{
lastIsFolder = IsFolder(node);
if(!lastIsFolder)
sortedList.Add(node);
pos++;
}
else if(nodeDepth > depth)
{
if(!lastIsFolder)
pos = SortRecursiveByDepth(sortedList, list, pos, nodeDepth, false);
else
pos++;
}
else
break;
}
}
// sort files
if(needFolders sortedList.Count < count)
pos = SortRecursiveByDepth(sortedList, list, position, depth, false);
return pos;
}
static void SortAssetTree()
{
if(treeData != null assetTree != null)
{
IList visibleRows = (IList)visibleRowsField.GetValue(treeData);
if(visibleRows != null)
{
// check if was reallocated
if(lastVisibleRowsHash != visibleRows.GetHashCode())
{
lastVisibleRowsHash = visibleRows.GetHashCode();
// sort
ArrayList sortedList = new ArrayList(visibleRows.Count);
SortRecursiveByDepth(sortedList, visibleRows, 0, 0, true);
// rewrite with sorted list
for(int i = 0; i < visibleRows.Count; i++)
visibleRows[i] = sortedList[i];
}
}
}
}
static void InitObjects()
{
// this objects changed when project changed and not only
assetTree = null;
folderTree = null;
treeData = null;
objectBrowser = objectBrowserField.GetValue(null);
if(objectBrowser != null)
{
assetTree = assetTreeField.GetValue(objectBrowser);
folderTree = folderTreeField.GetValue(objectBrowser);
if(assetTree != null)
treeData = treeDataField.GetValue(assetTree);
else if(folderTree != null)
treeData = treeDataField.GetValue(folderTree);
}
}
static void ProjectWindow_Changed()
{
InitObjects();
}
static void ProjectWindowItem_OnGUI(string guid, Rect drawingRect)
{
InitObjects();
SortAssetTree();
if(Event.current.type == EventType.MouseDown
Event.current.clickCount == 2
drawingRect.Contains(Event.current.mousePosition))
{
if(treeData != null)
{
int instanceID = (int)getInstanceIDFromGUIDMethod.Invoke(null, new object[] { guid });
object node = findNodeByIDMethod.Invoke(treeData, new object[] { instanceID });
if(node != null IsFolder(node))
{
bool isExpandable = (bool)isExpandableMethod.Invoke(treeData, new object[] { node });
bool isExpanded = (bool)isExpandedMethod.Invoke(treeData, new object[] { node });
if(isExpandable)
{
if(Event.current.alt)
{
if(isExpanded)
setExpandedWithChildrenMethod.Invoke(treeData, new object[] { node, false });
else
setExpandedWithChildrenMethod.Invoke(treeData, new object[] { node, true });
}
else
{
if(isExpanded)
setExpandedMethod.Invoke(treeData, new object[] { node, false });
else
setExpandedMethod.Invoke(treeData, new object[] { node, true });
}
}
//Event.current.Use();
}
}
}
}
}
EDIT: Actually, i found that Unity’s default sorting is not so bad. I found that when everything is sorted alphabetically - its easier for brain to find things. You just have to make good hierarchy of folders to not allow “folders” and “files” mix very often. So when you adding “folder” sorting you also adding a little brain pain, because you have to always remember, that “folders” on top and “files” bottom. But it is still very useful and i think there must be an option to switch sorting.
