Skip to content

Commit 491a1ed

Browse files
authored
Merge pull request #113 from Grizmu/feature/tree-line-renderer
[Feature Request] Hierarchy Tree View #99
2 parents 015d5dd + ac7a74f commit 491a1ed

File tree

6 files changed

+229
-13
lines changed

6 files changed

+229
-13
lines changed

Assets/Editor Toolbox/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.12.13 [WIP]
2+
3+
### Changed:
4+
- Hierarchy: Added Tree List renderer, which improves visual identification of parent and child gameobjects
5+
- Fix SceneView settings change events firing when they shouldn't
6+
17
## 0.12.12 [17.06.2024]
28

39
### Changed:

Assets/Editor Toolbox/Editor/Hierarchy/HierarchyItemDataType.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ public enum HierarchyItemDataType
66
Toggle,
77
Tag,
88
Layer,
9-
Script
9+
Script,
10+
TreeLines
1011
}
1112
}

Assets/Editor Toolbox/Editor/Hierarchy/HierarchyPropertyLabel.cs

Lines changed: 181 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Text;
34

45
using UnityEditor;
@@ -15,6 +16,15 @@ public abstract class HierarchyPropertyLabel
1516
{
1617
protected GameObject target;
1718

19+
/// <summary>
20+
/// Does this label draw over the whole item?
21+
/// </summary>
22+
public virtual bool UsesWholeItemRect { get; } = false;
23+
24+
/// <summary>
25+
/// Should this label draw for headers too?
26+
/// </summary>
27+
public virtual bool DrawForHeaders { get; } = false;
1828

1929
public virtual bool Prepare(GameObject target, Rect availableRect)
2030
{
@@ -60,6 +70,8 @@ public static HierarchyPropertyLabel GetPropertyLabel(HierarchyItemDataType data
6070
return new HierarchyLayerLabel();
6171
case HierarchyItemDataType.Script:
6272
return new HierarchyScriptLabel();
73+
case HierarchyItemDataType.TreeLines:
74+
return new HierarchyTreeLinesLabel();
6375
}
6476

6577
return null;
@@ -282,6 +294,159 @@ public override void OnGui(Rect rect)
282294
}
283295
}
284296

297+
private class HierarchyTreeLinesLabel : HierarchyPropertyLabel, IDisposable
298+
{
299+
private const float firstElementWidthOffset = 4.0f;
300+
private const float firstElementXOffset = -45.0f;
301+
private const float startXPosition = 30.0f;
302+
private const float columnSize = 14.0f;
303+
304+
private readonly List<TreeLineLevelRenderer> levelRenderers = new List<TreeLineLevelRenderer>();
305+
private int itemRenderCount = 0;
306+
307+
public HierarchyTreeLinesLabel()
308+
{
309+
EditorApplication.update += ResetItemRenderCount;
310+
}
311+
312+
public void Dispose()
313+
{
314+
EditorApplication.update -= ResetItemRenderCount;
315+
}
316+
317+
public override sealed void OnGui(Rect rect)
318+
{
319+
if (Event.current.type != EventType.Repaint)
320+
{
321+
return;
322+
}
323+
324+
var levels = (int)((rect.x + firstElementXOffset) / columnSize);
325+
326+
if (levels <= 0)
327+
{
328+
return;
329+
}
330+
331+
if (IsFirstRenderedElement)
332+
{
333+
levelRenderers.Clear();
334+
}
335+
336+
itemRenderCount++;
337+
338+
rect.x = startXPosition;
339+
rect.width = columnSize + firstElementWidthOffset;
340+
341+
var targetTransform = target.transform;
342+
var siblingIndex = targetTransform.GetSiblingIndex();
343+
344+
if (levels > levelRenderers.Count)
345+
{
346+
//Initialize missing tree line level render
347+
var startIndex = levelRenderers.Count;
348+
int x;
349+
for (x = startIndex; x < levels; x++)
350+
{
351+
var levelRenderer = new TreeLineLevelRenderer();
352+
levelRenderers.Add(levelRenderer);
353+
}
354+
355+
x--;
356+
357+
Transform transformBuffer = targetTransform;
358+
for (; x >= startIndex; x--)
359+
{
360+
levelRenderers[x].Initialize(transformBuffer);
361+
transformBuffer = transformBuffer.parent;
362+
}
363+
}
364+
365+
Color colorCache = GUI.color;
366+
GUI.color = Color.gray;
367+
368+
int i = 0;
369+
for (; i < (levels - 1); i++)
370+
{
371+
levelRenderers[i].OnGUI(rect, target, siblingIndex, false);
372+
rect.x += columnSize;
373+
}
374+
375+
levelRenderers[i].OnGUI(rect, target, siblingIndex, true);
376+
377+
GUI.color = colorCache;
378+
}
379+
380+
private void ResetItemRenderCount()
381+
{
382+
itemRenderCount = 0;
383+
}
384+
385+
public override sealed bool UsesWholeItemRect => true;
386+
387+
public override sealed bool DrawForHeaders => true;
388+
389+
private bool IsFirstRenderedElement => itemRenderCount == 0;
390+
391+
private class TreeLineLevelRenderer
392+
{
393+
private bool renderedLastLevelGameobject = false;
394+
395+
public void Initialize(Transform transform)
396+
{
397+
var siblingIndex = transform.GetSiblingIndex();
398+
renderedLastLevelGameobject = GetParentChildCount(transform) == (siblingIndex + 1);
399+
}
400+
401+
public void OnGUI(Rect rect, GameObject target, int siblingIndex, bool isCurrentLevel)
402+
{
403+
if (isCurrentLevel)
404+
{
405+
if (GetParentChildCount(target) == (siblingIndex + 1))
406+
{
407+
renderedLastLevelGameobject = true;
408+
EditorGUI.LabelField(rect, Style.elementLast, Style.centreAlignTreeLineStyle);
409+
}
410+
else
411+
{
412+
renderedLastLevelGameobject = false;
413+
EditorGUI.LabelField(rect, Style.elementCross, Style.centreAlignTreeLineStyle);
414+
}
415+
}
416+
else
417+
{
418+
if (!renderedLastLevelGameobject)
419+
{
420+
EditorGUI.LabelField(rect, Style.elementPass, Style.centreAlignTreeLineStyle);
421+
}
422+
}
423+
}
424+
425+
private int GetParentChildCount(Transform transform)
426+
{
427+
var parent = transform.parent;
428+
if (parent != null)
429+
{
430+
return parent.childCount;
431+
}
432+
433+
var scene = transform.gameObject.scene;
434+
return scene.rootCount;
435+
}
436+
437+
private int GetParentChildCount(GameObject gameObject)
438+
{
439+
var parent = gameObject.transform.parent;
440+
if (parent != null)
441+
{
442+
return parent.childCount;
443+
}
444+
445+
var scene = gameObject.scene;
446+
return scene.rootCount;
447+
}
448+
}
449+
}
285450
#endregion
286451

287452
protected static class Style
@@ -292,9 +457,18 @@ protected static class Style
292457
internal static readonly GUIStyle defaultAlignTextStyle;
293458
internal static readonly GUIStyle centreAlignTextStyle;
294459
internal static readonly GUIStyle rightAlignTextStyle;
460+
internal static readonly GUIStyle centreAlignTreeLineStyle;
461+
462+
internal static readonly GUIContent elementLast;
463+
internal static readonly GUIContent elementCross;
464+
internal static readonly GUIContent elementPass;
295465

296466
static Style()
297467
{
468+
elementLast = new GUIContent("└");
469+
elementCross = new GUIContent("├");
470+
elementPass = new GUIContent("│");
471+
298472
defaultAlignTextStyle = new GUIStyle(EditorStyles.miniLabel)
299473
{
300474
#if UNITY_2019_3_OR_NEWER
@@ -307,28 +481,26 @@ static Style()
307481
{
308482
#if UNITY_2019_3_OR_NEWER
309483
fontSize = 9,
310-
#else
311-
fontSize = 8,
312-
#endif
313-
#if UNITY_2019_3_OR_NEWER
314484
alignment = TextAnchor.MiddleCenter
315485
#else
486+
fontSize = 8,
316487
alignment = TextAnchor.UpperCenter
317488
#endif
318489
};
319490
rightAlignTextStyle = new GUIStyle(EditorStyles.miniLabel)
320491
{
321492
#if UNITY_2019_3_OR_NEWER
322493
fontSize = 9,
323-
#else
324-
fontSize = 8,
325-
#endif
326-
#if UNITY_2019_3_OR_NEWER
327494
alignment = TextAnchor.MiddleRight
328495
#else
496+
fontSize = 8,
329497
alignment = TextAnchor.UpperRight
330498
#endif
331499
};
500+
centreAlignTreeLineStyle = new GUIStyle(EditorStyles.miniLabel)
501+
{
502+
fontSize = 18,
503+
};
332504
}
333505
}
334506
}

Assets/Editor Toolbox/Editor/ToolboxEditorHierarchy.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,23 @@ private static void DrawHeaderItemLabel(Rect rect, GameObject gameObject, string
124124
var itemContent = new GUIContent(label, iconContent.image);
125125

126126
EditorGUI.LabelField(rect, itemContent, Style.headerLabelStyle);
127+
128+
var contentRect = rect;
129+
var labelsCount = propertyLabels.Count;
130+
var availableRect = contentRect;
131+
132+
for (var i = 0; i < labelsCount; i++)
133+
{
134+
if (!propertyLabels[i].DrawForHeaders)
135+
{
136+
continue;
137+
}
138+
139+
contentRect = AppendPropertyLabel(propertyLabels[i], gameObject, availableRect);
140+
availableRect.xMax -= contentRect.width;
141+
142+
EditorGUI.DrawRect(new Rect(contentRect.xMin, rect.y, Style.lineWidth, rect.height), Style.lineColor);
143+
}
127144
}
128145

129146
/// <summary>
@@ -167,6 +184,16 @@ private static void DrawDefaultItemLabel(Rect rect, GameObject gameObject, strin
167184

168185
private static Rect AppendPropertyLabel(HierarchyPropertyLabel propertyLabel, GameObject target, Rect availableRect)
169186
{
187+
if (propertyLabel.UsesWholeItemRect)
188+
{
189+
if (propertyLabel.Prepare(target, availableRect))
190+
{
191+
propertyLabel.OnGui(availableRect);
192+
}
193+
194+
return availableRect;
195+
}
196+
170197
//prepare currently used property label
171198
if (propertyLabel.Prepare(target, availableRect, out var width))
172199
{
@@ -275,6 +302,14 @@ internal static void CreateAllowedHierarchyContentCallbacks(params HierarchyItem
275302

276303
internal static void RemoveAllowedHierarchyContentCallbacks()
277304
{
305+
for (int i = 0; i < propertyLabels.Count; i++)
306+
{
307+
if (propertyLabels[i] is IDisposable disposable)
308+
{
309+
disposable.Dispose();
310+
}
311+
}
312+
278313
propertyLabels.Clear();
279314
}
280315

Assets/Editor Toolbox/Editor/ToolboxEditorSettings.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ internal void Validate()
192192
private void OnValidate()
193193
{
194194
//determine if any section was changed within the Editor
195-
var settingsDirty = hierarchySettingsDirty || projectSettingsDirty || inspectorSettingsDirty;
195+
var settingsDirty = hierarchySettingsDirty || projectSettingsDirty || inspectorSettingsDirty || sceneViewSettingsDirty;
196196
if (settingsDirty)
197197
{
198198
//check exactly what settings are changed and apply them
@@ -229,6 +229,7 @@ private void OnValidate()
229229
hierarchySettingsDirty = false;
230230
projectSettingsDirty = false;
231231
inspectorSettingsDirty = false;
232+
sceneViewSettingsDirty = false;
232233
}
233234

234235
#endregion
@@ -464,7 +465,8 @@ private static class Defaults
464465
HierarchyItemDataType.Toggle,
465466
HierarchyItemDataType.Tag,
466467
HierarchyItemDataType.Layer,
467-
HierarchyItemDataType.Script
468+
HierarchyItemDataType.Script,
469+
HierarchyItemDataType.TreeLines
468470
};
469471
}
470472
}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -842,7 +842,7 @@ Each row can contain:
842842
- Tag
843843
- Toggle to enable/disable GameObject
844844
- Icon
845-
845+
- Tree Lines
846846
![inspector](https://github.com/arimger/Unity-Editor-Toolbox/blob/develop/Docs/hierarchy.png)
847847
848848
### Project <a name="project"></a>

0 commit comments

Comments
 (0)