1+ #if UNITY_2019_1_OR_NEWER
2+ using System ;
3+ using System . Collections ;
4+ using System . Collections . Generic ;
5+ using System . Linq ;
6+ using System . Reflection ;
7+ using UnityEditor ;
8+ using UnityEditor . IMGUI . Controls ;
9+ using UnityEngine ;
10+ using UnityHierarchyFolders . Runtime ;
11+ using Object = UnityEngine . Object ;
12+
13+ namespace UnityHierarchyFolders . Editor
14+ {
15+ public static class HierarchyFolderIcon
16+ {
17+ #if UNITY_2020_1_OR_NEWER
18+ private const string _openedFolderPrefix = "FolderOpened" ;
19+ #else
20+ private const string _openedFolderPrefix = "OpenedFolder" ;
21+ #endif
22+ private const string _closedFolderPrefix = "Folder" ;
23+
24+ private static Texture2D _openFolderTexture ;
25+ private static Texture2D _closedFolderTexture ;
26+ private static Texture2D _openFolderSelectedTexture ;
27+ private static Texture2D _closedFolderSelectedTexture ;
28+
29+ private static bool _isInitialized ;
30+ private static bool _hasProcessedFrame = true ;
31+
32+ // Reflected members
33+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE1006:Naming Styles" , Justification = "Special naming scheme" ) ]
34+ private static PropertyInfo prop_sceneHierarchy ;
35+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE1006:Naming Styles" , Justification = "Special naming scheme" ) ]
36+ private static PropertyInfo prop_treeView ;
37+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE1006:Naming Styles" , Justification = "Special naming scheme" ) ]
38+ private static PropertyInfo prop_data ;
39+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE1006:Naming Styles" , Justification = "Special naming scheme" ) ]
40+ private static PropertyInfo prop_selectedIcon ;
41+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE1006:Naming Styles" , Justification = "Special naming scheme" ) ]
42+ private static PropertyInfo prop_objectPPTR ;
43+
44+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE1006:Naming Styles" , Justification = "Special naming scheme" ) ]
45+ private static MethodInfo meth_getRows ;
46+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE1006:Naming Styles" , Justification = "Special naming scheme" ) ]
47+ private static MethodInfo meth_isExpanded ;
48+ [ System . Diagnostics . CodeAnalysis . SuppressMessage ( "Style" , "IDE1006:Naming Styles" , Justification = "Special naming scheme" ) ]
49+ private static MethodInfo meth_getAllSceneHierarchyWindows ;
50+
51+ private static ( Texture2D open , Texture2D closed ) [ ] _coloredFolderIcons ;
52+ public static ( Texture2D open , Texture2D closed ) ColoredFolderIcons ( int i ) => _coloredFolderIcons [ i ] ;
53+
54+ public static int IconColumnCount => IconColors . GetLength ( 0 ) ;
55+ public static int IconRowCount => IconColors . GetLength ( 1 ) ;
56+
57+ private static readonly Color [ , ] IconColors = {
58+ { new Color ( 0.09f , 0.57f , 0.82f ) , new Color ( 0.05f , 0.34f , 0.48f ) , } ,
59+ { new Color ( 0.09f , 0.67f , 0.67f ) , new Color ( 0.05f , 0.42f , 0.42f ) , } ,
60+ { new Color ( 0.23f , 0.73f , 0.36f ) , new Color ( 0.15f , 0.41f , 0.22f ) , } ,
61+ { new Color ( 0.55f , 0.35f , 0.71f ) , new Color ( 0.35f , 0.24f , 0.44f ) , } ,
62+ { new Color ( 0.78f , 0.27f , 0.55f ) , new Color ( 0.52f , 0.15f , 0.35f ) , } ,
63+ { new Color ( 0.80f , 0.66f , 0.10f ) , new Color ( 0.56f , 0.46f , 0.02f ) , } ,
64+ { new Color ( 0.91f , 0.49f , 0.13f ) , new Color ( 0.62f , 0.33f , 0.07f ) , } ,
65+ { new Color ( 0.91f , 0.30f , 0.24f ) , new Color ( 0.77f , 0.15f , 0.09f ) , } ,
66+ { new Color ( 0.35f , 0.49f , 0.63f ) , new Color ( 0.24f , 0.33f , 0.42f ) , } ,
67+ } ;
68+
69+ [ InitializeOnLoadMethod ]
70+ private static void Startup ( )
71+ {
72+ EditorApplication . update += ResetFolderIcons ;
73+ EditorApplication . hierarchyWindowItemOnGUI += RefreshFolderIcons ;
74+ }
75+
76+ private static void InitIfNeeded ( )
77+ {
78+ if ( _isInitialized ) { return ; }
79+
80+ _openFolderTexture = ( Texture2D ) EditorGUIUtility . IconContent ( $ "{ _openedFolderPrefix } Icon") . image ;
81+ _closedFolderTexture = ( Texture2D ) EditorGUIUtility . IconContent ( $ "{ _closedFolderPrefix } Icon") . image ;
82+
83+ // We could use the actual white folder icons but I prefer the look of the tinted white folder icon
84+ // To use the actual white version:
85+ // texture = (Texture2D) EditorGUIUtility.IconContent($"{OpenedFolderPrefix | ClosedFolderPrefix} On Icon").image;
86+ _openFolderSelectedTexture = TextureHelper . GetWhiteTexture ( _openFolderTexture , $ "{ _openedFolderPrefix } Icon White") ;
87+ _closedFolderSelectedTexture = TextureHelper . GetWhiteTexture ( _closedFolderTexture , $ "{ _closedFolderPrefix } Icon White") ;
88+
89+ _coloredFolderIcons = new ( Texture2D , Texture2D ) [ ] { ( _openFolderTexture , _closedFolderTexture ) } ;
90+
91+ for ( int row = 0 ; row < IconRowCount ; row ++ )
92+ {
93+ for ( int column = 0 ; column < IconColumnCount ; column ++ )
94+ {
95+ int index = 1 + column + row * IconColumnCount ;
96+ var color = IconColors [ column , row ] ;
97+
98+ var openFolderIcon = TextureHelper . GetTintedTexture ( _openFolderSelectedTexture ,
99+ color , $ "{ _openFolderSelectedTexture . name } { index } ") ;
100+ var closedFolderIcon = TextureHelper . GetTintedTexture ( _closedFolderSelectedTexture ,
101+ color , $ "{ _closedFolderSelectedTexture . name } { index } ") ;
102+
103+ ArrayUtility . Add ( ref _coloredFolderIcons , ( openFolderIcon , closedFolderIcon ) ) ;
104+ }
105+ }
106+
107+ // reflection
108+
109+ const BindingFlags BindingAll = BindingFlags . Public
110+ | BindingFlags . NonPublic | BindingFlags . Static | BindingFlags . Instance ;
111+
112+ var assembly = typeof ( SceneView ) . Assembly ;
113+
114+ var type_sceneHierarchyWindow = assembly . GetType ( "UnityEditor.SceneHierarchyWindow" ) ;
115+ meth_getAllSceneHierarchyWindows = type_sceneHierarchyWindow . GetMethod ( "GetAllSceneHierarchyWindows" , BindingAll ) ;
116+ prop_sceneHierarchy = type_sceneHierarchyWindow . GetProperty ( "sceneHierarchy" ) ;
117+
118+ var type_sceneHierarchy = assembly . GetType ( "UnityEditor.SceneHierarchy" ) ;
119+ prop_treeView = type_sceneHierarchy . GetProperty ( "treeView" , BindingAll ) ;
120+
121+ var type_treeViewController = assembly . GetType ( "UnityEditor.IMGUI.Controls.TreeViewController" ) ;
122+ prop_data = type_treeViewController . GetProperty ( "data" , BindingAll ) ;
123+
124+ var type_iTreeViewDataSource = assembly . GetType ( "UnityEditor.IMGUI.Controls.ITreeViewDataSource" ) ;
125+ meth_getRows = type_iTreeViewDataSource . GetMethod ( "GetRows" ) ;
126+ meth_isExpanded = type_iTreeViewDataSource . GetMethod ( "IsExpanded" , new Type [ ] { typeof ( TreeViewItem ) } ) ;
127+
128+ var type_gameObjectTreeViewItem = assembly . GetType ( "UnityEditor.GameObjectTreeViewItem" ) ;
129+ prop_selectedIcon = type_gameObjectTreeViewItem . GetProperty ( "selectedIcon" , BindingAll ) ;
130+ prop_objectPPTR = type_gameObjectTreeViewItem . GetProperty ( "objectPPTR" , BindingAll ) ;
131+
132+ _isInitialized = true ;
133+ }
134+
135+ private static void ResetFolderIcons ( )
136+ {
137+ InitIfNeeded ( ) ;
138+ _hasProcessedFrame = false ;
139+ }
140+
141+ private static void RefreshFolderIcons ( int instanceid , Rect selectionrect )
142+ {
143+ if ( _hasProcessedFrame ) { return ; }
144+
145+ _hasProcessedFrame = true ;
146+
147+ var windows = ( ( IEnumerable ) meth_getAllSceneHierarchyWindows . Invoke ( null , Array . Empty < object > ( ) ) ) . Cast < EditorWindow > ( ) . ToList ( ) ;
148+ foreach ( var window in windows )
149+ {
150+ object sceneHierarchy = prop_sceneHierarchy . GetValue ( window ) ;
151+ object treeView = prop_treeView . GetValue ( sceneHierarchy ) ;
152+ object data = prop_data . GetValue ( treeView ) ;
153+
154+ var rows = ( IList < TreeViewItem > ) meth_getRows . Invoke ( data , Array . Empty < object > ( ) ) ;
155+ foreach ( var item in rows )
156+ {
157+ var itemObject = ( Object ) prop_objectPPTR . GetValue ( item ) ;
158+ if ( ! Folder . TryGetIconIndex ( itemObject , out int colorIndex ) ) { continue ; }
159+
160+ bool isExpanded = ( bool ) meth_isExpanded . Invoke ( data , new object [ ] { item } ) ;
161+
162+ var icons = ColoredFolderIcons ( Mathf . Clamp ( colorIndex , 0 , _coloredFolderIcons . Length - 1 ) ) ;
163+
164+ item . icon = isExpanded ? icons . open : icons . closed ;
165+
166+ prop_selectedIcon . SetValue ( item , isExpanded ? _openFolderSelectedTexture : _closedFolderSelectedTexture ) ;
167+ }
168+ }
169+ }
170+ }
171+ }
172+ #endif
0 commit comments