using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace EnhancedHierarchy {
public enum TintMode {
Flat = 0,
GradientRightToLeft = 1,
GradientLeftToRight = 2,
}
///
/// Per layer color setting.
///
[Serializable]
public struct LayerColor {
[SerializeField]
public int layer;
[SerializeField]
public Color color;
[SerializeField]
public TintMode mode;
public LayerColor(int layer) : this(layer, Color.clear) { }
public LayerColor(int layer, Color color, TintMode mode = TintMode.GradientRightToLeft) {
this.layer = layer;
this.color = color;
this.mode = mode;
}
public static implicit operator LayerColor(int layer) {
return new LayerColor(layer);
}
public static bool operator ==(LayerColor left, LayerColor right) {
return left.layer == right.layer;
}
public static bool operator !=(LayerColor left, LayerColor right) {
return left.layer != right.layer;
}
public override bool Equals(object obj) {
if (!(obj is LayerColor))
return false;
return ((LayerColor)obj).layer == layer;
}
public override int GetHashCode() {
return layer.GetHashCode();
}
}
///
/// Save and load hierarchy preferences.
///
public static partial class Preferences {
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
private class AutoPrefItemAttribute : Attribute {
public string Key { get; private set; }
public AutoPrefItemAttribute(string key = null) { Key = key; }
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
private class AutoPrefItemDefaultValueAttribute : Attribute {
public object DefaultValue { get; private set; }
public AutoPrefItemDefaultValueAttribute(object defaultValue) { DefaultValue = defaultValue; }
}
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
private class AutoPrefItemLabelAttribute : Attribute {
public GUIContent Label { get; private set; }
public AutoPrefItemLabelAttribute(string label, string tooltip = null) { Label = new GUIContent(label, tooltip); }
}
private static Color DefaultOddSortColor { get { return EditorGUIUtility.isProSkin ? new Color(0f, 0f, 0f, 0.1f) : new Color(1f, 1f, 1f, 0.2f); } }
private static Color DefaultEvenSortColor { get { return EditorGUIUtility.isProSkin ? new Color(0f, 0f, 0f, 0f) : new Color(1f, 1f, 1f, 0f); } }
private static Color DefaultLineColor { get { return new Color(0f, 0f, 0f, 0.2f); } }
private static Color DefaultHoverTint { get { return EditorGUIUtility.isProSkin ? new Color(0f, 0f, 0f, 0.2f) : new Color(0.12f, 0.12f, 0.12f, 0.2f); } }
#region PrefItems
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Enabled", "Enable or disable the entire plugin, it will be automatically disabled if any error occurs")]
public static PrefItem Enabled;
[AutoPrefItem]
[AutoPrefItemDefaultValue(0)]
[AutoPrefItemLabel("Right Margin", "Margin for icons, useful if you have more extensions that also uses hierarchy")]
public static PrefItem RightMargin;
[AutoPrefItem]
[AutoPrefItemDefaultValue(0)]
[AutoPrefItemLabel("Left Margin", "Margin for icons, useful if you have more extensions that also uses hierarchy")]
public static PrefItem LeftMargin;
[AutoPrefItem]
[AutoPrefItemDefaultValue(14)]
[AutoPrefItemLabel("Indent", "Indent for labels, useful for thin hierarchies")]
public static PrefItem Indent;
[AutoPrefItem]
[AutoPrefItemDefaultValue(0.8f)]
[AutoPrefItemLabel("Hierarchy Tree Opacity", "The opacity of the tree view lines connecting child transforms to their parent, useful if you have multiple children inside children")]
public static PrefItem TreeOpacity;
[AutoPrefItem]
[AutoPrefItemDefaultValue(0.5f)]
[AutoPrefItemLabel("Stem Proportion", "Stem length for hierarchy items that have no children")]
public static PrefItem TreeStemProportion;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Select on Tree", "Select the parent when you click on the tree lines\n\nTHIS MAY AFFECT PERFORMANCE")]
public static PrefItem SelectOnTree;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Tooltips", "Shows tooltips, like this one")]
public static PrefItem Tooltips;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Relevant Tooltips Only", "Hide tooltips that have static texts")]
public static PrefItem RelevantTooltipsOnly;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Enhanced selection", "Allow selecting GameObjects by dragging over them with right mouse button")]
public static PrefItem EnhancedSelection;
[AutoPrefItem]
[AutoPrefItemLabel("Highlight tint", "Tint the item under the mouse cursor")]
public static PrefItem HoverTintColor;
[AutoPrefItem]
[AutoPrefItemDefaultValue(false)]
[AutoPrefItemLabel("Hide native icon", "Hide the native icon on the left side of the name, introducted in Unity 2018.3")]
public static PrefItem DisableNativeIcon;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Trailing", "Append ... when names are bigger than the view area")]
public static PrefItem Trailing;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Select locked objects", "Allow selecting objects that are locked")]
public static PrefItem AllowSelectingLockedObjects;
[AutoPrefItem]
[AutoPrefItemDefaultValue(false)]
[AutoPrefItemLabel("Pick locked objects", "Allow picking objects that are locked on scene view\nObjects locked before you change this option will not be affected\nRequires Unity 2019.3 or newer")]
public static PrefItem AllowPickingLockedObjects;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Change all selected", "This will make the enable, lock, layer, tag and static buttons affect all selected objects in the hierarchy")]
public static PrefItem ChangeAllSelected;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Left side button at leftmost", "Put the left button to the leftmost side of the hierarchy, if disabled it will be next to the game object name")]
public static PrefItem LeftmostButton;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Open scripts of logs", "Clicking on warnings, logs or errors will open the script to edit in your IDE or text editor\n\nMAY AFFECT PERFORMANCE")]
public static PrefItem OpenScriptsOfLogs;
[AutoPrefItem]
[AutoPrefItemDefaultValue(false)]
[AutoPrefItemLabel("Replace default child toggle", "Replace the default toggle for expanding children to a new one that shows the children count")]
public static PrefItem NumericChildExpand;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Smaller font", "Use a smaller font on the minilabel for narrow hierarchies")]
public static PrefItem SmallerMiniLabel;
[AutoPrefItem]
[AutoPrefItemDefaultValue(15)]
[AutoPrefItemLabel("Icons Size", "The size of the icons in pixels")]
public static PrefItem IconsSize;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Centralize when possible", "Centralize minilabel when there's only tag or only layer on it")]
public static PrefItem CentralizeMiniLabelWhenPossible;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Hide \"Untagged\" tag", "Hide default tag on minilabel")]
public static PrefItem HideDefaultTag;
[AutoPrefItem]
[AutoPrefItemDefaultValue(true)]
[AutoPrefItemLabel("Hide \"Default\" layer", "Hide default layer on minilabel")]
public static PrefItem HideDefaultLayer;
[AutoPrefItem]
[AutoPrefItemDefaultValue(false)]
[AutoPrefItemLabel("Hide default icon", "Hide the default game object icon")]
public static PrefItem HideDefaultIcon;
[AutoPrefItem]
[AutoPrefItemDefaultValue(1)]
[AutoPrefItemLabel("Line thickness", "Separator line thickness")]
public static PrefItem LineSize;
[AutoPrefItem]
[AutoPrefItemLabel("Odd row tint", "The tint of odd rows")]
public static PrefItem OddRowColor;
[AutoPrefItem]
[AutoPrefItemLabel("Even row tint", "The tint of even rows")]
public static PrefItem EvenRowColor;
[AutoPrefItem]
[AutoPrefItemLabel("Line tint", "The tint of separators line")]
public static PrefItem LineColor;
[AutoPrefItem]
[AutoPrefItemLabel("Left side button", "The button that will appear in the left side of the hierarchy\nLooks better with \"Hierarchy Tree\" disabled")]
public static PrefItem LeftSideButtonPref;
[AutoPrefItem]
[AutoPrefItemLabel("Mini label", "The little label next to the GameObject name")]
public static PrefItem MiniLabels;
[AutoPrefItem]
[AutoPrefItemDefaultValue(ChildrenChangeMode.ObjectAndChildren)]
[AutoPrefItemLabel("Lock", "Which objects will be locked when you click on the lock toggle")]
public static PrefItem LockAskMode;
[AutoPrefItem]
[AutoPrefItemDefaultValue(ChildrenChangeMode.Ask)]
[AutoPrefItemLabel("Layer", "Which objects will have their layer changed when you click on the layer button or on the mini label")]
public static PrefItem LayerAskMode;
[AutoPrefItem]
[AutoPrefItemDefaultValue(ChildrenChangeMode.ObjectOnly)]
[AutoPrefItemLabel("Tag", "Which objects will have their tag changed when you click on the tag button or on the mini label")]
public static PrefItem TagAskMode;
[AutoPrefItem]
[AutoPrefItemDefaultValue(ChildrenChangeMode.Ask)]
[AutoPrefItemLabel("Static", "Which flags will be changed when you click on the static toggle")]
public static PrefItem StaticAskMode;
[AutoPrefItem]
[AutoPrefItemDefaultValue(ChildrenChangeMode.ObjectOnly)]
[AutoPrefItemLabel("Icon", "Which objects will have their icon changed when you click on the icon button")]
public static PrefItem IconAskMode;
[AutoPrefItem]
[AutoPrefItemLabel("Icons next to the object name", "The icons that appear next to the game object name")]
public static PrefItem LeftIcons;
[AutoPrefItem]
[AutoPrefItemLabel("Icons on the rightmost", "The icons that appear to the rightmost of the hierarchy")]
public static PrefItem RightIcons;
[AutoPrefItem]
[AutoPrefItemLabel("Per layer row color", "Set a row color for each different layer")]
public static PrefItem> PerLayerRowColors;
#endregion
public static MiniLabelProvider[] miniLabelProviders;
public static IconBase LeftSideButton {
get { return LeftSideButtonPref.Value.Icon; }
set {
LeftSideButtonPref.Value.Icon = value;
LeftSideButtonPref.ForceSave();
}
}
public static bool ProfilingEnabled {
get {
#if HIERARCHY_PROFILING
return true;
#else
return false;
#endif
}
}
public static bool DebugEnabled {
get {
#if HIERARCHY_DEBUG
return true;
#else
return false;
#endif
}
}
public static bool MiniLabelTagEnabled {
get { return miniLabelProviders.Any(ml => ml is TagMiniLabel); }
}
public static bool MiniLabelLayerEnabled {
get { return miniLabelProviders.Any(ml => ml is LayerMiniLabel); }
}
public static bool EnhancedSelectionSupported {
get { return Application.platform == RuntimePlatform.WindowsEditor; }
}
static Preferences() {
InitializePreferences();
Enabled.Label.text = string.Format("Enabled ({0}+H)", Utility.CtrlKey);
#if UNITY_2018_3_OR_NEWER
LeftSideButtonPref.DefaultValue = new IconData() { Icon = new Icons.None() };
#else
LeftSideButtonPref.DefaultValue = new IconData() { Icon = new Icons.GameObjectIcon() };
#endif
LineColor.DefaultValue = DefaultLineColor;
OddRowColor.DefaultValue = DefaultOddSortColor;
EvenRowColor.DefaultValue = DefaultEvenSortColor;
HoverTintColor.DefaultValue = DefaultHoverTint;
var defaultLeftIcons = new IconList { new Icons.MonoBehaviourIcon(), new Icons.Warnings(), new Icons.SoundIcon() };
var defaultRightIcons = new IconList { new Icons.Active(), new Icons.Lock(), new Icons.Static(), new Icons.PrefabApply() };
var defaultLayerColors = new List { new LayerColor(5, new Color(0f, 0f, 1f, 0.3019608f)) };
LeftIcons.DefaultValue = defaultLeftIcons;
RightIcons.DefaultValue = defaultRightIcons;
PerLayerRowColors.DefaultValue = defaultLayerColors;
MiniLabels.DefaultValue = new [] {
Array.IndexOf(MiniLabelProvider.MiniLabelsTypes, typeof(LayerMiniLabel)),
Array.IndexOf(MiniLabelProvider.MiniLabelsTypes, typeof(TagMiniLabel))
};
minilabelsNames = MiniLabelProvider.MiniLabelsTypes
.Select(ml => ml == null? "None": ObjectNames.NicifyVariableName(ml.Name.Replace("MiniLabel", "")))
.ToArray();
leftIconsList = GenerateReordableList(LeftIcons);
rightIconsList = GenerateReordableList(RightIcons);
leftIconsList.onAddDropdownCallback = (rect, newList) => LeftIconsMenu.DropDown(rect);
rightIconsList.onAddDropdownCallback = (rect, newList) => RightIconsMenu.DropDown(rect);
rowColorsList = GenerateReordableList(PerLayerRowColors);
rowColorsList.onAddDropdownCallback = (rect, newList) => RowColorsMenu.DropDown(rect);
rowColorsList.drawElementCallback = (rect, index, focused, active) => {
GUI.changed = false;
rect.xMin -= EditorGUI.indentLevel * 16f;
PerLayerRowColors.Value[index] = LayerColorField(rect, PerLayerRowColors.Value[index]);
if (GUI.changed)
PerLayerRowColors.ForceSave();
};
RecreateMiniLabelProviders();
}
public static void RecreateMiniLabelProviders() {
miniLabelProviders = MiniLabels.Value
.Select(ml => MiniLabelProvider.MiniLabelsTypes.ElementAtOrDefault(ml))
.Where(ml => ml != null)
.Select(ml => Activator.CreateInstance(ml)as MiniLabelProvider)
.ToArray();
}
public static bool IsButtonEnabled(IconBase button) {
if (button == null)
return false;
if (LeftSideButton == button)
return true;
return RightIcons.Value.Contains(button) || LeftIcons.Value.Contains(button);
}
public static void ForceDisableButton(IconBase button) {
if (button == null)
Debug.LogError("Removing null button");
else
Debug.LogWarning("Disabling \"" + button.Name + "\", most likely because it threw an exception");
if (LeftSideButton == button)
LeftSideButton = IconBase.none;
RightIcons.Value.Remove(button);
LeftIcons.Value.Remove(button);
RightIcons.ForceSave();
LeftIcons.ForceSave();
}
private static void InitializePreferences() {
var type = typeof(Preferences);
var members = type.GetMembers(ReflectionHelper.FULL_BINDING);
foreach (var member in members)
try {
if (member == null)
continue;
var prefItemType = (Type)null;
var prop = member as PropertyInfo;
var field = member as FieldInfo;
switch (member.MemberType) {
case MemberTypes.Field:
if (typeof(IPrefItem).IsAssignableFrom(field.FieldType))
prefItemType = field.FieldType;
else
continue;
break;
case MemberTypes.Property:
if (typeof(IPrefItem).IsAssignableFrom(prop.PropertyType))
prefItemType = prop.PropertyType;
else
continue;
break;
default:
continue;
}
var keyAttribute = (AutoPrefItemAttribute)member.GetCustomAttributes(typeof(AutoPrefItemAttribute), true).FirstOrDefault();
var labelAttribute = (AutoPrefItemLabelAttribute)member.GetCustomAttributes(typeof(AutoPrefItemLabelAttribute), true).FirstOrDefault();
var defaultValueAttribute = (AutoPrefItemDefaultValueAttribute)member.GetCustomAttributes(typeof(AutoPrefItemDefaultValueAttribute), true).FirstOrDefault();
var key = member.Name;
var defaultValue = (object)null;
var label = new GUIContent(key);
//var savedValueType = prefItemType.GetGenericArguments()[0];
if (keyAttribute == null)
continue;
if (!string.IsNullOrEmpty(keyAttribute.Key))
key = keyAttribute.Key;
if (labelAttribute != null)
label = labelAttribute.Label;
if (defaultValueAttribute != null)
defaultValue = defaultValueAttribute.DefaultValue;
//else if(savedValueType.IsValueType)
// defaultValue = Activator.CreateInstance(savedValueType);
var prefItem = Activator.CreateInstance(prefItemType, key, defaultValue, label.text, label.tooltip);
switch (member.MemberType) {
case MemberTypes.Field:
field.SetValue(null, prefItem);
break;
case MemberTypes.Property:
prop.SetValue(null, prefItem, null);
break;
}
} catch (Exception e) {
Debug.LogException(e);
}
}
}
}