1+ using System . Collections . Generic ;
2+ using UnityEditor ;
3+ using UnityEngine ;
4+
5+ namespace Toolbox . Editor . SceneView
6+ {
7+ public class ToolboxEditorSceneViewObjectSelector : EditorWindow
8+ {
9+ private List < GameObject > gameObjects ;
10+ private List < string > gameObjectPaths ;
11+
12+ private const float sizeXPadding = 2f ;
13+ private const float sizeYPadding = 2f ;
14+ private const float buttonYSpacing = 0.0f ;
15+ private const float buttonYSize = 20f ;
16+ private const float sizeXOffset = - 30f ;
17+
18+ private GameObject highlightedObject ;
19+ private Vector2 size ;
20+ private Vector2 buttonSize ;
21+ private static readonly Color selectionColor = new Color ( 0.50f , 0.70f , 1.00f ) ;
22+
23+ private int shiftMinSelectionId = - 1 ;
24+ private int shiftMaxSelectionId = - 1 ;
25+ private int shiftLastId = - 1 ;
26+
27+ public static void Show ( List < GameObject > gameObjects , Vector2 position )
28+ {
29+ var rect = new Rect ( position , Vector2 . one ) ;
30+
31+ var window = CreateInstance < ToolboxEditorSceneViewObjectSelector > ( ) ;
32+ window . wantsMouseMove = true ;
33+ window . wantsMouseEnterLeaveWindow = true ;
34+ window . gameObjects = gameObjects ;
35+ window . InitializeGameObjectPaths ( ) ;
36+ window . CalculateSize ( ) ;
37+ window . ShowAsDropDown ( rect , window . size ) ;
38+ }
39+
40+ private Vector2 CalculateSize ( )
41+ {
42+ size = Vector2 . zero ;
43+
44+ foreach ( var go in gameObjects )
45+ {
46+ GUIContent content = EditorGUIUtility . ObjectContent ( go , typeof ( GameObject ) ) ;
47+ Vector2 currentSize = Style . buttonStyle . CalcSize ( content ) ;
48+ if ( currentSize . x > size . x )
49+ {
50+ size . x = currentSize . x ;
51+ }
52+ }
53+
54+ //This is needed because CalcSize calculates content drawing with icon at full size.
55+ size . x += sizeXOffset ;
56+
57+ buttonSize . x = size . x ;
58+ buttonSize . y = buttonYSize ;
59+
60+ size . y = gameObjects . Count * buttonYSize + sizeYPadding * 2.0f + buttonYSpacing * gameObjects . Count - 1 ;
61+ size . x += sizeXPadding * 2.0f ;
62+
63+ return size ;
64+ }
65+
66+ private void InitializeGameObjectPaths ( )
67+ {
68+ gameObjectPaths = new List < string > ( ) ;
69+ var pathStack = new Stack < string > ( ) ;
70+
71+ for ( int i = 0 ; i < gameObjects . Count ; i ++ )
72+ {
73+ pathStack . Clear ( ) ;
74+ Transform transform = gameObjects [ i ] . transform ;
75+ pathStack . Push ( transform . gameObject . name ) ;
76+
77+ while ( transform . parent != null )
78+ {
79+ transform = transform . parent ;
80+ pathStack . Push ( transform . gameObject . name ) ;
81+ }
82+
83+ string path = string . Join ( "/" , pathStack . ToArray ( ) ) ;
84+ gameObjectPaths . Add ( path ) ;
85+ }
86+ }
87+
88+ private void OnGUI ( )
89+ {
90+ if ( Event . current . type == EventType . Layout )
91+ {
92+ return ;
93+ }
94+
95+ switch ( Event . current . type )
96+ {
97+ case EventType . MouseMove :
98+ {
99+ OnGUIMouseMove ( ) ;
100+ break ;
101+ }
102+ case EventType . MouseLeaveWindow :
103+ {
104+ OnGUIMouseLeave ( ) ;
105+ break ;
106+ }
107+ default :
108+ {
109+ OnGUINormal ( ) ;
110+ break ;
111+ }
112+ }
113+ }
114+
115+ private void OnGUINormal ( )
116+ {
117+ Rect rect = new Rect ( sizeXPadding , sizeYPadding , buttonSize . x , buttonSize . y ) ;
118+
119+ for ( int i = 0 ; i < gameObjects . Count ; i ++ )
120+ {
121+ var gameObject = gameObjects [ i ] ;
122+
123+ if ( gameObject == null )
124+ {
125+ //Can happen when something removes the gameobject during the window display.
126+ continue ;
127+ }
128+
129+ var content = EditorGUIUtility . ObjectContent ( gameObject , typeof ( GameObject ) ) ;
130+
131+ bool objectSelected = Selection . Contains ( gameObject ) ;
132+
133+ if ( objectSelected )
134+ {
135+ GUI . backgroundColor = selectionColor ;
136+ }
137+
138+ if ( GUI . Button ( rect , content , Style . buttonStyle ) )
139+ {
140+ GameObjectButtonPress ( i ) ;
141+ }
142+
143+ GUI . backgroundColor = Color . white ;
144+
145+ rect . y += buttonYSize + buttonYSpacing ;
146+ }
147+ }
148+
149+ private void OnGUIMouseMove ( )
150+ {
151+ var rect = new Rect ( sizeXPadding , sizeYPadding , buttonSize . x , buttonSize . y ) ;
152+
153+ for ( int i = 0 ; i < gameObjects . Count ; i ++ )
154+ {
155+ var gameObject = gameObjects [ i ] ;
156+
157+ if ( gameObject == null )
158+ {
159+ //Can happen when something removes the gameobject during the window display.
160+ continue ;
161+ }
162+
163+ var content = EditorGUIUtility . ObjectContent ( gameObject , typeof ( GameObject ) ) ;
164+
165+ GUI . Button ( rect , content , Style . buttonStyle ) ;
166+
167+ if ( rect . Contains ( Event . current . mousePosition ) )
168+ {
169+ HighlightedObject = gameObject ;
170+ }
171+
172+ rect . y += buttonYSize + buttonYSpacing ;
173+ }
174+ }
175+
176+ private void GameObjectButtonPress ( int id )
177+ {
178+ SelectObject ( id , Event . current . control , Event . current . shift ) ;
179+
180+ if ( Event . current . control || Event . current . shift )
181+ {
182+ return ;
183+ }
184+
185+ Close ( ) ;
186+ }
187+
188+ private void UpdateShiftSelectionIDs ( int id )
189+ {
190+ if ( shiftLastId == - 1 )
191+ {
192+ shiftLastId = id ;
193+ }
194+
195+ if ( shiftMinSelectionId == - 1 )
196+ {
197+ shiftMinSelectionId = id ;
198+ }
199+
200+ if ( shiftMaxSelectionId == - 1 )
201+ {
202+ shiftMaxSelectionId = id ;
203+ }
204+
205+ if ( id < shiftMinSelectionId )
206+ {
207+ shiftMinSelectionId = id ;
208+ }
209+ else if ( id >= shiftMaxSelectionId )
210+ {
211+ shiftMaxSelectionId = id ;
212+ }
213+ else if ( id > shiftMinSelectionId )
214+ {
215+ //ID is between min and max.
216+ if ( shiftLastId < id )
217+ {
218+ shiftMaxSelectionId = id ;
219+ }
220+ else
221+ {
222+ shiftMinSelectionId = id ;
223+ }
224+ }
225+
226+ shiftLastId = id ;
227+ }
228+
229+ private void SelectObject ( int id , bool control , bool shift )
230+ {
231+ var gameObject = gameObjects [ id ] ;
232+
233+ if ( shift )
234+ {
235+ UpdateShiftSelectionIDs ( id ) ;
236+ SelectObjects ( shiftMinSelectionId , shiftMaxSelectionId ) ;
237+ }
238+ else if ( control )
239+ {
240+ UpdateShiftSelectionIDs ( id ) ;
241+
242+ if ( Selection . Contains ( gameObject ) )
243+ {
244+ //Deselect
245+ RemoveObjectFromSelection ( gameObject ) ;
246+ }
247+ else
248+ {
249+ //Select
250+ AddObjectToSelection ( gameObject ) ;
251+ }
252+ }
253+ else
254+ {
255+ Selection . objects = new Object [ ] { gameObject } ;
256+ }
257+ }
258+
259+ private void SelectObjects ( int minID , int maxID )
260+ {
261+ var size = maxID - minID + 1 ;
262+ var newSelection = new Object [ size ] ;
263+
264+ int index = 0 ;
265+
266+ for ( int i = minID ; i <= maxID ; i ++ )
267+ {
268+ newSelection [ index ] = gameObjects [ i ] ;
269+ index ++ ;
270+ }
271+
272+ Selection . objects = newSelection ;
273+ }
274+
275+ private void AddObjectToSelection ( GameObject gameObject )
276+ {
277+ var currentSelection = Selection . objects ;
278+ var newSelection = new Object [ currentSelection . Length + 1 ] ;
279+
280+ currentSelection . CopyTo ( newSelection , 0 ) ;
281+ newSelection [ newSelection . Length - 1 ] = gameObject ;
282+
283+ Selection . objects = newSelection ;
284+ }
285+
286+ private void RemoveObjectFromSelection ( GameObject gameObject )
287+ {
288+ var currentSelection = Selection . objects ;
289+ var newSelection = new Object [ currentSelection . Length - 1 ] ;
290+
291+ var index = 0 ;
292+
293+ for ( int i = 0 ; i < currentSelection . Length ; i ++ )
294+ {
295+ if ( currentSelection [ i ] == gameObject )
296+ {
297+ continue ;
298+ }
299+
300+ newSelection [ index ] = currentSelection [ i ] ;
301+ index ++ ;
302+ }
303+
304+ Selection . objects = newSelection ;
305+ }
306+
307+ private void OnGUIMouseLeave ( )
308+ {
309+ HighlightedObject = null ;
310+ }
311+
312+ private GameObject HighlightedObject
313+ {
314+ set
315+ {
316+ if ( highlightedObject == value )
317+ {
318+ return ;
319+ }
320+
321+ highlightedObject = value ;
322+ UnityEditor . SceneView . RepaintAll ( ) ;
323+
324+ if ( highlightedObject != null )
325+ {
326+ EditorGUIUtility . PingObject ( highlightedObject ) ;
327+ }
328+ }
329+ }
330+
331+ private static class Style
332+ {
333+ internal static readonly GUIStyle buttonStyle ;
334+
335+ static Style ( )
336+ {
337+ buttonStyle = new GUIStyle ( GUI . skin . button ) ;
338+ buttonStyle . alignment = TextAnchor . MiddleLeft ;
339+ }
340+ }
341+ }
342+ }
0 commit comments