2025-08-18 09:22:24 +08:00
#if UNITY_EDITOR
using System.Collections ;
using System.Collections.Generic ;
using System.IO ;
using System.Linq ;
using System.Reflection ;
using UnityEngine ;
using UnityEditor ;
using UnityEditor.ShortcutManagement ;
using UnityEngine.UIElements ;
using UnityEngine.SceneManagement ;
using UnityEditor.SceneManagement ;
using UnityEditor.IMGUI.Controls ;
using Type = System . Type ;
using static VFolders . Libs . VUtils ;
using static VFolders . Libs . VGUI ;
// using static VTools.VDebug;
using static VFolders . VFoldersData ;
using static VFolders . VFoldersCache ;
namespace VFolders
{
public static class VFolders
{
static void WrappedGUI ( EditorWindow window )
{
var navbarHeight = 26 ;
var isOneColumn = window . GetMemberValue < int > ( "m_ViewMode" ) = = 0 ;
void navbarGui ( )
{
if ( ! navbars_byWindow . ContainsKey ( window ) )
navbars_byWindow [ window ] = new VFoldersNavbar ( window ) ;
var navbarRect = window . position . SetPos ( 0 , 0 ) . SetHeight ( navbarHeight ) ;
navbars_byWindow [ window ] . OnGUI ( navbarRect ) ;
}
void hideDefaultTopBar ( )
{
if ( curEvent . isLayout | | typeof ( GUILayoutUtility ) . GetMemberValue ( "current" ) ? . GetMemberValue ( "topLevel" ) ? . GetMemberValue < IList > ( "entries" ) . Count ! = 1 ) // prevents exception on focus by vTabs shift-scroll // doesnt always work. tbh not sure why it works at all
Space ( - 123 ) ;
}
void defaultGuiWithOffset ( )
{
var defaultTopBarHeight = 20 ;
var topOffset = navbarHeight - defaultTopBarHeight ;
var m_Pos_original = window . GetFieldValue < Rect > ( "m_Pos" ) ;
GUI . BeginGroup ( m_Pos_original . SetPos ( 0 , 0 ) . AddHeightFromBottom ( - topOffset ) ) ;
window . SetFieldValue ( "m_Pos" , m_Pos_original . AddHeightFromBottom ( - topOffset ) ) ;
try { window . InvokeMethod ( "OnGUI" ) ; }
catch ( System . Exception exception )
{
if ( exception . InnerException is ExitGUIException )
throw exception . InnerException ;
else
throw exception ;
// GUIUtility.ExitGUI() works by throwing ExitGUIException, which just exits imgui loop and doesn't appear in console
// but if ExitGUI is called from a reflected method (OnGUI in this case), the exception becomes TargetInvokationException
// which gets logged to console (only if debugger is attached, for some reason)
// so here in such cases we rethrow the original ExitGUIException
}
window . SetFieldValue ( "m_Pos" , m_Pos_original ) ;
GUI . EndGroup ( ) ;
}
void treeViewShadow ( )
{
if ( ! curEvent . isRepaint ) return ;
var shadowLength = 30 ;
var shadowPos = 21 ;
var shadowGreyscale = isDarkTheme ? . 1f : . 28f ;
var shadowAlpha = . 35f ;
var minScrollPos = 10 ;
var maxScrollPos = 20 ;
2025-09-06 19:28:38 +08:00
var scrollPos = window . GetMemberValue ( isOneColumn ? "m_AssetTree" : "m_FolderTree" ) . GetMemberValue < UnityEditor . IMGUI . Controls . TreeViewState < int > > ( "state" ) . scrollPos . y ;
2025-08-18 09:22:24 +08:00
var opacity = ( ( scrollPos - minScrollPos ) / ( maxScrollPos - minScrollPos ) ) . Clamp01 ( ) ;
var rectWidth = isOneColumn ? window . position . width : window . GetMemberValue < Rect > ( "m_TreeViewRect" ) . width ; // - 12;
var rect = window . position . SetPos ( 0 , 0 ) . MoveY ( shadowPos ) . SetHeight ( shadowLength ) . SetWidth ( rectWidth ) ;
var clipAtY = navbarHeight + 1 ;
GUI . BeginClip ( window . position . SetPos ( 0 , clipAtY ) ) ;
rect . MoveY ( - clipAtY ) . DrawCurtainDown ( Greyscale ( shadowGreyscale , shadowAlpha * opacity ) ) ;
GUI . EndClip ( ) ;
if ( isOneColumn ) return ;
var dividerRect = window . GetMemberValue < Rect > ( "m_TreeViewRect" ) . SetWidthFromRight ( 1 ) . MoveX ( 1 ) ;
var dividerColor = Greyscale ( . 16f ) ;
dividerRect . Draw ( dividerColor ) ;
}
void listAreaShadow ( )
{
if ( isOneColumn ) return ;
if ( ! curEvent . isRepaint ) return ;
var shadowLength = 30 ;
var shadowPos = navbarHeight + 17 ;
var shadowGreyscale = isDarkTheme ? . 1f : . 28f ;
var shadowAlpha = . 35f ;
var minScrollPos = 10 ;
var maxScrollPos = 20 ;
var scrollPos = window . GetMemberValue ( "m_ListAreaState" ) . GetMemberValue < Vector2 > ( "m_ScrollPosition" ) . y ;
var opacity = ( ( scrollPos - minScrollPos ) / ( maxScrollPos - minScrollPos ) ) . Clamp01 ( ) ;
var rectX = window . GetMemberValue < Rect > ( "m_TreeViewRect" ) . width + 1 ;
var rectWidth = window . position . width - rectX ; // - 12;
var rect = window . position . SetPos ( rectX , 0 ) . MoveY ( shadowPos ) . SetHeight ( shadowLength ) . SetWidth ( rectWidth ) ;
var clipAtY = navbarHeight + 22 ;
GUI . BeginClip ( window . position . SetPos ( 0 , clipAtY ) ) ;
rect . MoveY ( - clipAtY ) . DrawCurtainDown ( Greyscale ( shadowGreyscale , shadowAlpha * opacity ) ) ;
GUI . EndClip ( ) ;
if ( isDarkTheme )
window . position . SetPos ( rectX , clipAtY - 1 ) . SetSize ( 12321 , 1 ) . Draw ( Greyscale ( . 175f ) ) ; // line under breadcrumbs
}
var doNavbarFirst = GUI . GetNameOfFocusedControl ( ) = = "navbar search field" | | curEvent . keyCode = = KeyCode . Escape ;
if ( doNavbarFirst )
navbarGui ( ) ;
hideDefaultTopBar ( ) ;
defaultGuiWithOffset ( ) ;
treeViewShadow ( ) ;
listAreaShadow ( ) ;
if ( ! doNavbarFirst )
navbarGui ( ) ;
}
static Dictionary < EditorWindow , VFoldersNavbar > navbars_byWindow = new ( ) ;
static void UpdateGUIWrapping ( EditorWindow window )
{
if ( ! window . hasFocus ) return ;
var isLocked = window . GetMemberValue < bool > ( "isLocked" ) ;
var isVTabsActive = t_VTabs ! = null & & ! EditorPrefsCached . GetBool ( "vTabs-pluginDisabled" , false ) ;
var curOnGUIMethod = window . GetMemberValue ( "m_Parent" ) . GetMemberValue < System . Delegate > ( "m_OnGUI" ) . Method ;
var isWrapped = curOnGUIMethod = = mi_WrappedBrowserOnGUI ;
var shouldBeWrapped = VFoldersMenu . navigationBarEnabled & & ! ( isVTabsActive & & isLocked ) & & curOnGUIMethod ! = mi_VFavorites_WrappedOnGUI ;
void wrap ( )
{
var hostView = window . GetMemberValue ( "m_Parent" ) ;
var newDelegate = typeof ( VFolders ) . GetMethod ( nameof ( WrappedGUI ) , maxBindingFlags ) . CreateDelegate ( t_EditorWindowDelegate , window ) ;
hostView . SetMemberValue ( "m_OnGUI" , newDelegate ) ;
window . Repaint ( ) ;
}
void unwrap ( )
{
var hostView = window . GetMemberValue ( "m_Parent" ) ;
var originalDelegate = hostView . InvokeMethod ( "CreateDelegate" , "OnGUI" ) ;
hostView . SetMemberValue ( "m_OnGUI" , originalDelegate ) ;
window . Repaint ( ) ;
}
if ( shouldBeWrapped & & ! isWrapped )
wrap ( ) ;
if ( ! shouldBeWrapped & & isWrapped )
unwrap ( ) ;
}
static void UpdateGUIWrappingForAllBrowsers ( ) = > allBrowsers . ForEach ( r = > UpdateGUIWrapping ( r ) ) ;
static void OnDomainReloaded ( ) = > toCallInGUI + = UpdateGUIWrappingForAllBrowsers ;
static void OnWindowUnmaximized ( ) = > UpdateGUIWrappingForAllBrowsers ( ) ;
static void OnBrowserFocused ( ) = > UpdateGUIWrapping ( EditorWindow . focusedWindow ) ;
static void OnDelayCall ( ) = > UpdateGUIWrappingForAllBrowsers ( ) ;
static void CheckIfFocusedWindowChanged ( )
{
if ( prevFocusedWindow ! = EditorWindow . focusedWindow )
if ( EditorWindow . focusedWindow ? . GetType ( ) = = t_ProjectBrowser )
OnBrowserFocused ( ) ;
prevFocusedWindow = EditorWindow . focusedWindow ;
}
static EditorWindow prevFocusedWindow ;
static void CheckIfWindowWasUnmaximized ( )
{
var isMaximized = EditorWindow . focusedWindow ? . maximized = = true ;
if ( ! isMaximized & & wasMaximized )
OnWindowUnmaximized ( ) ;
wasMaximized = isMaximized ;
}
static bool wasMaximized ;
static void OnSomeGUI ( )
{
toCallInGUI ? . Invoke ( ) ;
toCallInGUI = null ;
CheckIfFocusedWindowChanged ( ) ;
}
static void ProjectWindowItemOnGUI ( string _ , Rect __ ) = > OnSomeGUI ( ) ;
static void HierarchyWindowItemOnGUI ( int _ , Rect __ ) = > OnSomeGUI ( ) ;
static System . Action toCallInGUI ;
static void DelayCallLoop ( )
{
OnDelayCall ( ) ;
EditorApplication . delayCall - = DelayCallLoop ;
EditorApplication . delayCall + = DelayCallLoop ;
}
static void ItemGUI ( Rect itemRect , string guid , int instanceId )
{
EditorWindow window ;
void findWindow ( )
{
if ( allBrowsers . Count ( ) = = 1 ) { window = allBrowsers . First ( ) ; return ; }
var pointInsideWindow = EditorGUIUtility . GUIToScreenPoint ( itemRect . center ) ;
window = allBrowsers . FirstOrDefault ( r = > r . position . AddHeight ( 30 ) . Contains ( pointInsideWindow ) & & r . hasFocus ) ;
}
void updateWindow ( )
{
if ( ! window ) return ; // happens on half-visible rows during expand animation
if ( curEvent . isLayout & & ! lastEventWasLayout )
UpdateWindow_Layout ( window ) ;
if ( curEvent . isRepaint & & ! lastEventWasRepaint )
UpdateWindow_Repaint ( window ) ;
lastEventWasLayout = curEvent . isLayout ;
lastEventWasRepaint = curEvent . isRepaint ;
}
void catchScrollInputForController ( )
{
if ( ! window ) return ;
if ( ! controllers_byWindow . ContainsKey ( window ) ) return ;
if ( curEvent . isScroll )
controllers_byWindow [ window ] . animatingScroll = false ;
}
void callGUI ( )
{
if ( ! window ) return ;
if ( ! guis_byWindow . ContainsKey ( window ) ) return ;
var gui = guis_byWindow [ window ] ;
if ( itemRect . height = = 16 )
gui . RowGUI ( itemRect , guid , instanceId ) ;
else
gui . CellGUI ( itemRect , guid , instanceId ) ;
}
findWindow ( ) ;
updateWindow ( ) ;
catchScrollInputForController ( ) ;
callGUI ( ) ;
}
static void ItemGUI_2021_3_and_older ( string guid , Rect itemRect )
{
var instanceId = typeof ( AssetDatabase ) . InvokeMethod < int > ( "GetMainAssetOrInProgressProxyInstanceID" , guid . ToPath ( ) ) ;
ItemGUI ( itemRect , guid , instanceId ) ;
}
static void ItemGUI_2022_1_and_newer ( int instanceId , Rect itemRect )
{
var guid = AssetDatabase . GetAssetPath ( instanceId ) . ToGuid ( ) ;
ItemGUI ( itemRect , guid , instanceId ) ;
}
static bool lastEventWasLayout ;
static bool lastEventWasRepaint ;
static void UpdateWindow_Layout ( EditorWindow window )
{
if ( ! guis_byWindow . TryGetValue ( window , out var gui ) )
gui = guis_byWindow [ window ] = new ( window ) ;
if ( ! controllers_byWindow . TryGetValue ( window , out var controller ) )
controller = controllers_byWindow [ window ] = new ( window ) ;
if ( ! histories_byWindow . TryGetValue ( window , out var history ) )
history = histories_byWindow [ window ] = new ( window ) ;
gui . UpdateState_Layout ( ) ;
gui . UpdateFoldersFirst ( ) ;
controller . UpdateState ( ) ;
controller . UpdateExpandQueue ( ) ;
controller . UpdateScrollAnimation ( ) ;
controller . UpdateHighlightAnimation ( ) ;
history . UpdateState ( ) ;
history . CheckTreeStateChange ( ) ;
history . CheckFolderPathChange ( ) ;
}
static void UpdateWindow_Repaint ( EditorWindow window )
{
if ( guis_byWindow . ContainsKey ( window ) )
guis_byWindow [ window ] . UpdateState_Repaint ( ) ;
}
public static Dictionary < EditorWindow , VFoldersGUI > guis_byWindow = new ( ) ;
public static Dictionary < EditorWindow , VFoldersController > controllers_byWindow = new ( ) ;
public static Dictionary < EditorWindow , VFoldersHistory > histories_byWindow = > VFoldersHistorySingleton . instance . histories_byWindow ;
public static Texture2D GetSmallFolderIcon ( FolderInfo folderInfo , bool removeColor = false )
{
var hasColor = folderInfo . hasColor & & ! removeColor ;
var hasIcon = folderInfo . hasIcon ;
var color = hasColor ? folderInfo . color : default ;
var iconNameOrPath = hasIcon ? folderInfo . iconNameOrPath : "" ;
var isEmpty = folderInfo . folderState . isEmpty ;
var key = new object [ ] { iconNameOrPath , color , isEmpty , isDarkTheme } . Aggregate ( 0 , ( hash , r ) = > ( hash * 2 ) ^ r . GetHashCode ( ) ) ;
Texture2D icon = null ;
void getCached ( )
{
if ( ! cache . HasIcon ( key ) ) return ;
icon = cache . GetIcon ( key ) ;
}
void generateAndCache ( )
{
if ( icon ! = null ) return ;
if ( Event . current ! = null ) return ; // interactions with gpu in OnGUI may interfere with gui rendering
var iconSizeX = hasIcon ? 36 : 32 ;
var iconSizeY = 32 ;
var assetIconSize = 20 ; // 20 21
var assetIconOffsetX = 16 ;
var assetIconOffsetY = - 2 ; // -2 -3
var folderIconSize = iconSizeY ;
var folderIconOffsetY_ifHasAssetIcon = 1 ;
Color [ ] iconPixels ;
Texture2D folderIcon ;
Color [ ] folderIconPixels ;
void createIcon ( )
{
icon = new Texture2D ( iconSizeX , iconSizeY , TextureFormat . RGBA32 , 1 , false ) ;
icon . hideFlags = HideFlags . DontSave ;
icon . SetPropertyValue ( "pixelsPerPoint" , 2 ) ;
iconPixels = new Color [ iconSizeX * iconSizeY ] ;
}
void createFolderIcon ( )
{
var folderIconName = hasColor ? ( isEmpty ? "FolderEmpty On Icon" : "Folder On Icon" ) :
( isEmpty ? "FolderEmpty Icon" : "Folder Icon" ) ;
folderIcon = EditorGUIUtility . FindTexture ( folderIconName ) ;
if ( folderIcon . width ! = folderIconSize )
folderIcon = folderIcon . CreateResizedCopy ( folderIconSize , folderIconSize ) ;
else
folderIcon = folderIcon . CreateCopy ( ) ;
folderIconPixels = folderIcon . GetPixels ( 0 ) ;
}
void copyFolderIcon ( )
{
if ( ! hasIcon ) { iconPixels = folderIconPixels ; return ; }
for ( int x = 0 ; x < folderIcon . width ; x + + )
for ( int y = 0 ; y < folderIcon . height - folderIconOffsetY_ifHasAssetIcon ; y + + )
iconPixels [ x + ( y + folderIconOffsetY_ifHasAssetIcon ) * icon . width ] = folderIconPixels [ x + y * folderIcon . width ] ;
}
void applyColor ( )
{
if ( ! hasColor ) return ;
for ( int i = 0 ; i < iconPixels . Length ; i + + )
iconPixels [ i ] * = ( color * 1.06f ) . SetAlpha ( 1 ) ;
}
void insertAssetIcon ( )
{
if ( ! hasIcon ) return ;
var assetIconOriginal = EditorIcons . GetIcon ( iconNameOrPath ) ;
if ( ! assetIconOriginal ) return ;
var prevFilter = assetIconOriginal . filterMode ;
assetIconOriginal . filterMode = FilterMode . Bilinear ;
var assetIconPixels_bilinear = assetIconOriginal . CreateResizedCopy ( assetIconSize , assetIconSize ) . GetPixels ( ) ;
assetIconOriginal . filterMode = FilterMode . Point ;
var assetIconPixels_point = assetIconOriginal . CreateResizedCopy ( assetIconSize , assetIconSize ) . GetPixels ( ) ;
assetIconOriginal . filterMode = prevFilter ;
for ( int x = 0 ; x < iconSizeX ; x + + )
for ( int y = 0 ; y < iconSizeY ; y + + )
{
var xAssetIcon = x - assetIconOffsetX ;
var yAssetIcon = y - assetIconOffsetY - folderIconOffsetY_ifHasAssetIcon ;
if ( ! xAssetIcon . IsInRange ( 0 , assetIconSize - 1 ) ) continue ;
if ( ! yAssetIcon . IsInRange ( 0 , assetIconSize - 1 ) ) continue ;
var innerRadius = ( iconNameOrPath = = "AudioClip Icon" ? . 2f : . 4f ) ;
var isInnerPixel = ( new Vector2 ( xAssetIcon , yAssetIcon ) / ( assetIconSize - 1 ) - Vector2 . one * . 5f ) . magnitude < innerRadius ;
var isOutlinePixel = false ;
var outlineRadius = isInnerPixel ? 2 : 1 ;
for ( int xx = xAssetIcon - outlineRadius ; xx < = xAssetIcon + outlineRadius ; xx + + )
if ( ! isOutlinePixel )
for ( int yy = yAssetIcon - outlineRadius ; yy < = yAssetIcon + outlineRadius ; yy + + )
if ( ! isOutlinePixel )
if ( xx . IsInRange ( 0 , assetIconSize - 1 ) & & yy . IsInRange ( 0 , assetIconSize - 1 ) )
if ( assetIconPixels_bilinear [ xx + yy * assetIconSize ] . a > . 2f )
isOutlinePixel = true ;
var pxBilinear = assetIconPixels_bilinear [ xAssetIcon + yAssetIcon * assetIconSize ] ;
var pxPoint = assetIconPixels_point [ xAssetIcon + yAssetIcon * assetIconSize ] ;
var pxCombined = new Color ( pxPoint . r , pxPoint . g , pxPoint . b , pxBilinear . a ) ;
if ( pxCombined . a = = 0 & & ! isOutlinePixel ) continue ;
iconPixels [ x + y * iconSizeX ] = pxCombined ;
}
}
createIcon ( ) ;
createFolderIcon ( ) ;
copyFolderIcon ( ) ;
applyColor ( ) ;
insertAssetIcon ( ) ;
icon . SetPixels ( iconPixels ) ;
icon . Apply ( ) ;
cache . AddIcon ( key , icon ) ;
}
void queueGeneration ( )
{
if ( icon ! = null ) return ;
toGenerateInUpdate . Add ( generateAndCache ) ;
}
getCached ( ) ;
generateAndCache ( ) ;
queueGeneration ( ) ;
return icon ? ? EditorGUIUtility . FindTexture ( isEmpty ? "FolderEmpty Icon" : "Project@2x" ) ;
}
static void GenerateIconsInUpdate ( )
{
foreach ( var r in toGenerateInUpdate )
r . Invoke ( ) ;
toGenerateInUpdate . Clear ( ) ;
}
static List < System . Action > toGenerateInUpdate = new ( ) ;
public static void DrawBigFolderIcon ( Rect rect , FolderInfo folderInfo )
{
Rect folderIconRect ;
Rect assetIconRect ;
void calcRects ( )
{
folderIconRect = rect . SetHeight ( rect . width ) ;
#if ! UNITY_2022_3_OR_NEWER
if ( Application . platform = = RuntimePlatform . OSXEditor )
if ( folderIconRect . width > 64 )
folderIconRect = folderIconRect . SetSizeFromMid ( 64 , 64 ) ;
#endif
var assetIconOffsetMin = new Vector2 ( 4.5f , 3.5f ) ;
var assetIconSizeMin = 10 ;
var assetIconOffsetMax = new Vector2 ( 19 , 15 ) ;
var assetIconSizeMax = 24.5f ; // 25
var t = ( ( folderIconRect . width - 16 ) / ( 64 - 16 ) ) ;
#if ! UNITY_2022_3_OR_NEWER
if ( Application . platform = = RuntimePlatform . OSXEditor )
t = t . Clamp01 ( ) ;
#endif
var assetIconOffset = MathUtil . Lerp ( assetIconOffsetMin , assetIconOffsetMax , t ) ;
var assetIconSize = MathUtil . Lerp ( assetIconSizeMin , assetIconSizeMax , t ) ;
assetIconRect = folderIconRect . Move ( assetIconOffset ) . SetSizeFromMid ( assetIconSize , assetIconSize ) . AlignToPixelGrid ( ) ;
}
void color ( )
{
if ( ! folderInfo . hasColor ) return ;
SetGUIColor ( folderInfo . color . SetAlpha ( 1 ) ) ;
GUI . DrawTexture ( folderIconRect , EditorGUIUtility . FindTexture ( folderInfo . folderState . isEmpty ? "FolderEmpty On Icon" : "Folder On Icon" ) ) ;
ResetGUIColor ( ) ;
}
void assetIcon ( )
{
if ( ! folderInfo . hasIcon ) return ;
var texture = EditorIcons . GetIcon ( folderInfo . iconNameOrPath ) ;
if ( ! texture ) return ;
if ( texture . width < texture . height ) assetIconRect = assetIconRect . SetWidthFromMid ( assetIconRect . height * texture . width / texture . height ) ;
if ( texture . height < texture . width ) assetIconRect = assetIconRect . SetHeightFromMid ( assetIconRect . width * texture . height / texture . width ) ;
void material ( )
{
if ( ! outlineMaterial )
outlineMaterial = new Material ( Shader . Find ( "Hidden/Internal-GUITextureClipText" ) ) ;
outlineMaterial . color = isDarkTheme ? Greyscale ( . 2f , . 6f ) : Greyscale ( . 75f ) ;
// .color needs to be updated continiously because it gets reset on some editor events
// only happens when internal shader is used
}
void shadow ( )
{
var contrast = isDarkTheme ? . 6f : . 2f ; // was .65 then .6
assetIconRect . SetSizeFromMid ( assetIconRect . width * . 8f ) . DrawBlurred ( Greyscale ( . 2f , contrast ) , assetIconRect . width * . 55f ) ;
}
void outline ( )
{
var outlineRect = assetIconRect . Resize ( rect . height > = 70 & & EditorGUIUtility . pixelsPerPoint > = 2 ? - 1f / EditorGUIUtility . pixelsPerPoint : 0 ) . AlignToPixelGrid ( ) ;
EditorGUI . DrawPreviewTexture ( outlineRect . Move ( - 1 , - 1 ) , texture , outlineMaterial ) ;
EditorGUI . DrawPreviewTexture ( outlineRect . Move ( - 1 , 1 ) , texture , outlineMaterial ) ;
EditorGUI . DrawPreviewTexture ( outlineRect . Move ( 1 , 1 ) , texture , outlineMaterial ) ;
EditorGUI . DrawPreviewTexture ( outlineRect . Move ( 1 , - 1 ) , texture , outlineMaterial ) ;
}
void background ( )
{
for ( int i = 0 ; i < assetIconRect . size . x ; i + + )
EditorGUI . DrawPreviewTexture ( assetIconRect . Resize ( i * . 5f + 1 ) , texture , outlineMaterial ) ;
}
void icon ( )
{
GUI . DrawTexture ( assetIconRect , texture ) ;
}
material ( ) ;
shadow ( ) ;
outline ( ) ;
background ( ) ;
icon ( ) ;
}
calcRects ( ) ;
color ( ) ;
assetIcon ( ) ;
}
static Material outlineMaterial ;
public static Texture2D GetSmallFolderIcon_forVTabs ( string folderGuid )
{
var folderInfo = GetFolderInfo ( folderGuid ) ;
if ( folderInfo . hasColor | | folderInfo . hasIcon )
return GetSmallFolderIcon ( folderInfo ) ;
return null ;
}
public static void DrawBigFolderIcon_forVFavorites ( Rect rect , string folderGuid )
{
DrawBigFolderIcon ( rect , GetFolderInfo ( folderGuid ) ) ;
}
public static void SetIcon ( string folderPath , string iconName , bool recursive = false )
{
var folderData = GetFolderData ( folderPath . ToGuid ( ) , createDataIfDoesntExist : true ) ;
folderData . iconNameOrGuid = iconName ? ? "" ;
folderData . isIconRecursive = recursive ;
folderInfoCache . Clear ( ) ;
EditorApplication . RepaintProjectWindow ( ) ;
}
public static void SetColor ( string folderPath , int colorIndex , bool recursive = false )
{
var folderData = GetFolderData ( folderPath . ToGuid ( ) , createDataIfDoesntExist : true ) ;
folderData . colorIndex = colorIndex ;
folderData . isColorRecursive = recursive ;
folderInfoCache . Clear ( ) ;
EditorApplication . RepaintProjectWindow ( ) ;
}
static void Shortcuts ( ) // globalEventHandler
{
if ( ! curEvent . isKeyDown ) return ;
if ( curEvent . keyCode = = KeyCode . None ) return ;
if ( EditorWindow . mouseOverWindow is not EditorWindow hoveredWindow ) return ;
if ( hoveredWindow . GetType ( ) ! = t_ProjectBrowser ) return ;
void toggleExpanded ( )
{
if ( ! curEvent . isKeyDown ) return ;
if ( curEvent . keyCode ! = KeyCode . E ) return ;
if ( curEvent . holdingAnyModifierKey ) return ;
if ( ! VFoldersMenu . toggleExpandedEnabled ) return ;
if ( lastHoveredTreeItem = = null ) return ;
if ( ! lastHoveredRowRect_screenSpace . Contains ( lastKnownMousePosition_screenSpace ) ) return ;
curEvent . Use ( ) ;
if ( lastHoveredTreeItem . children = = null ) return ;
if ( lastHoveredTreeItem . children . Count = = 0 ) return ;
controllers_byWindow [ hoveredWindow ] . ToggleExpanded ( lastHoveredTreeItem ) ;
}
void collapseEverything ( )
{
if ( ! curEvent . isKeyDown ) return ;
if ( curEvent . keyCode ! = KeyCode . E ) return ;
if ( curEvent . modifiers ! = ( EventModifiers . Shift | EventModifiers . Command ) & & curEvent . modifiers ! = ( EventModifiers . Shift | EventModifiers . Control ) ) return ;
if ( ! VFoldersMenu . collapseEverythingEnabled ) return ;
curEvent . Use ( ) ;
controllers_byWindow [ hoveredWindow ] . CollapseAll ( ) ;
}
void collapseEverythingElse ( )
{
if ( ! curEvent . isKeyDown ) return ;
if ( curEvent . keyCode ! = KeyCode . E ) return ;
if ( curEvent . modifiers ! = EventModifiers . Shift ) return ;
if ( ! VFoldersMenu . collapseEverythingElseEnabled ) return ;
if ( lastHoveredTreeItem = = null ) return ;
if ( ! lastHoveredRowRect_screenSpace . Contains ( lastKnownMousePosition_screenSpace ) ) return ;
curEvent . Use ( ) ;
if ( lastHoveredTreeItem . children = = null ) return ;
if ( lastHoveredTreeItem . children . Count = = 0 ) return ;
controllers_byWindow [ hoveredWindow ] . Isolate ( lastHoveredTreeItem ) ;
}
toggleExpanded ( ) ;
collapseEverything ( ) ;
collapseEverythingElse ( ) ;
}
2025-09-06 19:28:38 +08:00
public static TreeViewItem < int > lastHoveredTreeItem ;
2025-08-18 09:22:24 +08:00
public static Rect lastHoveredRowRect_screenSpace ;
public static Vector2 lastKnownMousePosition_screenSpace ;
public static FolderInfo GetFolderInfo ( string guid )
{
if ( folderInfoCache . TryGetValue ( guid , out var cachedFolderInfo ) ) return cachedFolderInfo ;
var folderInfo = new FolderInfo ( ) ;
var folderData = folderInfo . folderData = GetFolderData ( guid , createDataIfDoesntExist : false ) ;
var folderState = folderInfo . folderState = GetFolderState ( guid ) ;
var recursiveIconNameOrGuid = "" ;
var recursiveColorIndex = 0 ;
var ruledIconNameOrGuid = "" ;
var ruledColorIndex = 0 ;
void checkRules ( )
{
if ( rules = = null )
rules = TypeCache . GetMethodsWithAttribute < RuleAttribute > ( )
. Where ( r = > r . IsStatic
& & r . GetParameters ( ) . Count ( ) = = 1
& & r . GetParameters ( ) . First ( ) . ParameterType = = typeof ( Folder ) ) . ToList ( ) ;
if ( ! rules . Any ( ) ) return ;
var folder = new Folder ( guid ) ;
foreach ( var rule in rules )
rule . Invoke ( null , new [ ] { folder } ) ;
ruledIconNameOrGuid = folder . icon ;
ruledColorIndex = folder . color ;
}
void checkRecursion ( string path , int depth )
{
if ( ! path . HasParentPath ( ) ) return ;
var parentFolderData = GetFolderData ( path . GetParentPath ( ) . ToGuid ( ) , createDataIfDoesntExist : false ) ;
if ( parentFolderData ! = null )
{
if ( parentFolderData . isIconRecursive & & parentFolderData . iconNameOrGuid ! = "" )
if ( recursiveIconNameOrGuid = = "" )
recursiveIconNameOrGuid = parentFolderData . iconNameOrGuid ;
if ( parentFolderData . isColorRecursive & & parentFolderData . colorIndex ! = 0 )
if ( recursiveColorIndex = = 0 )
recursiveColorIndex = parentFolderData . colorIndex ;
if ( parentFolderData . isColorRecursive & & parentFolderData . colorIndex ! = 0 )
folderInfo . maxColorRecursionDepth = depth + 1 ;
}
checkRecursion ( path . GetParentPath ( ) , depth + 1 ) ;
}
void setIcon ( )
{
var iconNameOrGuid = "" ;
if ( folderData ! = null & & folderData . iconNameOrGuid ! = "" )
iconNameOrGuid = folderData . iconNameOrGuid ;
else if ( recursiveIconNameOrGuid ! = "" )
iconNameOrGuid = recursiveIconNameOrGuid ;
else if ( ruledIconNameOrGuid ! = "" )
iconNameOrGuid = ruledIconNameOrGuid ;
else if ( VFoldersMenu . autoIconsEnabled & & folderState . autoIconName ! = "" & & folderData ? . isIconRecursive ! = true )
iconNameOrGuid = folderState . autoIconName ;
if ( iconNameOrGuid = = "" | | iconNameOrGuid = = "none" ) { folderInfo . hasIcon = false ; return ; }
folderInfo . hasIcon = true ;
folderInfo . hasIconByRecursion = recursiveIconNameOrGuid ! = "" ;
folderInfo . iconNameOrPath = iconNameOrGuid . Length = = 32 ? iconNameOrGuid . ToPath ( )
: iconNameOrGuid ;
}
void setColor ( )
{
var colorIndex = 0 ;
if ( folderData ! = null & & folderData . colorIndex > 0 )
colorIndex = folderData . colorIndex ;
else if ( recursiveColorIndex ! = 0 )
colorIndex = recursiveColorIndex ;
else if ( ruledColorIndex ! = 0 )
colorIndex = ruledColorIndex ;
if ( colorIndex = = 0 ) { folderInfo . hasColor = false ; return ; }
folderInfo . hasColor = true ;
folderInfo . hasColorByRecursion = recursiveColorIndex ! = 0 ;
var brightness = palette ? . colorBrightness ? ? 1 ;
var saturation = palette ? . colorSaturation ? ? 1 ;
var rawColor = palette ? palette . colors [ colorIndex - 1 ] : VFoldersPalette . GetDefaultColor ( colorIndex - 1 ) ;
var brightenedColor = MathUtil . Lerp ( Greyscale ( . 2f ) , rawColor , brightness ) ;
Color . RGBToHSV ( brightenedColor , out float h , out float s , out float v ) ;
var saturatedColor = Color . HSVToRGB ( h , s * saturation , v ) ;
folderInfo . color = saturatedColor ;
}
checkRules ( ) ;
checkRecursion ( guid . ToPath ( ) , 0 ) ;
setIcon ( ) ;
setColor ( ) ;
return folderInfoCache [ guid ] = folderInfo ;
}
public class FolderInfo
{
public string iconNameOrPath = "" ;
public bool hasIcon ;
public bool hasIconByRecursion ;
public Color color ;
public bool hasColor ;
public bool hasColorByRecursion ;
public int maxColorRecursionDepth ;
public FolderData folderData ;
public FolderState folderState ;
}
public static Dictionary < string , FolderInfo > folderInfoCache = new ( ) ;
public static List < MethodInfo > rules = null ;
public static FolderData GetFolderData ( string guid , bool createDataIfDoesntExist )
{
if ( ! data ) return null ;
FolderData folderData = null ;
void fromScripableObject ( )
{
if ( VFoldersData . storeDataInMetaFiles ) return ;
data . folderDatas_byGuid . TryGetValue ( guid , out folderData ) ;
if ( folderData ! = null | | ! createDataIfDoesntExist ) return ;
folderData = new FolderData ( ) ;
data . folderDatas_byGuid [ guid ] = folderData ;
}
void fromMetaFile ( )
{
if ( ! VFoldersData . storeDataInMetaFiles ) return ;
folderDatasFromMetaFiles_byGuid . TryGetValue ( guid , out folderData ) ;
if ( folderData ! = null ) return ;
var importer = AssetImporter . GetAtPath ( guid . ToPath ( ) ) ;
try { folderData = JsonUtility . FromJson < FolderData > ( importer . userData ) ; } catch { }
folderDatasFromMetaFiles_byGuid [ guid ] = folderData ;
if ( folderData ! = null | | ! createDataIfDoesntExist ) return ;
folderData = new FolderData ( ) ;
folderDatasFromMetaFiles_byGuid [ guid ] = folderData ;
}
fromScripableObject ( ) ;
fromMetaFile ( ) ;
return folderData ;
}
public static Dictionary < string , FolderData > folderDatasFromMetaFiles_byGuid = new ( ) ;
public static FolderState GetFolderState ( string guid )
{
FolderState folderState = null ;
void getCached ( )
{
cache . folderStates_byGuid . TryGetValue ( guid , out folderState ) ;
}
void create ( )
{
if ( folderState ! = null ) return ;
folderState = new FolderState ( ) ;
folderState . needsUpdate = true ;
cache . folderStates_byGuid [ guid ] = folderState ;
}
void update ( )
{
if ( ! folderState . needsUpdate ) return ;
if ( ! Directory . Exists ( guid . ToPath ( ) ) ) { folderState . needsUpdate = false ; return ; }
var typesInFolder = Directory . GetFiles ( guid . ToPath ( ) , "*.*" ) . Select ( r = > AssetDatabase . GetMainAssetTypeAtPath ( r ) ) . Where ( r = > r ! = null ) ;
void isEmpty ( )
{
folderState . isEmpty = ! Directory . EnumerateFileSystemEntries ( guid . ToPath ( ) ) . Any ( ) ;
}
void contentMinimap ( )
{
var iconNames = new List < string > ( ) ;
void fill ( )
{
foreach ( var type in typesInFolder )
if ( type = = typeof ( Texture2D ) )
iconNames . Add ( "Texture Icon" ) ;
else if ( type = = typeof ( GameObject ) )
iconNames . Add ( "Prefab Icon" ) ;
else if ( type . BaseType = = typeof ( ScriptableObject ) | | type . BaseType ? . BaseType = = typeof ( ScriptableObject ) )
iconNames . Add ( "ScriptableObject Icon" ) ;
else if ( type = = typeof ( MonoScript ) )
iconNames . Add ( "cs Script Icon" ) ;
else if ( AssetPreview . GetMiniTypeThumbnail ( type ) ? . name is string iconName )
iconNames . Add ( iconName ) ;
}
void filter ( )
{
iconNames = iconNames . Distinct ( ) . ToList ( ) ;
for ( int i = 0 ; i < iconNames . Count ; i + + )
if ( iconNames [ i ] . StartsWith ( "d_" ) )
iconNames [ i ] = iconNames [ i ] . Substring ( 2 ) ;
iconNames . Remove ( "DefaultAsset Icon" ) ;
iconNames . Remove ( "TextAsset Icon" ) ;
if ( iconNames . Contains ( "cs Script Icon" ) )
iconNames . Remove ( "AssemblyDefinitionAsset Icon" ) ;
if ( iconNames . Contains ( "Shader Icon" ) )
iconNames . Remove ( "ShaderInclude Icon" ) ;
}
void order ( )
{
var order = new List < string >
{
"SceneAsset Icon" ,
"Prefab Icon" ,
"Mesh Icon" ,
"Material Icon" ,
"Texture Icon" ,
"cs Script Icon" ,
"Shader Icon" ,
"ComputeShader Icon" ,
"ShaderInclude Icon" ,
"ScriptableObject Icon" ,
} ;
iconNames = iconNames . OrderBy ( r = > order . IndexOf ( r ) is int i & & i ! = - 1 ? i : 1232 )
. ThenBy ( r = > r )
. ToList ( ) ;
}
fill ( ) ;
filter ( ) ;
order ( ) ;
folderState . contentMinimapIconNames = iconNames ;
}
void autoIcon ( )
{
folderState . autoIconName = "" ;
if ( ! typesInFolder . Any ( ) ) return ;
if ( ! typesInFolder . All ( r = > r = = typesInFolder . First ( ) ) & & ! typesInFolder . All ( r = > typeof ( ScriptableObject ) . IsAssignableFrom ( r ) ) ) return ;
var type = typesInFolder . First ( ) ;
if ( type = = typeof ( SceneAsset ) )
folderState . autoIconName = "SceneAsset Icon" ;
else if ( type = = typeof ( GameObject ) )
folderState . autoIconName = "Prefab Icon" ;
else if ( type = = typeof ( Material ) )
folderState . autoIconName = "Material Icon" ;
else if ( type = = typeof ( Texture ) )
folderState . autoIconName = "Texture Icon" ;
else if ( type = = typeof ( TerrainData ) )
folderState . autoIconName = "TerrainData Icon" ;
else if ( type = = typeof ( AudioClip ) )
folderState . autoIconName = "AudioClip Icon" ;
else if ( type = = typeof ( Shader ) )
folderState . autoIconName = "Shader Icon" ;
else if ( type = = typeof ( ComputeShader ) )
folderState . autoIconName = "ComputeShader Icon" ;
else if ( type = = typeof ( MonoScript ) | | type = = typeof ( UnityEditorInternal . AssemblyDefinitionAsset ) | | type = = typeof ( UnityEditorInternal . AssemblyDefinitionReferenceAsset ) )
folderState . autoIconName = "cs Script Icon" ;
else if ( typeof ( ScriptableObject ) . IsAssignableFrom ( type ) )
folderState . autoIconName = "ScriptableObject Icon" ;
}
isEmpty ( ) ;
contentMinimap ( ) ;
autoIcon ( ) ;
folderState . needsUpdate = false ;
}
getCached ( ) ;
create ( ) ;
update ( ) ;
return folderState ;
}
class FolderStateChangeDetector : AssetPostprocessor
{
#if UNITY_2021_1_OR_NEWER
static void OnPostprocessAllAssets ( string [ ] importedAssets , string [ ] deletedAssets , string [ ] movedAssets , string [ ] movedFromAssetPaths , bool didDomainReload )
#else
static void OnPostprocessAllAssets ( string [ ] importedAssets , string [ ] deletedAssets , string [ ] movedAssets , string [ ] movedFromAssetPaths )
#endif
{
foreach ( var path in importedAssets . Concat ( deletedAssets ) . Concat ( movedAssets ) . Concat ( movedFromAssetPaths ) )
if ( path . HasParentPath ( ) )
if ( cache . folderStates_byGuid . TryGetValue ( path . GetParentPath ( ) . ToGuid ( ) , out var folderState ) )
folderState . needsUpdate = true ; // todo only clear folderinfo cache here?
}
}
public static VFoldersCache cache = > VFoldersCache . instance ;
public static void OnProjectChanged ( ) = > folderInfoCache . Clear ( ) ;
public static void OnDataSerialization ( ) = > folderInfoCache . Clear ( ) ;
[InitializeOnLoadMethod]
static void Init ( )
{
if ( VFoldersMenu . pluginDisabled ) return ;
void subscribe ( )
{
// gui
#if UNITY_2022_1_OR_NEWER
EditorApplication . projectWindowItemInstanceOnGUI - = ItemGUI_2022_1_and_newer ;
EditorApplication . projectWindowItemInstanceOnGUI = ItemGUI_2022_1_and_newer + EditorApplication . projectWindowItemInstanceOnGUI ;
#else
EditorApplication . projectWindowItemOnGUI - = ItemGUI_2021_3_and_older ;
EditorApplication . projectWindowItemOnGUI = ItemGUI_2021_3_and_older + EditorApplication . projectWindowItemOnGUI ;
#endif
// wrapping updaters
EditorApplication . projectWindowItemOnGUI - = ProjectWindowItemOnGUI ;
EditorApplication . projectWindowItemOnGUI + = ProjectWindowItemOnGUI ;
EditorApplication . hierarchyWindowItemOnGUI - = HierarchyWindowItemOnGUI ;
EditorApplication . hierarchyWindowItemOnGUI + = HierarchyWindowItemOnGUI ;
EditorApplication . delayCall - = DelayCallLoop ;
EditorApplication . delayCall + = DelayCallLoop ;
EditorApplication . update - = CheckIfFocusedWindowChanged ;
EditorApplication . update + = CheckIfFocusedWindowChanged ;
// shortcuts
var globalEventHandler = typeof ( EditorApplication ) . GetFieldValue < EditorApplication . CallbackFunction > ( "globalEventHandler" ) ;
typeof ( EditorApplication ) . SetFieldValue ( "globalEventHandler" , Shortcuts + ( globalEventHandler - Shortcuts ) ) ;
// other
EditorApplication . update - = GenerateIconsInUpdate ;
EditorApplication . update + = GenerateIconsInUpdate ;
EditorApplication . projectChanged - = OnProjectChanged ;
EditorApplication . projectChanged + = OnProjectChanged ;
}
void loadData ( )
{
data = AssetDatabase . LoadAssetAtPath < VFoldersData > ( ProjectPrefs . GetString ( "vFolders-lastKnownDataPath" ) ) ;
if ( data ) return ;
data = AssetDatabase . FindAssets ( "t:VFoldersData" ) . Select ( guid = > AssetDatabase . LoadAssetAtPath < VFoldersData > ( guid . ToPath ( ) ) ) . FirstOrDefault ( ) ;
if ( ! data ) return ;
ProjectPrefs . SetString ( "vFolders-lastKnownDataPath" , data . GetPath ( ) ) ;
}
void loadPalette ( )
{
palette = AssetDatabase . LoadAssetAtPath < VFoldersPalette > ( ProjectPrefs . GetString ( "vFolders-lastKnownPalettePath" ) ) ;
if ( palette ) return ;
palette = AssetDatabase . FindAssets ( "t:VFoldersPalette" ) . Select ( guid = > AssetDatabase . LoadAssetAtPath < VFoldersPalette > ( guid . ToPath ( ) ) ) . FirstOrDefault ( ) ;
if ( ! palette ) return ;
ProjectPrefs . SetString ( "vFolders-lastKnownPalettePath" , palette . GetPath ( ) ) ;
}
void loadDataAndPaletteDelayed ( )
{
if ( ! data )
EditorApplication . delayCall + = ( ) = > EditorApplication . delayCall + = loadData ;
if ( ! palette )
EditorApplication . delayCall + = ( ) = > EditorApplication . delayCall + = loadPalette ;
// AssetDatabase isn't up to date at this point (it gets updated after InitializeOnLoadMethod)
// and if current AssetDatabase state doesn't contain the data - it won't be loaded during Init()
// so here we schedule an additional, delayed attempt to load the data
// this addresses reports of data loss when trying to load it on a new machine
}
void migrateDataFromV1 ( )
{
if ( ! data ) return ;
if ( ProjectPrefs . GetBool ( "vFolders-dataMigrationFromV1Attempted" , false ) ) return ;
ProjectPrefs . SetBool ( "vFolders-dataMigrationFromV1Attempted" , true ) ;
var lines = System . IO . File . ReadAllLines ( data . GetPath ( ) ) ;
if ( lines . Length < 15 | | ! lines [ 14 ] . Contains ( "folderDatasByGuid" ) ) return ;
var guids = new List < string > ( ) ;
var icons = new List < string > ( ) ;
var colors = new List < int > ( ) ;
void parseGudis ( )
{
for ( int i = 16 ; i < lines . Length ; i + + )
{
if ( lines [ i ] . Contains ( "values:" ) ) break ;
var startIndex = lines [ i ] . IndexOf ( "- " ) + 2 ;
if ( startIndex < lines [ i ] . Length )
guids . Add ( lines [ i ] . Substring ( startIndex ) ) ;
else
guids . Add ( "" ) ;
}
}
void parseIcons ( )
{
for ( int i = 0 ; i < guids . Count ; i + + )
if ( lines [ 29 + i * 5 + 3 ] is string line )
if ( line . Length > line . IndexOf ( ": " ) + 2 )
icons . Add ( line . Substring ( line . IndexOf ( ": " ) + 2 ) ) ;
else
icons . Add ( "" ) ;
}
void parseColors ( )
{
for ( int i = 0 ; i < guids . Count ; i + + )
if ( lines [ 29 + i * 5 + 1 ] is string line )
if ( line . Length > line . IndexOf ( ": " ) + 2 )
colors . Add ( int . Parse ( line . Substring ( line . IndexOf ( ": " ) + 2 ) ) ) ;
else
colors . Add ( 0 ) ;
}
void remapColors ( )
{
for ( int i = 0 ; i < colors . Count ; i + + )
if ( colors [ i ] = = 10 )
colors [ i ] = 1 ;
else if ( colors [ i ] ! = 0 )
colors [ i ] + + ;
}
void fillData ( )
{
for ( int i = 0 ; i < guids . Count ; i + + )
if ( icons [ i ] ! = "" | | colors [ i ] ! = 0 )
data . folderDatas_byGuid [ guids [ i ] ] = new FolderData { iconNameOrGuid = icons [ i ] , colorIndex = colors [ i ] } ;
data . Dirty ( ) ;
data . Save ( ) ;
}
try
{
parseGudis ( ) ;
parseIcons ( ) ;
parseColors ( ) ;
remapColors ( ) ;
fillData ( ) ;
}
catch { }
}
void fixIconNamesForUnity6 ( )
{
if ( ! Application . unityVersion . Contains ( "6000" ) ) return ;
if ( ProjectPrefs . GetBool ( "vFolders-iconNamesForUnity6Fixed" , false ) ) return ;
if ( ! palette ) return ;
if ( ! data ) return ;
foreach ( var iconRow in palette . iconRows )
if ( iconRow . builtinIcons . Contains ( "PhysicMaterial Icon" ) )
iconRow . builtinIcons [ iconRow . builtinIcons . IndexOf ( "PhysicMaterial Icon" ) ] = "PhysicsMaterial Icon" ;
foreach ( var folderData in data . folderDatas_byGuid . Values )
if ( folderData . iconNameOrGuid = = "PhysicMaterial Icon" )
folderData . iconNameOrGuid = "PhysicsMaterial Icon" ;
ProjectPrefs . SetBool ( "vFolders-iconNamesForUnity6Fixed" , true ) ;
}
subscribe ( ) ;
loadData ( ) ;
loadPalette ( ) ;
loadDataAndPaletteDelayed ( ) ;
migrateDataFromV1 ( ) ;
fixIconNamesForUnity6 ( ) ;
OnDomainReloaded ( ) ;
}
public static VFoldersData data ;
public static VFoldersPalette palette ;
static IEnumerable < EditorWindow > allBrowsers = > _allBrowsers ? ? = t_ProjectBrowser . GetFieldValue < IList > ( "s_ProjectBrowsers" ) . Cast < EditorWindow > ( ) ;
static IEnumerable < EditorWindow > _allBrowsers ;
static Type t_ProjectBrowser = typeof ( Editor ) . Assembly . GetType ( "UnityEditor.ProjectBrowser" ) ;
static Type t_HostView = typeof ( Editor ) . Assembly . GetType ( "UnityEditor.HostView" ) ;
static Type t_EditorWindowDelegate = t_HostView . GetNestedType ( "EditorWindowDelegate" , maxBindingFlags ) ;
static Type t_VTabs = Type . GetType ( "VTabs.VTabs" ) ? ? Type . GetType ( "VTabs.VTabs, VTabs, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" ) ;
static Type t_VFavorites = Type . GetType ( "VFavorites.VFavorites" ) ? ? Type . GetType ( "VFavorites.VFavorites, VFavorites, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null" ) ;
static MethodInfo mi_WrappedBrowserOnGUI = typeof ( VFolders ) . GetMethod ( nameof ( WrappedGUI ) , maxBindingFlags ) ;
static MethodInfo mi_VFavorites_WrappedOnGUI = t_VFavorites ? . GetMethod ( "WrappedOnGUI" , maxBindingFlags ) ;
const string version = "2.1.5" ;
}
#region Rules
public class RuleAttribute : System . Attribute { }
public class Folder
{
public string path = > guid . ToPath ( ) ;
public string name = > path . Split ( '/' ) . Last ( ) ;
public int color = 0 ;
public string icon = "" ;
public Folder ( string guid ) = > this . guid = guid ;
string guid ;
}
#endregion
}
#endif