-
Notifications
You must be signed in to change notification settings - Fork 144
Expand file tree
/
Copy pathToolboxEditorHierarchy.cs
More file actions
380 lines (329 loc) · 13.8 KB
/
ToolboxEditorHierarchy.cs
File metadata and controls
380 lines (329 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Toolbox.Editor
{
using Toolbox.Editor.Hierarchy;
/// <summary>
/// Static GUI representation for the Hierarchy Overlay.
/// </summary>
[InitializeOnLoad]
public static class ToolboxEditorHierarchy
{
/// <summary>
/// Possible types of the GameObject label.
/// </summary>
internal enum LabelType
{
Empty,
Header,
Default
}
static ToolboxEditorHierarchy()
{
#if UNITY_6000_4_OR_NEWER
EditorApplication.hierarchyWindowItemByEntityIdOnGUI -= OnItemCallback;
EditorApplication.hierarchyWindowItemByEntityIdOnGUI += OnItemCallback;
#else
EditorApplication.hierarchyWindowItemOnGUI -= OnItemCallback;
EditorApplication.hierarchyWindowItemOnGUI += OnItemCallback;
#endif
}
/// <summary>
/// All valid and prepared label drawers for each item.
/// </summary>
private static readonly List<HierarchyPropertyLabel> propertyLabels = new List<HierarchyPropertyLabel>();
/// <summary>
/// Tries to display item label in the Hierarchy Window.
/// </summary>
#if UNITY_6000_4_OR_NEWER
private static void OnItemCallback(EntityId instanceId, Rect rect)
#else
private static void OnItemCallback(int instanceId, Rect rect)
#endif
{
if (!IsOverlayAllowed)
{
return;
}
//use Unity's internal method to determinate the proper GameObject instance
#if UNITY_6000_3_OR_NEWER
var gameObject = EditorUtility.EntityIdToObject(instanceId) as GameObject;
#else
var gameObject = EditorUtility.InstanceIDToObject(instanceId) as GameObject;
#endif
if (gameObject != null)
{
var type = GetLabelType(gameObject, out var label);
//draw label using one of the possible forms
switch (type)
{
case LabelType.Empty:
DrawEmptyItemLabel(rect, gameObject, label);
break;
case LabelType.Header:
DrawHeaderItemLabel(rect, gameObject, label);
break;
case LabelType.Default:
DrawDefaultItemLabel(rect, gameObject, label);
break;
}
}
else
{
DrawSceneHeaderLabel(rect);
}
}
/// <summary>
/// Creates optional information about selected objects using the internal <see cref="Selection"/> class.
/// </summary>
private static void DrawSceneHeaderLabel(Rect rect)
{
if (!ShowSelectionsCount)
{
return;
}
//validate selected objects
var count = Selection.gameObjects?.Length ?? 0;
if (count == 0)
{
return;
}
//draw dedicated label field
EditorGUI.LabelField(rect, count.ToString(), Style.selectLabelStyle);
}
/// <summary>
/// Draws item in the completely raw way.
/// </summary>
private static void DrawEmptyItemLabel(Rect rect, GameObject gameObject, string label)
{
//TODO: draw label without prefix
}
/// <summary>
/// Draws GameObject's as header. Creates separation lines and a proper background.
/// </summary>
private static void DrawHeaderItemLabel(Rect rect, GameObject gameObject, string label)
{
if (Event.current.type == EventType.Repaint)
{
Style.backgroundStyle.Draw(rect, false, false, false, false);
}
EditorGUI.DrawRect(new Rect(rect.x, rect.yMin - Style.lineWidth, rect.width, Style.lineWidth), Style.lineColor);
EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - Style.lineWidth, rect.width, Style.lineWidth), Style.lineColor);
EditorGUI.DrawRect(new Rect(rect.xMax, rect.y, Style.lineWidth, rect.height), Style.lineColor);
EditorGUI.DrawRect(new Rect(rect.xMin, rect.y, Style.lineWidth, rect.height), Style.lineColor);
//try to retrive a content for the provided GameObject
var iconContent = EditorGuiUtility.GetObjectContent(gameObject, typeof(GameObject));
//prepare content for the associated (fixed) label
var itemContent = new GUIContent(label, iconContent.image);
EditorGUI.LabelField(rect, itemContent, Style.headerLabelStyle);
var contentRect = rect;
var labelsCount = propertyLabels.Count;
var availableRect = contentRect;
for (var i = 0; i < labelsCount; i++)
{
if (!propertyLabels[i].DrawForHeaders)
{
continue;
}
contentRect = AppendPropertyLabel(propertyLabels[i], gameObject, availableRect);
availableRect.xMax -= contentRect.width;
EditorGUI.DrawRect(new Rect(contentRect.xMin, rect.y, Style.lineWidth, rect.height), Style.lineColor);
}
}
/// <summary>
/// Creates separation lines and content based on the <see cref="allowedDrawContentCallbacks"/> collection.
/// </summary>
private static void DrawDefaultItemLabel(Rect rect, GameObject gameObject, string label)
{
var contentRect = rect;
var labelsCount = propertyLabels.Count;
EditorGUI.DrawRect(new Rect(contentRect.xMax, rect.y, Style.lineWidth, rect.height), Style.lineColor);
if (labelsCount > 0)
{
var availableRect = contentRect;
for (var i = 0; i < labelsCount; i++)
{
//each property label has to be created in validated (adjusted) area
//depending on previously occupied rect we have to adjust current rect
var propertyLabel = propertyLabels[i];
contentRect = AppendPropertyLabel(propertyLabel, gameObject, availableRect);
if (propertyLabel.UsesWholeItemRect)
{
continue;
}
availableRect.xMax -= contentRect.width;
EditorGUI.DrawRect(new Rect(contentRect.xMin, rect.y, Style.lineWidth, rect.height), Style.lineColor);
}
//additionaly draw tooltip for the rest of the label
ToolboxEditorGui.DrawTooltip(availableRect, label);
}
//draw a horiozntal line but only if it is expected
if (DrawHorizontalLines)
{
if (contentRect.xMin < rect.xMin)
{
rect.xMin = contentRect.xMin;
}
EditorGUI.DrawRect(new Rect(rect.x, rect.yMax - Style.lineWidth, rect.width, Style.lineWidth), Style.lineColor);
}
}
private static Rect AppendPropertyLabel(HierarchyPropertyLabel propertyLabel, GameObject target, Rect availableRect)
{
if (propertyLabel.UsesWholeItemRect)
{
if (propertyLabel.Prepare(target, availableRect))
{
propertyLabel.OnGui(availableRect);
}
return availableRect;
}
//prepare currently used property label
if (propertyLabel.Prepare(target, availableRect, out var width))
{
availableRect.xMin = availableRect.xMax - width;
//draw hierarchy overlay background
if (Event.current.type == EventType.Repaint)
{
Style.backgroundStyle.Draw(availableRect, false, false, false, false);
}
propertyLabel.OnGui(availableRect);
}
return availableRect;
}
/// <summary>
/// Determines valid <see cref="LabelType"/> using GameObject's name property.
/// </summary>
private static LabelType GetLabelType(GameObject gameObject, out string label)
{
var type = LabelType.Default;
var name = gameObject.name;
if (name.StartsWith("#") && name.Length > 1)
{
switch (name[1])
{
case 'e':
name = name.Remove(0, 2);
type = LabelType.Empty;
break;
case 'h':
name = name.Remove(0, 2);
type = LabelType.Header;
break;
}
}
label = name;
return type;
}
#region Methods: Utilities
[MenuItem("GameObject/Editor Toolbox/Hierarchy Header", false, 10)]
private static void CreateHeaderObject(MenuCommand menuCommand)
{
var parentGameObject = menuCommand.context as GameObject;
var headerGameObject = new GameObject();
//hide the redundant transform component
headerGameObject.transform.hideFlags = HideFlags.HideInInspector;
//set proper essentials
headerGameObject.name = "#hHeader";
headerGameObject.layer = 0;
headerGameObject.tag = "EditorOnly";
headerGameObject.isStatic = false;
//ensure it gets reparented if this was a context click(otherwise does nothing) and fix name
GameObjectUtility.SetParentAndAlign(headerGameObject, parentGameObject);
//register the creation in the undo system
Undo.RegisterCreatedObjectUndo(headerGameObject, "Create a Header");
//set proper selection
Selection.activeObject = headerGameObject;
}
[Obsolete]
private static void HandleHeaderObject(UnityEditor.Editor editor)
{
if (editor.targets.Length > 1)
{
return;
}
if (editor.target.name.StartsWith("#h"))
{
var target = editor.target as GameObject;
EditorGUILayout.LabelField("Hierachy Header Object");
editor.serializedObject.Update();
target.tag = "EditorOnly";
editor.serializedObject.ApplyModifiedProperties();
}
}
[Obsolete]
private static void HandleHeaderObject(GameObject gameObject)
{
throw new NotImplementedException();
}
#endregion
internal static void CreatePropertyLabels(params HierarchyItemDataType[] items)
{
foreach (var item in items)
{
var propertyLabel = HierarchyPropertyLabel.GetPropertyLabel(item);
if (propertyLabel == null)
{
continue;
}
propertyLabels.Add(propertyLabel);
}
}
internal static void RemovePropertyLabels()
{
for (int i = 0; i < propertyLabels.Count; i++)
{
if (propertyLabels[i] is IDisposable disposable)
{
disposable.Dispose();
}
}
propertyLabels.Clear();
}
internal static void RepaintHierarchyOverlay() => EditorApplication.RepaintHierarchyWindow();
/// <summary>
/// Determines if <see cref="ToolboxEditorHierarchy"/> can create an additional overlay on the Hierarchy Window.
/// </summary>
internal static bool IsOverlayAllowed { get; set; } = false;
internal static bool DrawHorizontalLines { get; set; } = true;
internal static bool ShowSelectionsCount { get; set; } = true;
internal static bool DrawSeparationLines { get; set; } = true;
private static class Style
{
internal static readonly float lineWidth = 1.0f;
#if UNITY_2019_3_OR_NEWER
internal static readonly Color lineColor = EditorGUIUtility.isProSkin ? new Color(0.15f, 0.15f, 0.15f) : new Color(0.69f, 0.69f, 0.69f);
#else
internal static readonly Color lineColor = EditorGUIUtility.isProSkin ? new Color(0.15f, 0.15f, 0.15f) : new Color(0.59f, 0.59f, 0.59f);
#endif
#if UNITY_2019_3_OR_NEWER
internal static readonly Color labelColor = EditorGUIUtility.isProSkin ? new Color(0.31f, 0.31f, 0.31f) : new Color(0.909f, 0.909f, 0.909f);
#else
internal static readonly Color labelColor = EditorGUIUtility.isProSkin ? new Color(0.22f, 0.22f, 0.22f) : new Color(0.855f, 0.855f, 0.855f);
#endif
internal static readonly GUIStyle headerLabelStyle;
internal static readonly GUIStyle remarkLabelStyle;
internal static readonly GUIStyle selectLabelStyle;
internal static readonly GUIStyle backgroundStyle;
static Style()
{
headerLabelStyle = new GUIStyle(EditorStyles.boldLabel)
{
alignment = TextAnchor.MiddleCenter
};
remarkLabelStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel);
selectLabelStyle = new GUIStyle(EditorStyles.centeredGreyMiniLabel)
{
alignment = TextAnchor.MiddleRight,
contentOffset = new Vector2()
{
x = -2.0f
}
};
backgroundStyle = new GUIStyle();
backgroundStyle.normal.background = EditorGuiUtility.CreateColorTexture(labelColor);
}
}
}
}