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