Tanoda
ObjectTreeViewer.cs
Go to the documentation of this file.
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Globalization;
5using System.Linq;
11using UnityEngine;
13using UnityEngine.UI;
14using Object = UnityEngine.Object;
15
17{
18 public sealed class ObjectTreeViewer
19 {
20 internal Action<InspectorStackEntryBase[]> InspectorOpenCallback;
21 internal Action<Transform> TreeSelectionChangedCallback;
22
23 private readonly HashSet<GameObject> _openedObjects = new HashSet<GameObject>();
24 private Vector2 _propertiesScrollPosition;
25 private Transform _selectedTransform;
26 private Vector2 _treeScrollPosition;
27 private Rect _windowRect;
28 private float _objectTreeHeight;
29 private int _singleObjectTreeItemHeight;
30 private bool _scrollTreeToSelected;
31 private bool _enabled;
32 private readonly GameObjectSearcher _gameObjectSearcher;
33 private readonly Dictionary<Image, Texture2D> _imagePreviewCache = new Dictionary<Image, Texture2D>();
34 private readonly GUILayoutOption _drawVector3FieldWidth = GUILayout.Width(38);
35 private readonly GUILayoutOption _drawVector3FieldHeight = GUILayout.Height(19);
36 private readonly GUILayoutOption _drawVector3SliderHeight = GUILayout.Height(10);
37 private readonly GUILayoutOption _drawVector3SliderWidth = GUILayout.Width(33);
38 private readonly int _windowId;
39
40 public void SelectAndShowObject(Transform target)
41 {
42 SelectedTransform = target;
43
44 if (target == null) return;
45
46 target = target.parent;
47 while (target != null)
48 {
49 _openedObjects.Add(target.gameObject);
50 target = target.parent;
51 }
52
53 _scrollTreeToSelected = true;
54 Enabled = true;
55 }
56
57 public ObjectTreeViewer(MonoBehaviour pluginObject, GameObjectSearcher gameObjectSearcher)
58 {
59 if (pluginObject == null) throw new ArgumentNullException();
60 if (gameObjectSearcher == null) throw new ArgumentNullException();
61
62 _gameObjectSearcher = gameObjectSearcher;
63 _windowId = GetHashCode();
64
65 pluginObject.StartCoroutine(SetWireframeCo());
66 }
67
68 private bool _wireframe;
69 private bool _actuallyInsideOnGui;
70
71 private IEnumerator SetWireframeCo()
72 {
73 while (true)
74 {
75 yield return null;
76
77 _actuallyInsideOnGui = true;
78
79 yield return new WaitForEndOfFrame();
80
81 if (GL.wireframe != _wireframe)
82 GL.wireframe = _wireframe;
83
84 _actuallyInsideOnGui = false;
85 }
86 }
87
88 public bool Enabled
89 {
90 get
91 {
92 return _enabled;
93 }
94
95 set
96 {
97 if (value && !_enabled)
99
100 _enabled = value;
101 }
102 }
103
104 public Transform SelectedTransform
105 {
106 get
107 {
108 return _selectedTransform;
109 }
110
111 set
112 {
113 if (_selectedTransform != value)
114 {
115 _selectedTransform = value;
116 _searchTextComponents = "";
117 if(TreeSelectionChangedCallback != null) TreeSelectionChangedCallback.Invoke(_selectedTransform);
118 }
119 }
120 }
121
122 public void ClearCaches()
123 {
124 _imagePreviewCache.Clear();
125 }
126
127 private void OnInspectorOpen(params InspectorStackEntryBase[] items)
128 {
129 InspectorOpenCallback.Invoke(items);
130 }
131
132 public void UpdateWindowSize(Rect windowRect)
133 {
134 _windowRect = windowRect;
135 _objectTreeHeight = _windowRect.height / 3;
136 }
137
138 private void DisplayObjectTreeHelper(GameObject go, int indent, ref int currentCount)
139 {
140 currentCount++;
141
142 var needsHeightMeasure = _singleObjectTreeItemHeight == 0;
143
144 var isVisible = currentCount * _singleObjectTreeItemHeight >= _treeScrollPosition.y &&
145 (currentCount - 1) * _singleObjectTreeItemHeight <= _treeScrollPosition.y + _objectTreeHeight;
146
147 if (needsHeightMeasure || isVisible)
148 {
149 var c = GUI.color;
150 if (SelectedTransform == go.transform)
151 {
152 GUI.color = Color.cyan;
153 if (_scrollTreeToSelected && Event.current.type == EventType.Repaint)
154 {
155 _scrollTreeToSelected = false;
156 _treeScrollPosition.y = GUILayoutUtility.GetLastRect().y - 50;
157 }
158 }
159 else if (!go.activeSelf)
160 {
161 GUI.color = new Color(1, 1, 1, 0.6f);
162 }
163
164 GUILayout.BeginHorizontal();
165 {
166 GUILayout.Space(indent * 20f);
167
168 GUILayout.BeginHorizontal();
169 {
170 if (go.transform.childCount != 0)
171 {
172 if (GUILayout.Toggle(_openedObjects.Contains(go), "", GUILayout.ExpandWidth(false)))
173 _openedObjects.Add(go);
174 else
175 _openedObjects.Remove(go);
176 }
177 else
178 {
179 GUILayout.Space(20f);
180 }
181
182 if (GUILayout.Button(go.name, GUI.skin.label, GUILayout.ExpandWidth(true), GUILayout.MinWidth(200)))
183 {
184 if (SelectedTransform == go.transform)
185 {
186 // Toggle on/off
187 if (!_openedObjects.Add(go))
188 _openedObjects.Remove(go);
189 }
190 else
191 {
192 SelectedTransform = go.transform;
193 }
194 }
195
196 GUI.color = c;
197 }
198 GUILayout.EndHorizontal();
199 }
200 GUILayout.EndHorizontal();
201
202 if (needsHeightMeasure && Event.current.type == EventType.Repaint)
203 _singleObjectTreeItemHeight = Mathf.CeilToInt(GUILayoutUtility.GetLastRect().height);
204 }
205 else
206 {
207 GUILayout.Space(_singleObjectTreeItemHeight);
208 }
209
210 if (_openedObjects.Contains(go))
211 {
212 for (var i = 0; i < go.transform.childCount; ++i)
213 DisplayObjectTreeHelper(go.transform.GetChild(i).gameObject, indent + 1, ref currentCount);
214 }
215 }
216
217 public void DisplayViewer()
218 {
219 if (_wireframe && _actuallyInsideOnGui && Event.current.type == EventType.Layout)
220 GL.wireframe = false;
221
222 if (Enabled)
223 {
224 _windowRect = GUILayout.Window(_windowId, _windowRect, WindowFunc, "Scene Browser - RuntimeUnityEditor v" + RuntimeUnityEditorCore.Version);
225 InterfaceMaker.EatInputInRect(_windowRect);
226 }
227 }
228
229 private void WindowFunc(int id)
230 {
231 GUILayout.BeginVertical();
232 {
233 DisplayObjectTree();
234
235 DisplayControls();
236
237 DisplayObjectProperties();
238 }
239 GUILayout.EndHorizontal();
240
241 GUI.DragWindow();
242 }
243
244 private void DisplayControls()
245 {
246 GUILayout.BeginHorizontal();
247 {
248 GUILayout.BeginHorizontal(GUI.skin.box);
249 {
250 GUILayout.Label("Time", GUILayout.ExpandWidth(false));
251
252 if (GUILayout.Button(">", GUILayout.ExpandWidth(false)))
253 Time.timeScale = 1;
254 if (GUILayout.Button("||", GUILayout.ExpandWidth(false)))
255 Time.timeScale = 0;
256 float newVal = 0.0f;
257 if (float.TryParse(GUILayout.TextField(Time.timeScale.ToString("F2", CultureInfo.InvariantCulture), _drawVector3FieldWidth), NumberStyles.Any, CultureInfo.InvariantCulture, out newVal))
258 {
259 Time.timeScale = newVal;
260 }
261 }
262 GUILayout.EndHorizontal();
263
264 GUILayout.BeginHorizontal(GUI.skin.box);
265 {
266 if (SelectedTransform == null) GUI.enabled = false;
267 if (GUILayout.Button("Dump", GUILayout.ExpandWidth(false)))
268 if (SelectedTransform != null)
269 SceneDumper.DumpObjects(SelectedTransform.gameObject);
270 GUI.enabled = true;
271
272 if (GUILayout.Button("Log", GUILayout.ExpandWidth(false)))
273 UnityFeatureHelper.OpenLog();
274
275 GUILayout.FlexibleSpace();
276
277 _wireframe = GUILayout.Toggle(_wireframe, "Wireframe");
278 }
279 GUILayout.EndHorizontal();
280 }
281 GUILayout.EndHorizontal();
282
283 AssetBundleManagerHelper.DrawButtonIfAvailable();
284
286 }
287
288 private void DisplayObjectProperties()
289 {
290 _propertiesScrollPosition = GUILayout.BeginScrollView(_propertiesScrollPosition, GUI.skin.box);
291 {
292 if (SelectedTransform == null)
293 {
294 GUILayout.Label("No object selected");
295 }
296 else
297 {
298 DrawTransformControls();
299
300 GUILayout.BeginHorizontal();
301 {
302 GUILayout.Label("Search components ", GUILayout.ExpandWidth(false));
303
304 _searchTextComponents = GUILayout.TextField(_searchTextComponents, GUILayout.ExpandWidth(true));
305
306 if (GUILayout.Button("Clear", GUILayout.ExpandWidth(false)))
307 _searchTextComponents = string.Empty;
308 }
309 GUILayout.EndHorizontal();
310
311 foreach (var component in SelectedTransform.GetComponents<Component>())
312 {
313 if (component == null)
314 continue;
315
316 if (!string.IsNullOrEmpty(_searchTextComponents) && !GameObjectSearcher.SearchInComponent(_searchTextComponents, component, false))
317 continue;
318
319 DrawSingleComponent(component);
320 }
321 }
322 }
323 GUILayout.EndScrollView();
324 }
325
326 private void DrawTransformControls()
327 {
328 GUILayout.BeginVertical(GUI.skin.box);
329 {
330 var fullTransfromPath = SelectedTransform.GetFullTransfromPath();
331
332 GUILayout.TextArea(fullTransfromPath, GUI.skin.label);
333
334 GUILayout.BeginHorizontal();
335 {
336 GUILayout.Label(string.Format("Layer {0} ({1})", SelectedTransform.gameObject.layer,
337 LayerMask.LayerToName(SelectedTransform.gameObject.layer)));
338
339 GUILayout.Space(8);
340
341 GUILayout.Toggle(SelectedTransform.gameObject.isStatic, "isStatic");
342
343 SelectedTransform.gameObject.SetActive(GUILayout.Toggle(SelectedTransform.gameObject.activeSelf, "Active", GUILayout.ExpandWidth(false)));
344
345 GUILayout.FlexibleSpace();
346
347 if (GUILayout.Button("Inspect"))
348 OnInspectorOpen(new InstanceStackEntry(SelectedTransform.gameObject, SelectedTransform.gameObject.name));
349
350 if (GUILayout.Button("X"))
351 Object.Destroy(SelectedTransform.gameObject);
352 }
353 GUILayout.EndHorizontal();
354
355 DrawVector3("position", vector3 => SelectedTransform.position = vector3, () => SelectedTransform.position, -5, 5);
356 DrawVector3("localPosition", vector3 => SelectedTransform.localPosition = vector3, () => SelectedTransform.localPosition, -5, 5);
357 DrawVector3("localScale", vector3 => SelectedTransform.localScale = vector3, () => SelectedTransform.localScale, 0.00001f, 5);
358 DrawVector3("eulerAngles", vector3 => SelectedTransform.eulerAngles = vector3, () => SelectedTransform.eulerAngles, 0, 360);
359 DrawVector3("localEuler", vector3 => SelectedTransform.localEulerAngles = vector3, () => SelectedTransform.localEulerAngles, 0, 360);
360 }
361 GUILayout.EndVertical();
362 }
363
364 private void DrawVector3(string name, Action<Vector3> set, Func<Vector3> get, float minVal, float maxVal)
365 {
366 var v3 = get();
367 var v3New = v3;
368
369 GUI.changed = false;
370 GUILayout.BeginHorizontal();
371 {
372 GUILayout.Label(name, GUILayout.ExpandWidth(true), _drawVector3FieldHeight);
373 v3New.x = GUILayout.HorizontalSlider(v3.x, minVal, maxVal, _drawVector3SliderWidth, _drawVector3SliderHeight);
374 float.TryParse(GUILayout.TextField(v3New.x.ToString("F2", CultureInfo.InvariantCulture), _drawVector3FieldWidth, _drawVector3FieldHeight), NumberStyles.Any, CultureInfo.InvariantCulture, out v3New.x);
375 v3New.y = GUILayout.HorizontalSlider(v3.y, minVal, maxVal, _drawVector3SliderWidth, _drawVector3SliderHeight);
376 float.TryParse(GUILayout.TextField(v3New.y.ToString("F2", CultureInfo.InvariantCulture), _drawVector3FieldWidth, _drawVector3FieldHeight), NumberStyles.Any, CultureInfo.InvariantCulture, out v3New.y);
377 v3New.z = GUILayout.HorizontalSlider(v3.z, minVal, maxVal, _drawVector3SliderWidth, _drawVector3SliderHeight);
378 float.TryParse(GUILayout.TextField(v3New.z.ToString("F2", CultureInfo.InvariantCulture), _drawVector3FieldWidth, _drawVector3FieldHeight), NumberStyles.Any, CultureInfo.InvariantCulture, out v3New.z);
379 }
380 GUILayout.EndHorizontal();
381
382 if (GUI.changed && v3 != v3New) set(v3New);
383 }
384
385 private void DrawVector2(string name, Action<Vector2> set, Func<Vector2> get, float minVal, float maxVal)
386 {
387 var vector2 = get();
388 var vector2New = vector2;
389
390 GUI.changed = false;
391 GUILayout.BeginHorizontal();
392 {
393 GUILayout.Label(name, GUILayout.ExpandWidth(true), _drawVector3FieldHeight);
394 vector2New.x = GUILayout.HorizontalSlider(vector2.x, minVal, maxVal, _drawVector3SliderWidth, _drawVector3SliderHeight);
395 float.TryParse(GUILayout.TextField(vector2New.x.ToString("F2", CultureInfo.InvariantCulture), _drawVector3FieldWidth, _drawVector3FieldHeight), NumberStyles.Any, CultureInfo.InvariantCulture, out vector2New.x);
396 vector2New.y = GUILayout.HorizontalSlider(vector2.y, minVal, maxVal, _drawVector3SliderWidth, _drawVector3SliderHeight);
397 float.TryParse(GUILayout.TextField(vector2New.y.ToString("F2", CultureInfo.InvariantCulture), _drawVector3FieldWidth, _drawVector3FieldHeight), NumberStyles.Any, CultureInfo.InvariantCulture, out vector2New.y);
398 }
399 GUILayout.EndHorizontal();
400
401 if (GUI.changed && vector2 != vector2New) set(vector2New);
402 }
403
404 private void DrawSingleComponent(Component component)
405 {
406 GUILayout.BeginHorizontal(GUI.skin.box);
407 {
408 var bh = component as Behaviour;
409 var img = component as Image;
410 var b = component as Slider;
411 var text = component as Text;
412 var r = component as RawImage;
413 var re = component as Renderer;
414 var bu = component as Button;
415 var to = component as Toggle;
416 var rt = component as RectTransform;
417 if (bh != null) bh.enabled = GUILayout.Toggle(bh.enabled, "", GUILayout.ExpandWidth(false));
418
419 if (GUILayout.Button(component.GetType().Name, GUI.skin.label))
420 {
421 OnInspectorOpen(new InstanceStackEntry(component.transform, component.transform.name),
422 new InstanceStackEntry(component, component.GetType().FullName));
423 }
424
425 if (img != null && (img.sprite != null && img.sprite.texture != null))
426 {
427 GUILayout.Label(img.sprite.name);
428 Texture2D tex;
429 if (!_imagePreviewCache.TryGetValue(img, out tex))
430 {
431 try
432 {
433 var newImg = img.sprite.texture.GetPixels(
434 (int)img.sprite.textureRect.x, (int)img.sprite.textureRect.y,
435 (int)img.sprite.textureRect.width,
436 (int)img.sprite.textureRect.height);
437 tex = new Texture2D((int)img.sprite.textureRect.width,
438 (int)img.sprite.textureRect.height);
439 tex.SetPixels(newImg);
440 //todo tex.Resize(0, 0); get proper width
441 tex.Apply();
442 }
443 catch (Exception)
444 {
445 tex = null;
446 }
447
448 _imagePreviewCache.Add(img, tex);
449 }
450
451 if (tex != null)
452 GUILayout.Label(tex);
453 else
454 GUILayout.Label("Can't display texture");
455 }
456 //todo img.sprite.texture.EncodeToPNG() button
457
458 if (b != null)
459 for (var i = 0; i < b.onValueChanged.GetPersistentEventCount(); ++i)
460 GUILayout.Label(ToStringConverter.EventEntryToString(b.onValueChanged, i));
461
462 if (text != null)
463 GUILayout.Label(
464 string.Format("{0} {1} {2} {3} {4} {5} {6}", text.text, text.font, text.fontStyle,
465 text.fontSize, text.alignment, text.resizeTextForBestFit, text.color));
466
467 if (r != null) GUILayout.Label(r.mainTexture);
468
469 GUILayout.Label(re != null && re.material != null
470 ? re.material.shader.name
471 : "[No material]");
472 if (bu != null)
473 {
474 var eventObj = bu.onClick;
475 for (var i = 0; i < eventObj.GetPersistentEventCount(); ++i)
476 GUILayout.Label(ToStringConverter.EventEntryToString(eventObj, i));
477
478 var calls = (IList)eventObj.GetPrivateExplicit<UnityEventBase>("m_Calls").GetPrivate("m_RuntimeCalls");
479 foreach (var call in calls)
480 GUILayout.Label(ToStringConverter.ObjectToString(call.GetPrivate("Delegate")));
481 }
482
483 if (to != null)
484 {
485 var eventObj = to.onValueChanged;
486 for (var i = 0; i < eventObj.GetPersistentEventCount(); ++i)
487 GUILayout.Label(ToStringConverter.EventEntryToString(to.onValueChanged, i));
488
489 var calls = (IList)to.onValueChanged.GetPrivateExplicit<UnityEventBase>("m_Calls").GetPrivate("m_RuntimeCalls");
490 foreach (var call in calls)
491 GUILayout.Label(ToStringConverter.ObjectToString(call.GetPrivate("Delegate")));
492 }
493
494 if (rt)
495 {
496 GUILayout.BeginVertical();
497 {
498 DrawVector2("anchorMin", vector2 => rt.anchorMin = vector2, () => rt.anchorMin, 0, 1);
499 DrawVector2("anchorMax", vector2 => rt.anchorMax = vector2, () => rt.anchorMax, 0, 1);
500 DrawVector2("offsetMin", vector2 => rt.offsetMin = vector2, () => rt.offsetMin, -1000, 1000);
501 DrawVector2("offsetMax", vector2 => rt.offsetMax = vector2, () => rt.offsetMax, -1000, 1000);
502 DrawVector2("sizeDelta", vector2 => rt.sizeDelta = vector2, () => rt.sizeDelta, -1000, 1000);
503 GUILayout.Label("rect " + rt.rect);
504 }
505 GUILayout.EndVertical();
506 }
507
508 GUILayout.FlexibleSpace();
509
510 if (!(component is Transform))
511 {
512 /*if (GUILayout.Button("R"))
513 {
514 var t = component.GetType();
515 var g = component.gameObject;
516
517 IEnumerator RecreateCo()
518 {
519 Object.Destroy(component);
520 yield return null;
521 g.AddComponent(t);
522 }
523
524 Object.FindObjectOfType<CheatTools>().StartCoroutine(RecreateCo());
525 }*/
526
527 if (GUILayout.Button("X"))
528 {
529 Object.Destroy(component);
530 }
531 }
532 }
533 GUILayout.EndHorizontal();
534 }
535
536 private string _searchText = string.Empty;
537 private string _searchTextComponents = string.Empty;
538 private void DisplayObjectTree()
539 {
540 GUILayout.BeginVertical(GUI.skin.box);
541 {
542 DisplayTreeSearchBox();
543
544 _treeScrollPosition = GUILayout.BeginScrollView(_treeScrollPosition,
545 GUILayout.Height(_objectTreeHeight), GUILayout.ExpandWidth(true));
546 {
547 var currentCount = 0;
548 foreach (var rootGameObject in _gameObjectSearcher.GetSearchedOrAllObjects())
549 DisplayObjectTreeHelper(rootGameObject, 0, ref currentCount);
550 }
551 GUILayout.EndScrollView();
552 }
553 GUILayout.EndVertical();
554 }
555
556 private void DisplayTreeSearchBox()
557 {
558 GUILayout.BeginHorizontal();
559 {
560 GUI.SetNextControlName("searchbox");
561 _searchText = GUILayout.TextField(_searchText, GUILayout.ExpandWidth(true));
562
563 if (GUILayout.Button("Clear", GUILayout.ExpandWidth(false)))
564 {
565 _searchText = string.Empty;
566 _gameObjectSearcher.Search(_searchText, false);
568 }
569 }
570 GUILayout.EndHorizontal();
571
572 GUILayout.BeginHorizontal();
573 {
574 if (GUILayout.Button("Search scene"))
575 _gameObjectSearcher.Search(_searchText, false);
576
577 if (Event.current.isKey && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.KeypadEnter) && GUI.GetNameOfFocusedControl() == "searchbox")
578 {
579 _gameObjectSearcher.Search(_searchText, false);
580 Event.current.Use();
581 }
582
583 if (GUILayout.Button("Deep scene"))
584 _gameObjectSearcher.Search(_searchText, true);
585
586 if (GUILayout.Button("Search static"))
587 {
588 if (string.IsNullOrEmpty(_searchText))
589 {
590 RuntimeUnityEditorCore.Logger.Log(LogLevel.Message | LogLevel.Warning, "Can't search for empty string");
591 }
592 else
593 {
594 var matchedTypes = AppDomain.CurrentDomain.GetAssemblies()
595 .SelectMany(Extensions.GetTypesSafe)
596 .Where(x => x.GetSourceCodeRepresentation().Contains(_searchText, StringComparison.OrdinalIgnoreCase));
597
598 var stackEntries = matchedTypes.Select(t => new StaticStackEntry(t, t.FullName)).ToList();
599
600 if (stackEntries.Count == 0)
601 RuntimeUnityEditorCore.Logger.Log(LogLevel.Message | LogLevel.Warning, "No static type names contained the search string");
602 else if (stackEntries.Count == 1)
603 RuntimeUnityEditorCore.Instance.Inspector.Push(stackEntries.Single(), true);
604 else
605 RuntimeUnityEditorCore.Instance.Inspector.Push(new InstanceStackEntry(stackEntries, "Static type search"), true);
606 }
607 }
608 }
609 GUILayout.EndHorizontal();
610 }
611 }
612}
UnityEngine.Component Component
UnityEngine.UI.Button Button
Definition: Pointer.cs:7
RuntimeUnityEditor.Core.LogLevel LogLevel
Definition: RUEInvoker.cs:5
System.Drawing.Image Image
Definition: TestScript.cs:37
UnityEngine.Color Color
Definition: TestScript.cs:32
Keeps track of root gameobjects and allows searching objects in the scene
ObjectTreeViewer(MonoBehaviour pluginObject, GameObjectSearcher gameObjectSearcher)
UnityEngine.Object Object