@@ -7,17 +7,20 @@ namespace Toolbox.Editor.SceneView
77 public class ToolboxEditorSceneViewObjectSelector : EditorWindow
88 {
99 private static readonly Color selectionColor = new Color ( 0.50f , 0.70f , 1.00f ) ;
10+ private static readonly Color highlightWireColor = Color . yellow ;
11+ private const float outlineFillOpacity = 1f ;
1012
1113 private const float sizeXPadding = 2f ;
1214 private const float sizeYPadding = 2f ;
1315 private const float buttonYSpacing = 0.0f ;
1416 private const float buttonYSize = 20f ;
1517 private const float sizeXOffset = - 30f ;
18+ private const float indentWidth = 12f ;
1619
17- private List < GameObject > gameObjects ;
18- private List < string > gameObjectPaths ;
20+ private List < DisplayEntry > displayEntries ;
1921
2022 private GameObject highlightedObject ;
23+ private readonly List < Renderer > highlightedRenderers = new List < Renderer > ( ) ;
2124 private Vector2 size ;
2225 private Vector2 buttonSize ;
2326
@@ -32,12 +35,23 @@ public static void Show(List<GameObject> gameObjects, Vector2 position)
3235 var window = CreateInstance < ToolboxEditorSceneViewObjectSelector > ( ) ;
3336 window . wantsMouseMove = true ;
3437 window . wantsMouseEnterLeaveWindow = true ;
35- window . gameObjects = gameObjects ;
36- window . InitializeGameObjectPaths ( ) ;
38+ window . displayEntries = window . BuildDisplayEntries ( gameObjects ) ;
3739 window . CalculateSize ( ) ;
3840 window . ShowAsDropDown ( rect , window . size ) ;
3941 }
4042
43+ private void OnEnable ( )
44+ {
45+ UnityEditor . SceneView . duringSceneGui += OnSceneViewDuringSceneGui ;
46+ }
47+
48+ private void OnDisable ( )
49+ {
50+ UnityEditor . SceneView . duringSceneGui -= OnSceneViewDuringSceneGui ;
51+ highlightedRenderers . Clear ( ) ;
52+ highlightedObject = null ;
53+ }
54+
4155 private void OnGUI ( )
4256 {
4357 if ( Event . current . type == EventType . Layout )
@@ -67,18 +81,24 @@ private void OnGuiMouseLeave()
6781 private void OnGuiMouseMove ( )
6882 {
6983 var rect = new Rect ( sizeXPadding , sizeYPadding , buttonSize . x , buttonSize . y ) ;
70- for ( var i = 0 ; i < gameObjects . Count ; i ++ )
84+ for ( var i = 0 ; i < displayEntries . Count ; i ++ )
7185 {
72- var gameObject = gameObjects [ i ] ;
86+ var entry = displayEntries [ i ] ;
87+ var gameObject = entry . GameObject ;
7388 if ( gameObject == null )
7489 {
7590 //Can happen when something removes the gameobject during the window display.
7691 continue ;
7792 }
7893
94+ var indentOffset = entry . Depth * indentWidth ;
95+ var drawRect = new Rect ( rect ) ;
96+ drawRect . x += indentOffset ;
97+ drawRect . width -= indentOffset ;
98+
7999 var content = EditorGUIUtility . ObjectContent ( gameObject , typeof ( GameObject ) ) ;
80- GUI . Button ( rect , content , Style . buttonStyle ) ;
81- if ( rect . Contains ( Event . current . mousePosition ) )
100+ GUI . Button ( drawRect , content , Style . buttonStyle ) ;
101+ if ( drawRect . Contains ( Event . current . mousePosition ) )
82102 {
83103 HighlightedObject = gameObject ;
84104 }
@@ -90,23 +110,29 @@ private void OnGuiMouseMove()
90110 private void OnGuiNormal ( )
91111 {
92112 var rect = new Rect ( sizeXPadding , sizeYPadding , buttonSize . x , buttonSize . y ) ;
93- for ( var i = 0 ; i < gameObjects . Count ; i ++ )
113+ for ( var i = 0 ; i < displayEntries . Count ; i ++ )
94114 {
95- var gameObject = gameObjects [ i ] ;
115+ var entry = displayEntries [ i ] ;
116+ var gameObject = entry . GameObject ;
96117 if ( gameObject == null )
97118 {
98119 //Can happen when something removes the gameobject during the window display.
99120 continue ;
100121 }
101122
123+ var indentOffset = entry . Depth * indentWidth ;
124+ var drawRect = new Rect ( rect ) ;
125+ drawRect . x += indentOffset ;
126+ drawRect . width -= indentOffset ;
127+
102128 var content = EditorGUIUtility . ObjectContent ( gameObject , typeof ( GameObject ) ) ;
103129 var objectSelected = Selection . Contains ( gameObject ) ;
104130 if ( objectSelected )
105131 {
106132 GUI . backgroundColor = selectionColor ;
107133 }
108134
109- if ( GUI . Button ( rect , content , Style . buttonStyle ) )
135+ if ( GUI . Button ( drawRect , content , Style . buttonStyle ) )
110136 {
111137 GameObjectButtonPress ( i ) ;
112138 }
@@ -120,47 +146,107 @@ private Vector2 CalculateSize()
120146 {
121147 size = Vector2 . zero ;
122148
123- foreach ( var go in gameObjects )
149+ var maxIndent = 0f ;
150+ foreach ( var entry in displayEntries )
124151 {
125- var content = EditorGUIUtility . ObjectContent ( go , typeof ( GameObject ) ) ;
152+ var content = EditorGUIUtility . ObjectContent ( entry . GameObject , typeof ( GameObject ) ) ;
126153 var currentSize = Style . buttonStyle . CalcSize ( content ) ;
127154 if ( currentSize . x > size . x )
128155 {
129156 size . x = currentSize . x ;
130157 }
158+
159+ var currentIndent = entry . Depth * indentWidth ;
160+ if ( currentIndent > maxIndent )
161+ {
162+ maxIndent = currentIndent ;
163+ }
131164 }
132165
166+ size . x += maxIndent ;
133167 //This is needed because CalcSize calculates content drawing with icon at full size.
134168 size . x += sizeXOffset ;
135169
136170 buttonSize . x = size . x ;
137171 buttonSize . y = buttonYSize ;
138172
139- size . y = gameObjects . Count * buttonYSize + sizeYPadding * 2.0f + buttonYSpacing * gameObjects . Count - 1 ;
173+ size . y = displayEntries . Count * buttonYSize + sizeYPadding * 2.0f + buttonYSpacing * displayEntries . Count - 1 ;
140174 size . x += sizeXPadding * 2.0f ;
141175
142176 return size ;
143177 }
144178
145- private void InitializeGameObjectPaths ( )
179+ private List < DisplayEntry > BuildDisplayEntries ( List < GameObject > objectsUnderCursor )
146180 {
147- gameObjectPaths = new List < string > ( ) ;
148- var pathStack = new Stack < string > ( ) ;
181+ var entries = new List < DisplayEntry > ( ) ;
182+
183+ if ( objectsUnderCursor == null || objectsUnderCursor . Count == 0 )
184+ {
185+ return entries ;
186+ }
187+
188+ var underCursorSet = new HashSet < Transform > ( ) ;
189+ foreach ( var gameObject in objectsUnderCursor )
190+ {
191+ if ( gameObject == null )
192+ {
193+ continue ;
194+ }
195+
196+ underCursorSet . Add ( gameObject . transform ) ;
197+ }
198+
199+ var relevantTransforms = new HashSet < Transform > ( ) ;
200+ foreach ( var transform in underCursorSet )
201+ {
202+ var current = transform ;
203+ while ( current != null && relevantTransforms . Add ( current ) )
204+ {
205+ current = current . parent ;
206+ }
207+ }
208+
209+ var orderedRoots = new List < Transform > ( ) ;
210+ foreach ( var gameObject in objectsUnderCursor )
211+ {
212+ if ( gameObject == null )
213+ {
214+ continue ;
215+ }
216+
217+ var top = gameObject . transform ;
218+ while ( top . parent != null && relevantTransforms . Contains ( top . parent ) )
219+ {
220+ top = top . parent ;
221+ }
222+
223+ if ( ! orderedRoots . Contains ( top ) )
224+ {
225+ orderedRoots . Add ( top ) ;
226+ }
227+ }
149228
150- for ( var i = 0 ; i < gameObjects . Count ; i ++ )
229+ foreach ( var root in orderedRoots )
151230 {
152- pathStack . Clear ( ) ;
153- var transform = gameObjects [ i ] . transform ;
154- pathStack . Push ( transform . gameObject . name ) ;
231+ AppendHierarchy ( root , 0 , relevantTransforms , entries ) ;
232+ }
233+
234+ return entries ;
235+ }
236+
237+ private void AppendHierarchy ( Transform current , int depth , HashSet < Transform > relevantTransforms , List < DisplayEntry > entries )
238+ {
239+ entries . Add ( new DisplayEntry ( current . gameObject , depth ) ) ;
155240
156- while ( transform . parent != null )
241+ for ( var i = 0 ; i < current . childCount ; i ++ )
242+ {
243+ var child = current . GetChild ( i ) ;
244+ if ( ! relevantTransforms . Contains ( child ) )
157245 {
158- transform = transform . parent ;
159- pathStack . Push ( transform . gameObject . name ) ;
246+ continue ;
160247 }
161248
162- var path = string . Join ( "/" , pathStack . ToArray ( ) ) ;
163- gameObjectPaths . Add ( path ) ;
249+ AppendHierarchy ( child , depth + 1 , relevantTransforms , entries ) ;
164250 }
165251 }
166252
@@ -218,7 +304,7 @@ private void UpdateShiftSelectionIDs(int id)
218304
219305 private void SelectObject ( int id , bool control , bool shift )
220306 {
221- var gameObject = gameObjects [ id ] ;
307+ var gameObject = displayEntries [ id ] . GameObject ;
222308
223309 if ( shift )
224310 {
@@ -252,7 +338,7 @@ private void SelectObjects(int minId, int maxId)
252338
253339 for ( var i = minId ; i <= maxId ; i ++ )
254340 {
255- newSelection [ index ] = gameObjects [ i ] ;
341+ newSelection [ index ] = displayEntries [ i ] . GameObject ;
256342 index ++ ;
257343 }
258344
@@ -307,9 +393,63 @@ private GameObject HighlightedObject
307393 {
308394 EditorGUIUtility . PingObject ( highlightedObject ) ;
309395 }
396+
397+ UpdateHighlightedRenderers ( ) ;
310398 }
311399 }
312400
401+ private void UpdateHighlightedRenderers ( )
402+ {
403+ highlightedRenderers . Clear ( ) ;
404+
405+ if ( highlightedObject == null )
406+ {
407+ return ;
408+ }
409+
410+ highlightedRenderers . AddRange ( highlightedObject . GetComponentsInChildren < Renderer > ( true ) ) ;
411+ }
412+
413+ private void OnSceneViewDuringSceneGui ( UnityEditor . SceneView sceneView )
414+ {
415+ if ( highlightedRenderers . Count == 0 ||
416+ Event . current . type != EventType . Repaint )
417+ {
418+ return ;
419+ }
420+
421+ // Unity 6.1+ provides Handles.DrawOutline which also highlights children in one call.
422+ #if UNITY_6000_1_OR_NEWER
423+ Handles . DrawOutline ( highlightedRenderers . ToArray ( ) , highlightWireColor , highlightWireColor , outlineFillOpacity ) ;
424+ #else
425+ using ( new Handles . DrawingScope ( highlightWireColor ) )
426+ {
427+ foreach ( var renderer in highlightedRenderers )
428+ {
429+ if ( renderer == null )
430+ {
431+ continue ;
432+ }
433+
434+ var bounds = renderer . bounds ;
435+ Handles . DrawWireCube ( bounds . center , bounds . size ) ;
436+ }
437+ }
438+ #endif
439+ }
440+
441+ private readonly struct DisplayEntry
442+ {
443+ internal DisplayEntry ( GameObject gameObject , int depth )
444+ {
445+ GameObject = gameObject ;
446+ Depth = depth ;
447+ }
448+
449+ internal GameObject GameObject { get ; }
450+ internal int Depth { get ; }
451+ }
452+
313453 private static class Style
314454 {
315455 internal static readonly GUIStyle buttonStyle ;
0 commit comments