using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEngine;
using Object = UnityEngine.Object;
namespace EnhancedHierarchy
{
///
/// Main class, draws hierarchy items.
///
[InitializeOnLoad]
public static partial class EnhancedHierarchy
{
private static MiniLabelProvider[] MiniLabelProviders
{
get { return Preferences.miniLabelProviders; }
}
static EnhancedHierarchy()
{
if (Preferences.DebugEnabled || Preferences.ProfilingEnabled)
{
Utility.EnableFPSCounter();
Utility.ForceUpdateHierarchyEveryFrame();
}
EditorApplication.hierarchyWindowItemOnGUI += SetItemInformation;
EditorApplication.hierarchyWindowItemOnGUI += OnItemGUI;
EditorApplication.RepaintHierarchyWindow();
}
private static void OnItemGUI(int id, Rect rect)
{
if (!Preferences.Enabled)
return;
bool isActivie = EditorPrefs.GetBool("isActiveObjectTool", false);
if (!isActivie)
{
return;
}
using (ProfilerSample.Get("Enhanced Hierarchy"))
try
{
if (IsGameObject)
{
for (var i = 0; i < Preferences.RightIcons.Value.Count; i++)
Preferences.RightIcons.Value[i].SafeInit();
for (var i = 0; i < Preferences.LeftIcons.Value.Count; i++)
Preferences.LeftIcons.Value[i].SafeInit();
Preferences.LeftSideButton.SafeInit();
for (var i = 0; i < MiniLabelProviders.Length; i++)
{
MiniLabelProviders[i].Init();
}
}
if (IsFirstVisible && Reflected.HierarchyArea.Supported)
{
Reflected.HierarchyArea.IndentWidth = Preferences.Indent;
Reflected.HierarchyArea.BaseIndent = Preferences.LeftMargin;
}
//SetTitle("EH 2.0");
CalculateIconsWidth();
DoSelection(RawRect);
IgnoreLockedSelection();
DrawTree(RawRect);
ChildToggle();
var trailingWidth = DoTrailing();
DrawHover();
ColorSort(RawRect);
DrawLeftSideIcons(RawRect);
DrawTooltip(RawRect, trailingWidth);
if (Reflected.IconWidthSupported)
Reflected.IconWidth = Preferences.DisableNativeIcon ? 0 : 16;
if (IsGameObject)
{
rect.xMax -= Preferences.RightMargin;
rect.xMin = rect.xMax;
rect.y++;
for (var i = 0; i < Preferences.RightIcons.Value.Count; i++)
using (new GUIBackgroundColor(Styles.backgroundColorEnabled))
{
var icon = Preferences.RightIcons.Value[i];
rect.xMin -= icon.SafeGetWidth();
icon.SafeDoGUI(rect);
rect.xMax -= icon.SafeGetWidth();
}
var leftSideRect = RawRect;
if (Preferences.LeftmostButton)
leftSideRect.xMin = 0f;
else
leftSideRect.xMin -= 2f + CurrentGameObject.transform.childCount > 0 || Preferences.TreeOpacity > ALPHA_THRESHOLD ? 30f : 18f;
leftSideRect.xMax = leftSideRect.xMin + Preferences.LeftSideButton.SafeGetWidth();
using (new GUIBackgroundColor(Styles.backgroundColorEnabled))
Preferences.LeftSideButton.SafeDoGUI(leftSideRect);
}
DrawMiniLabel(ref rect);
DrawHorizontalSeparator(RawRect);
}
catch (Exception e)
{
Utility.LogException(e);
}
}
private static void DrawHover()
{
if (Reflected.NativeHierarchyHoverTintSupported)
{
if (IsFirstVisible && IsRepaintEvent)
Reflected.NativeHierarchyHoverTint = Preferences.HoverTintColor;
return;
}
var tint = Preferences.HoverTintColor.Value;
if (IsFirstVisible && Reflected.NativeHierarchyHoverTintSupported)
Reflected.HierarchyWindowInstance.wantsMouseMove = tint.a >= ALPHA_THRESHOLD;
if (tint.a < ALPHA_THRESHOLD)
return;
if (!Utility.ShouldCalculateTooltipAt(FullSizeRect))
return;
if (IsRepaintEvent)
EditorGUI.DrawRect(FullSizeRect, tint);
switch (Event.current.type)
{
case EventType.MouseMove:
Event.current.Use();
break;
}
}
private static void IgnoreLockedSelection()
{
if (Preferences.AllowSelectingLockedObjects || !IsFirstVisible || !IsRepaintEvent)
return;
using (ProfilerSample.Get())
{
var selection = Selection.objects;
var changed = false;
for (var i = 0; i < selection.Length; i++)
if (selection[i] is GameObject && (selection[i].hideFlags & HideFlags.NotEditable) != 0 && !EditorUtility.IsPersistent(selection[i]))
{
selection[i] = null;
changed = true;
}
if (changed)
{
Selection.objects = selection;
Reflected.SetHierarchySelectionNeedSync();
EditorApplication.RepaintHierarchyWindow();
}
}
}
private static void ChildToggle()
{
using (ProfilerSample.Get())
{
if (!Preferences.NumericChildExpand || !IsRepaintEvent || !IsGameObject || CurrentGameObject.transform.childCount <= 0)
return;
var rect = RawRect;
var childString = CurrentGameObject.transform.childCount.ToString("00");
var expanded = Reflected.GetTransformIsExpanded(CurrentGameObject);
rect.xMax = rect.xMin - 1f;
rect.xMin -= 15f;
if (childString.Length > 2)
rect.xMin -= 4f;
using (new GUIBackgroundColor(Styles.childToggleColor))
Styles.newToggleStyle.Draw(rect, Utility.GetTempGUIContent(childString), false, false, expanded, false);
}
}
private static void DrawHorizontalSeparator(Rect rect)
{
if (Preferences.LineSize < 1 || Preferences.LineColor.Value.a <= ALPHA_THRESHOLD || !IsRepaintEvent)
return;
using (ProfilerSample.Get())
{
rect.xMin = 0f;
rect.xMax = rect.xMax + 50f;
rect.yMin -= Preferences.LineSize / 2;
rect.yMax = rect.yMin + Preferences.LineSize;
EditorGUI.DrawRect(rect, Preferences.LineColor);
if (!IsFirstVisible)
return;
rect.y = FinalRect.y - Preferences.LineSize / 2;
var height = Reflected.HierarchyWindowInstance.position.height;
var count = (height - FinalRect.y) / FinalRect.height;
if (FinalRect.height <= 0f)
count = 100f;
for (var i = 0; i < count; i++)
{
rect.y += RawRect.height;
EditorGUI.DrawRect(rect, Preferences.LineColor);
}
}
}
private static void ColorSort(Rect rect)
{
if (!IsRepaintEvent)
return;
using (ProfilerSample.Get())
{
rect.xMin = 0f;
rect.xMax = rect.xMax + 50f;
var rowTint = GetRowTint();
var rowCustomTint = GetRowCustomTint();
if (rowCustomTint.color.a > ALPHA_THRESHOLD)
using (new GUIColor(rowCustomTint.color))
{
switch (rowCustomTint.mode)
{
case TintMode.Flat:
EditorGUI.DrawRect(rect, Color.white);
break;
case TintMode.GradientLeftToRight:
GUI.DrawTexture(Utility.FlipRectHorizontally(rect), Styles.fadeTexture, ScaleMode.StretchToFill);
break;
case TintMode.GradientRightToLeft:
GUI.DrawTexture(rect, Styles.fadeTexture, ScaleMode.StretchToFill);
break;
}
}
if (rowTint.a > ALPHA_THRESHOLD)
EditorGUI.DrawRect(rect, rowTint);
if (!IsFirstVisible)
return;
rect.y = FinalRect.y;
var height = Reflected.HierarchyWindowInstance.position.height;
var count = (height - FinalRect.y) / FinalRect.height;
if (FinalRect.height <= 0f)
count = 100f;
for (var i = 0; i < count; i++)
{
rect.y += RawRect.height;
rowTint = GetRowTint(rect);
if (rowTint.a > ALPHA_THRESHOLD)
EditorGUI.DrawRect(rect, rowTint);
}
}
}
private static void DrawTree(Rect rect)
{
if (Preferences.TreeOpacity <= ALPHA_THRESHOLD || !IsGameObject)
return;
if (!IsRepaintEvent && !Preferences.SelectOnTree)
return;
using (ProfilerSample.Get())
using (new GUIColor(Utility.GetHierarchyColor(CurrentGameObject.transform.parent), Preferences.TreeOpacity))
{
var indent = Reflected.HierarchyArea.Supported ? Reflected.HierarchyArea.IndentWidth : 16f;
// #42 - Jules: pull back indent one level and neatly align tree lines with expansion arrow tips
rect.x -= indent + 2f;
rect.xMin -= 14f;
rect.xMax = rect.xMin + 14f;
// #42 - Jules: allow showing of stems for container objects
if (CurrentGameObject.transform.parent)
{
var lastInHierarchy = Utility.TransformIsLastChild(CurrentGameObject.transform);
GUI.DrawTexture(rect, lastInHierarchy ? Styles.treeElbowTexture : Styles.treeTeeTexture);
/*
#42 - Jules: add short horizontal line to extend stem if there's no expansion triangle.
NOTE: Please make this value into a slider in the preferences, since it's arguably a bad thing to have it.
It looks "nicer" with extended stems but is less clear since it throws off the stem alignments.
At a value of 1 this can make things look an entire level off of where they truly are relative to other things!!!
I think 0.5 is an okay compromise, but 0 is the most consistent, which means no extra stem line at all.
So it's the sort of thing where people might have different ideas of what's best for them...
*/
var extendStemProportion = CurrentGameObject.transform.childCount == 0 ? Preferences.TreeStemProportion.Value * indent : indent - 14f;
if (extendStemProportion > 0.01f)
{
var extendedStemRect = new Rect(rect.x + rect.size.x, rect.y + (lastInHierarchy ? 9f : 8f), extendStemProportion, 1f);
EditorGUI.DrawRect(extendedStemRect, Color.white);
}
if (Preferences.SelectOnTree && GUI.Button(rect, GUIContent.none, Styles.labelNormal))
Selection.activeTransform = CurrentGameObject.transform.parent;
}
var currentTransform = CurrentGameObject.transform.parent;
for (rect.x -= indent; rect.xMin > 0f && currentTransform && currentTransform.parent; rect.x -= indent)
{
if (!Utility.TransformIsLastChild(currentTransform))
using (new GUIColor(Utility.GetHierarchyColor(currentTransform.parent), Preferences.TreeOpacity))
{
GUI.DrawTexture(rect, Styles.treeLineTexture);
if (Preferences.SelectOnTree && GUI.Button(rect, GUIContent.none, Styles.labelNormal))
Selection.activeTransform = currentTransform.parent;
}
currentTransform = currentTransform.parent;
}
}
}
private static void CalculateIconsWidth()
{
using (ProfilerSample.Get())
{
LeftIconsWidth = 0f;
RightIconsWidth = 0f;
if (!IsGameObject)
return;
for (var i = 0; i < Preferences.RightIcons.Value.Count; i++)
RightIconsWidth += Preferences.RightIcons.Value[i].SafeGetWidth();
for (var i = 0; i < Preferences.LeftIcons.Value.Count; i++)
LeftIconsWidth += Preferences.LeftIcons.Value[i].SafeGetWidth();
}
}
private static void DrawLeftSideIcons(Rect rect)
{
if (!IsGameObject)
return;
using (ProfilerSample.Get())
{
rect.xMin += LabelSize;
rect.xMin = Math.Min(rect.xMax - RightIconsWidth - LeftIconsWidth - CalcMiniLabelSize() - 5f - Preferences.RightMargin, rect.xMin);
for (var i = 0; i < Preferences.LeftIcons.Value.Count; i++)
using (new GUIBackgroundColor(Styles.backgroundColorEnabled))
{
var icon = Preferences.LeftIcons.Value[i];
rect.xMax = rect.xMin + icon.SafeGetWidth();
icon.SafeDoGUI(rect);
rect.xMin = rect.xMax;
}
}
}
private static float DoTrailing()
{
if (!IsRepaintEvent || !Preferences.Trailing || !IsGameObject)
return RawRect.xMax;
using (ProfilerSample.Get())
{
var size = LabelSize; // CurrentStyle.CalcSize(Utility.GetTempGUIContent(GameObjectName)).x;
var iconsWidth = RightIconsWidth + LeftIconsWidth + CalcMiniLabelSize() + Preferences.RightMargin;
var iconsMin = FullSizeRect.xMax - iconsWidth;
var labelMax = LabelOnlyRect.xMax;
var overlapping = iconsMin <= labelMax;
if (!overlapping)
return labelMax;
var rect = FullSizeRect;
rect.xMin = iconsMin - 18;
rect.xMax = labelMax;
if (Selection.gameObjects.Contains(CurrentGameObject))
EditorGUI.DrawRect(rect, Reflected.HierarchyFocused ? Styles.selectedFocusedColor : Styles.selectedUnfocusedColor);
else
EditorGUI.DrawRect(rect, Styles.normalColor);
rect.y++;
using (new GUIColor(CurrentColor))
EditorStyles.boldLabel.Draw(rect, trailingContent, 0);
return iconsMin;
}
}
private static void DrawMiniLabel(ref Rect rect)
{
if (!IsGameObject)
return;
rect.x -= 3f;
using (ProfilerSample.Get())
switch (MiniLabelProviders.Length)
{
case 0:
return;
case 1:
if (MiniLabelProviders[0].HasValue())
MiniLabelProviders[0].Draw(ref rect);
break;
default:
var ml0 = MiniLabelProviders[0];
var ml1 = MiniLabelProviders[1];
var ml0HasValue = ml0.HasValue();
var ml1HasValue = ml1.HasValue();
if (ml0HasValue && ml1HasValue || !Preferences.CentralizeMiniLabelWhenPossible)
{
var topRect = rect;
var bottomRect = rect;
topRect.yMax = RawRect.yMax - RawRect.height / 2f;
bottomRect.yMin = RawRect.yMin + RawRect.height / 2f;
if (ml0HasValue)
ml0.Draw(ref topRect);
if (ml1HasValue)
ml1.Draw(ref bottomRect);
rect.xMin = Mathf.Min(topRect.xMin, bottomRect.xMin);
}
else if (ml1HasValue)
ml1.Draw(ref rect);
else if (ml0HasValue)
ml0.Draw(ref rect);
break;
}
}
private static float CalcMiniLabelSize()
{
Styles.miniLabelStyle.fontSize = Preferences.SmallerMiniLabel ? 8 : 9;
using (ProfilerSample.Get())
{
switch (MiniLabelProviders.Length)
{
case 0:
return 0f;
case 1:
return MiniLabelProviders[0].Measure();
default:
return Math.Max(
MiniLabelProviders[0].Measure(),
MiniLabelProviders[1].Measure()
);
}
}
}
private static void DrawTooltip(Rect rect, float fullTrailingWidth)
{
if (!Preferences.Tooltips || !IsGameObject || !IsRepaintEvent)
return;
using (ProfilerSample.Get())
{
if (DragSelection != null)
return;
rect.xMax = Mathf.Min(fullTrailingWidth, rect.xMin + LabelSize);
rect.xMin = 0f;
if (!Utility.ShouldCalculateTooltipAt(rect))
return;
var tooltip = new StringBuilder(100);
tooltip.AppendLine(GameObjectName);
tooltip.AppendFormat("\nTag: {0}", GameObjectTag);
tooltip.AppendFormat("\nLayer: {0}", LayerMask.LayerToName(CurrentGameObject.layer));
if (GameObjectUtility.GetStaticEditorFlags(CurrentGameObject) != 0)
tooltip.AppendFormat("\nStatic: {0}", Utility.EnumFlagsToString(GameObjectUtility.GetStaticEditorFlags(CurrentGameObject)));
tooltip.AppendLine();
tooltip.AppendLine();
foreach (var component in Components)
if (component is Transform)
continue;
else if (component)
tooltip.AppendLine(ObjectNames.GetInspectorTitle(component));
else
tooltip.AppendLine("Missing Component");
EditorGUI.LabelField(rect, Utility.GetTempGUIContent(null, tooltip.ToString().TrimEnd('\n', '\r')));
}
}
private static void DoSelection(Rect rect)
{
if (!Preferences.EnhancedSelectionSupported || !Preferences.EnhancedSelection || Event.current.button != 1)
{
DragSelection = null;
return;
}
using (ProfilerSample.Get())
{
rect.xMin = 0f;
switch (Event.current.type)
{
case EventType.MouseDrag:
if (!IsFirstVisible)
return;
if (DragSelection == null)
{
DragSelection = new List