Tanoda
Inspector.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using JetBrains.Annotations;
8using UnityEngine;
9
11{
12 public sealed partial class Inspector
13 {
14 private const int InspectorRecordHeight = 25;
15 private readonly GUILayoutOption[] _inspectorTypeWidth = { GUILayout.Width(170), GUILayout.MaxWidth(170) };
16 private readonly GUILayoutOption[] _inspectorNameWidth = { GUILayout.Width(240), GUILayout.MaxWidth(240) };
17 private readonly GUILayoutOption _inspectorRecordHeight = GUILayout.Height(InspectorRecordHeight);
18 private readonly GUILayoutOption _dnSpyButtonOptions = GUILayout.Width(19);
19 private readonly int _windowId;
20
21 private readonly List<InspectorTab> _tabs = new List<InspectorTab>();
22 private InspectorTab _currentTab;
23 private InspectorTab GetCurrentTab()
24 {
25 return _currentTab ?? (_currentTab = _tabs.FirstOrDefault());
26 }
27
28 private Vector2 _tabScrollPos = Vector2.zero;
29
30 private GUIStyle _alignedButtonStyle;
31 private Rect _inspectorWindowRect;
32
33 private object _currentlyEditingTag;
34 private string _currentlyEditingText;
35 private bool _userHasHitReturn;
36
37 public bool Show { get; set; }
38
39 private bool _focusSearchBox;
40 private const string SearchBoxName = "InspectorFilterBox";
41 private string _searchString = "";
42
43 public string SearchString
44 {
45 get
46 {
47 return _searchString;
48 }
49 // The string can't be null under unity 5.x or we crash
50 set
51 {
52 _searchString = value ?? "";
53 }
54 }
55
56 private static Action<Transform> _treeListShowCallback;
57
58 public Inspector(Action<Transform> treeListShowCallback)
59 {
60 _treeListShowCallback = treeListShowCallback;
61 _windowId = GetHashCode();
62 }
63
64 private void DrawEditableValue(ICacheEntry field, object value, params GUILayoutOption[] layoutParams)
65 {
66 var isBeingEdited = _currentlyEditingTag == field;
67 var text = isBeingEdited ? _currentlyEditingText : ToStringConverter.GetEditValue(field, value);
68 var result = GUILayout.TextField(text, layoutParams);
69
70 if (!Equals(text, result) || isBeingEdited)
71 if (_userHasHitReturn)
72 {
73 _currentlyEditingTag = null;
74 _userHasHitReturn = false;
75 try
76 {
77 ToStringConverter.SetEditValue(field, value, result);
78 }
79 catch (Exception ex)
80 {
81 RuntimeUnityEditorCore.Logger.Log(LogLevel.Error, "[Inspector] Failed to set value - " + ex.Message);
82 }
83 }
84 else
85 {
86 _currentlyEditingText = result;
87 _currentlyEditingTag = field;
88 }
89 }
90
91 private void DrawVariableNameEnterButton(ICacheEntry field)
92 {
93 if (_alignedButtonStyle == null)
94 {
95 _alignedButtonStyle = new GUIStyle(GUI.skin.button)
96 {
97 alignment = TextAnchor.MiddleLeft,
98 wordWrap = true
99 };
100 }
101
102 if (GUILayout.Button(field.Name(), _alignedButtonStyle, _inspectorNameWidth))
103 {
104 var val = field.EnterValue();
105 if (val != null)
106 {
107 var entry = val as InspectorStackEntryBase ?? new InstanceStackEntry(val, field.Name(), field);
108 Push(entry, IsContextClick());
109 }
110 }
111 }
112
113 [Obsolete("Use push and Show instead")]
114 public void InspectorClear() { }
115
116 [Obsolete("Use push instead")]
118 {
119 Push(stackEntry, true);
120 }
121
122 public void Push(InspectorStackEntryBase stackEntry, bool newTab)
123 {
124 _focusSearchBox = true;
125 SearchString = null;
126
127 var tab = GetCurrentTab();
128 if (tab == null || newTab)
129 {
130 tab = new InspectorTab();
131 _tabs.Add(tab);
132 _currentTab = tab;
133 }
134 tab.Push(stackEntry);
135
136 Show = true;
137 }
138
139 private void RemoveTab(InspectorTab tab)
140 {
141 _tabs.Remove(tab);
142 if (_currentTab == tab)
143 _currentTab = null;
144 }
145
146 public object GetInspectedObject()
147 {
148 var tmp = GetCurrentTab();
149 if (tmp == null) return null;
150 var se = tmp.CurrentStackItem as InstanceStackEntry;
151 return se != null ? se.Instance : null;
152 }
153
154 private void InspectorWindow(int id)
155 {
156 try
157 {
158 GUILayout.BeginVertical();
159 {
160 GUILayout.BeginHorizontal();
161 {
162 GUILayout.BeginHorizontal(GUI.skin.box, GUILayout.ExpandWidth(true));
163 {
164 GUILayout.Label("Filter:", GUILayout.ExpandWidth(false));
165
166 GUI.SetNextControlName(SearchBoxName);
167 SearchString = GUILayout.TextField(SearchString, GUILayout.ExpandWidth(true));
168
169 if (_focusSearchBox)
170 {
171 GUI.FocusWindow(id);
172 GUI.FocusControl(SearchBoxName);
173 _focusSearchBox = false;
174 }
175
176 GUILayout.Label("Find:", GUILayout.ExpandWidth(false));
177 foreach (var obj in new[]
178 {
179 new KeyValuePair<object, string>(
180 EditorUtilities.GetInstanceClassScanner().OrderBy(x => x.Name()), "Instances"),
181 new KeyValuePair<object, string>(EditorUtilities.GetComponentScanner().OrderBy(x => x.Name()),
182 "Components"),
183 new KeyValuePair<object, string>(
184 EditorUtilities.GetMonoBehaviourScanner().OrderBy(x => x.Name()), "MonoBehaviours"),
185 new KeyValuePair<object, string>(EditorUtilities.GetTransformScanner().OrderBy(x => x.Name()),
186 "Transforms")
187 // new KeyValuePair<object, string>(GetTypeScanner(CurrentTab.InspectorStack.Peek().GetType()).OrderBy(x=>x.Name()), CurrentTab.InspectorStack.Peek().GetType().ToString()+"s"),
188 })
189 {
190 if (obj.Key == null) continue;
191 if (GUILayout.Button(obj.Value, GUILayout.ExpandWidth(false)))
192 Push(new InstanceStackEntry(obj.Key, obj.Value), true);
193 }
194 }
195 GUILayout.EndHorizontal();
196
197 GUILayout.Space(6);
198
199 GUILayout.BeginHorizontal(GUI.skin.box, GUILayout.Width(160));
200 {
201 if (GUILayout.Button("Help"))
202 Push(InspectorHelpObject.Create(), true);
203 if (GUILayout.Button("Close"))
204 Show = false;
205 }
206 GUILayout.EndHorizontal();
207 }
208 GUILayout.EndHorizontal();
209
210 var currentTab = GetCurrentTab();
211 var defaultGuiColor = GUI.color;
212 if (_tabs.Count >= 2)
213 {
214 _tabScrollPos = GUILayout.BeginScrollView(_tabScrollPos, false, false,
215 GUI.skin.horizontalScrollbar, GUIStyle.none, GUIStyle.none); //, GUILayout.Height(46)
216 {
217 GUILayout.BeginHorizontal(GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));
218 for (var index = 0; index < _tabs.Count; index++)
219 {
220 var tab = _tabs[index];
221
222 if (currentTab == tab)
223 GUI.color = Color.cyan;
224
225 if (GUILayout.Button(
226 string.Format("Tab {0}: {1}", index + 1,
227 LimitStringLengthForPreview(tab != null ? tab.CurrentStackItem != null ? tab.CurrentStackItem.Name : "" : "", 18)), GUILayout.ExpandWidth(false)))
228 {
229 if (IsContextClick())
230 RemoveTab(tab);
231 else
232 _currentTab = tab;
233
234 GUI.color = defaultGuiColor;
235 break;
236 }
237
238 GUI.color = defaultGuiColor;
239 }
240
241 GUILayout.FlexibleSpace();
242 GUI.color = new Color(1, 1, 1, 0.6f);
243 if (GUILayout.Button("Close all"))
244 {
245 _tabs.Clear();
246 _currentTab = null;
247 }
248 GUI.color = defaultGuiColor;
249
250 GUILayout.EndHorizontal();
251 }
252 GUILayout.EndScrollView();
253 }
254
255 if (currentTab != null)
256 {
257 currentTab.InspectorStackScrollPos = GUILayout.BeginScrollView(currentTab.InspectorStackScrollPos, false, false,
258 GUI.skin.horizontalScrollbar, GUIStyle.none, GUIStyle.none); //, GUILayout.Height(46)
259 {
260 GUILayout.BeginHorizontal(GUI.skin.box, GUILayout.ExpandWidth(false), GUILayout.ExpandHeight(false));
261 var stackEntries = currentTab.InspectorStack.Reverse().ToArray();
262 for (var i = 0; i < stackEntries.Length; i++)
263 {
264 var item = stackEntries[i];
265
266 if (i + 1 == stackEntries.Length)
267 GUI.color = Color.cyan;
268
269 if (GUILayout.Button(LimitStringLengthForPreview(item.Name, 90), GUILayout.ExpandWidth(false)))
270 {
271 currentTab.PopUntil(item);
272 GUI.color = defaultGuiColor;
273 return;
274 }
275
276 if (i + 1 < stackEntries.Length)
277 GUILayout.Label(">", GUILayout.ExpandWidth(false));
278
279 GUI.color = defaultGuiColor;
280 }
281 GUILayout.EndHorizontal();
282 }
283 GUILayout.EndScrollView();
284
285 GUILayout.BeginVertical(GUI.skin.box);
286 {
287 GUILayout.BeginHorizontal();
288 {
289 GUILayout.Space(1);
290 GUILayout.Label("Value/return type", GUI.skin.box, _inspectorTypeWidth);
291 GUILayout.Space(2);
292 GUILayout.Label("Member name", GUI.skin.box, _inspectorNameWidth);
293 GUILayout.Space(1);
294 GUILayout.Label("Value", GUI.skin.box, GUILayout.ExpandWidth(true));
295 }
296 GUILayout.EndHorizontal();
297
298 DrawContentScrollView(currentTab);
299 }
300 GUILayout.EndVertical();
301 }
302 else
303 {
304 GUILayout.Label("Nothing to show. Click on objects in the scene browser to open them in a new tab.");
305 GUILayout.Label("Tip: You can right click on a member inside inspector to open in a new tab, and on a tab to close it.");
306 GUILayout.FlexibleSpace();
307 }
308 }
309 GUILayout.EndVertical();
310 }
311 catch (Exception ex)
312 {
313 RuntimeUnityEditorCore.Logger.Log(LogLevel.Error, "[Inspector] GUI crash: " + ex);
314 //CurrentTab?.Pop();
315 }
316
317 GUI.DragWindow();
318 }
319
320 private static string LimitStringLengthForPreview(string name, int maxLetters)
321 {
322 if (name == null) name = "NULL";
323 if (name.Length >= maxLetters) name = name.Substring(0, maxLetters - 2) + "...";
324 return name;
325 }
326
327 private static bool IsContextClick()
328 {
329 return Event.current.button >= 1;
330 }
331
332 private void DrawContentScrollView(InspectorTab tab)
333 {
334 if (tab == null || tab.InspectorStack.Count == 0)
335 {
336 GUILayout.FlexibleSpace();
337 return;
338 }
339
340 var currentItem = tab.CurrentStackItem;
341 currentItem.ScrollPosition = GUILayout.BeginScrollView(currentItem.ScrollPosition);
342 {
343 GUILayout.BeginVertical();
344 {
345 var visibleFields = string.IsNullOrEmpty(SearchString) ?
346 tab.FieldCache :
347 tab.FieldCache.Where(x => x.Name().Contains(SearchString, StringComparison.OrdinalIgnoreCase) || x.TypeName().Contains(SearchString, StringComparison.OrdinalIgnoreCase)).ToList();
348
349 var firstIndex = (int)(currentItem.ScrollPosition.y / InspectorRecordHeight);
350
351 GUILayout.Space(firstIndex * InspectorRecordHeight);
352
353 var currentVisibleCount = (int)(_inspectorWindowRect.height / InspectorRecordHeight) - 4;
354 for (var index = firstIndex; index < Mathf.Min(visibleFields.Count, firstIndex + currentVisibleCount); index++)
355 {
356 var entry = visibleFields[index];
357 try
358 {
359 DrawSingleContentEntry(entry);
360 }
361 catch (ArgumentException)
362 {
363 // Needed to avoid GUILayout: Mismatched LayoutGroup.Repaint crashes on large lists
364 }
365 }
366 try
367 {
368 GUILayout.Space(Mathf.FloorToInt(Mathf.Max(_inspectorWindowRect.height / 2, (visibleFields.Count - firstIndex - currentVisibleCount) * InspectorRecordHeight)));
369 // Fixes layout exploding when searching
370 GUILayout.FlexibleSpace();
371 }
372 catch
373 {
374 // Needed to avoid GUILayout: Mismatched LayoutGroup.Repaint crashes on large lists
375 }
376 }
377 GUILayout.EndVertical();
378 }
379 GUILayout.EndScrollView();
380 }
381
382 private void DrawSingleContentEntry(ICacheEntry entry)
383 {
384 GUILayout.BeginHorizontal(_inspectorRecordHeight);
385 {
386 GUILayout.Label(entry.TypeName(), _inspectorTypeWidth);
387
388 var value = entry.GetValue();
389
390 if (entry.CanEnterValue() || value is Exception)
391 DrawVariableNameEnterButton(entry);
392 else
393 GUILayout.TextArea(entry.Name(), GUI.skin.label, _inspectorNameWidth);
394
395 if (entry.CanSetValue() && ToStringConverter.CanEditValue(entry, value))
396 DrawEditableValue(entry, value, GUILayout.ExpandWidth(true));
397 else
398 GUILayout.TextArea(ToStringConverter.ObjectToString(value), GUI.skin.label, GUILayout.ExpandWidth(true));
399 }
400 GUILayout.EndHorizontal();
401 }
402
403 public void DisplayInspector()
404 {
405 if (!Show) return;
406
407 if (Event.current.isKey && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.KeypadEnter)) _userHasHitReturn = true;
408
409 // Clean up dead tab contents
410 foreach (var tab in _tabs.ToList())
411 {
412 while (tab.InspectorStack.Count > 0 && !tab.InspectorStack.Peek().EntryIsValid())
413 {
414 RuntimeUnityEditorCore.Logger.Log(LogLevel.Message,
415 string.Format("[Inspector] Removed invalid/removed stack object: \"{0}\"",
416 tab.InspectorStack.Peek().Name));
417 tab.Pop();
418 }
419
420 if (tab.InspectorStack.Count == 0) RemoveTab(tab);
421 }
422
423 _inspectorWindowRect = GUILayout.Window(_windowId, _inspectorWindowRect, InspectorWindow, "Inspector");
424 InterfaceMaker.EatInputInRect(_inspectorWindowRect);
425 }
426
427 public void UpdateWindowSize(Rect windowRect)
428 {
429 _inspectorWindowRect = windowRect;
430 }
431 }
432}
RuntimeUnityEditor.Core.LogLevel LogLevel
Definition: RUEInvoker.cs:5
UnityEngine.Color Color
Definition: TestScript.cs:32
object Instance
Inspector(Action< Transform > treeListShowCallback)
Definition: Inspector.cs:58
void Push(InspectorStackEntryBase stackEntry, bool newTab)
Definition: Inspector.cs:122
void InspectorPush(InspectorStackEntryBase stackEntry)
Definition: Inspector.cs:117
void Log(LogLevel logLogLevel, object content)
Definition: ICacheEntry.cs:6
bool CanEnterValue()
bool CanSetValue()
object EnterValue()
Get object that is entered when variable name is clicked in inspector
object GetValue()
string TypeName()
string Name()