diff --git a/Documentation~/CustomRuleExample1.png b/Documentation~/CustomRuleExample1.png index b3f1bd0..e13ceb2 100644 Binary files a/Documentation~/CustomRuleExample1.png and b/Documentation~/CustomRuleExample1.png differ diff --git a/Documentation~/CustomRuleExample2.png b/Documentation~/CustomRuleExample2.png index 6f7da0a..2090347 100644 Binary files a/Documentation~/CustomRuleExample2.png and b/Documentation~/CustomRuleExample2.png differ diff --git a/Documentation~/CustomRuleExample3.png b/Documentation~/CustomRuleExample3.png index 47a877e..cd99b77 100644 Binary files a/Documentation~/CustomRuleExample3.png and b/Documentation~/CustomRuleExample3.png differ diff --git a/Documentation~/HierarchyExample.png b/Documentation~/HierarchyExample.png index 3d865a4..9b0c419 100644 Binary files a/Documentation~/HierarchyExample.png and b/Documentation~/HierarchyExample.png differ diff --git a/Documentation~/RandomColorStylingExample.png b/Documentation~/RandomColorStylingExample.png new file mode 100644 index 0000000..93f78d0 Binary files /dev/null and b/Documentation~/RandomColorStylingExample.png differ diff --git a/Documentation~/RuleAdditionExample.png b/Documentation~/RuleAdditionExample.png index 8d1e315..d392cd8 100644 Binary files a/Documentation~/RuleAdditionExample.png and b/Documentation~/RuleAdditionExample.png differ diff --git a/Documentation~/SettingsExample.png b/Documentation~/SettingsExample.png index c187c3d..0cba47b 100644 Binary files a/Documentation~/SettingsExample.png and b/Documentation~/SettingsExample.png differ diff --git a/Editor/BuiltInRules/DisplayScriptsFromSpecifiedAssemblyHierarchyRule.cs b/Editor/BuiltInRules/DisplayScriptsFromSpecifiedAssemblyHierarchyRule.cs index 221a523..3d635cb 100644 --- a/Editor/BuiltInRules/DisplayScriptsFromSpecifiedAssemblyHierarchyRule.cs +++ b/Editor/BuiltInRules/DisplayScriptsFromSpecifiedAssemblyHierarchyRule.cs @@ -17,8 +17,9 @@ internal class DisplayScriptsFromSpecifiedAssemblyHierarchyRule : HierarchyLabel [SerializeField] public string AssemblyName; [SerializeField] private bool _assemblyFullName; - public override bool GetLabel(Component component, out string label) + public override bool GetLabel(Component component, out string label, out GUIStyle style) { + style = StyleProvider.GetStyle(component); if (_types.ContainsKey(component.GetType())) { label = component.GetType().Name; diff --git a/Editor/BuiltInRules/DisplaySpecificComponentTypeHierarchyRule.cs b/Editor/BuiltInRules/DisplaySpecificComponentTypeHierarchyRule.cs index cb6cc05..6967ee6 100644 --- a/Editor/BuiltInRules/DisplaySpecificComponentTypeHierarchyRule.cs +++ b/Editor/BuiltInRules/DisplaySpecificComponentTypeHierarchyRule.cs @@ -10,8 +10,9 @@ internal class DisplaySpecificComponentTypeHierarchyRule : HierarchyLabelRule { [SerializeField] private string _typeName; - public override bool GetLabel(Component component, out string label) + public override bool GetLabel(Component component, out string label, out GUIStyle style) { + style = StyleProvider.GetStyle(component); if (component.GetType().Name == _typeName) { label = _typeName; diff --git a/Editor/BuiltInStyles.meta b/Editor/BuiltInStyles.meta new file mode 100644 index 0000000..b484407 --- /dev/null +++ b/Editor/BuiltInStyles.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 85ed9f51670045f7bd260904cf7876bb +timeCreated: 1642959688 \ No newline at end of file diff --git a/Editor/ColorLabelStyleProvider.cs b/Editor/BuiltInStyles/ColorLabelStyleProvider.cs similarity index 52% rename from Editor/ColorLabelStyleProvider.cs rename to Editor/BuiltInStyles/ColorLabelStyleProvider.cs index b678b6a..cb31b2f 100644 --- a/Editor/ColorLabelStyleProvider.cs +++ b/Editor/BuiltInStyles/ColorLabelStyleProvider.cs @@ -1,18 +1,25 @@ using System; +using System.ComponentModel; using UnityEditor; using UnityEngine; +using Component = UnityEngine.Component; -namespace HierarchyLabels +namespace HierarchyLabels.BuiltInStyles { - [Serializable] + [Serializable, DisplayName("Colored Label")] public class ColorLabelStyleProvider : ILabelStyleProvider, ISerializationCallbackReceiver { [SerializeField] private Color _color = Color.black; - public GUIStyle GetStyle() + public GUIStyle GetStyle(Component component) { - var style = new DefaultLabelStyleProvider().GetStyle(); - style.normal.textColor = _color; + var style = new GUIStyle(DefaultLabelStyleProvider.Style) + { + normal = + { + textColor = _color + } + }; return style; } diff --git a/Editor/ColorLabelStyleProvider.cs.meta b/Editor/BuiltInStyles/ColorLabelStyleProvider.cs.meta similarity index 100% rename from Editor/ColorLabelStyleProvider.cs.meta rename to Editor/BuiltInStyles/ColorLabelStyleProvider.cs.meta diff --git a/Editor/BuiltInStyles/DefaultLabelStyleProvider.cs b/Editor/BuiltInStyles/DefaultLabelStyleProvider.cs new file mode 100644 index 0000000..971a1b4 --- /dev/null +++ b/Editor/BuiltInStyles/DefaultLabelStyleProvider.cs @@ -0,0 +1,30 @@ +using System; +using System.ComponentModel; +using UnityEditor; +using UnityEngine; +using Component = UnityEngine.Component; + +namespace HierarchyLabels.BuiltInStyles +{ + [Serializable, DisplayName("Default Label Style")] + public class DefaultLabelStyleProvider : ILabelStyleProvider + { + private static GUIStyle _style; + public static GUIStyle Style => + _style ??= new GUIStyle(EditorStyles.whiteLabel) + { + name = nameof(DefaultLabelStyleProvider), + fontSize = EditorStyles.whiteLabel.fontSize, + normal = + { + background = Texture2D.grayTexture + }, + alignment = TextAnchor.MiddleCenter + }; + + public GUIStyle GetStyle(Component component) + { + return Style; + } + } +} \ No newline at end of file diff --git a/Editor/DefaultLabelStyleProvider.cs.meta b/Editor/BuiltInStyles/DefaultLabelStyleProvider.cs.meta similarity index 100% rename from Editor/DefaultLabelStyleProvider.cs.meta rename to Editor/BuiltInStyles/DefaultLabelStyleProvider.cs.meta diff --git a/Editor/DefaultLabelStyleProvider.cs b/Editor/DefaultLabelStyleProvider.cs deleted file mode 100644 index 3caaf07..0000000 --- a/Editor/DefaultLabelStyleProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using UnityEditor; -using UnityEngine; - -namespace HierarchyLabels -{ - [Serializable] - public class DefaultLabelStyleProvider : ILabelStyleProvider - { - public GUIStyle GetStyle() - { - var style = new GUIStyle(EditorStyles.whiteLabel) - { - normal = - { - background = Texture2D.grayTexture - }, - alignment = TextAnchor.MiddleCenter - }; - return style; - } - } -} \ No newline at end of file diff --git a/Editor/HierarchyLabelManager.cs b/Editor/HierarchyLabelManager.cs index 31adffc..e2c810b 100644 --- a/Editor/HierarchyLabelManager.cs +++ b/Editor/HierarchyLabelManager.cs @@ -1,6 +1,7 @@ using System; +using System.Collections.Generic; using System.Linq; -using System.Text; +using HierarchyLabels.BuiltInStyles; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; @@ -27,27 +28,43 @@ private static void OnDrawHierarchyItem(int instanceID, Rect selectionRect) if (gameObject != null) { - selectionRect.x += HierarchyLabelsSettings.instance.StyleProvider.GetStyle().CalcSize(new GUIContent(gameObject.name)).x + + selectionRect.x += EditorStyles.label.CalcSize(new GUIContent(gameObject.name)).x + HierarchyLabelsSettings.instance.Alignment.x; selectionRect.y += HierarchyLabelsSettings.instance.Alignment.y; - ApplyLabel(selectionRect, GetLabel(gameObject)); + ApplyLabels(selectionRect, GetLabels(gameObject)); } } - private static void ApplyLabel(Rect selectionRect, string label) + private static void ApplyLabels(Rect selectionRect, List> labels) { - if (string.IsNullOrEmpty(label)) + if (!labels.Any()) { return; } - var labelStyle = HierarchyLabelsSettings.instance.StyleProvider.GetStyle(); + var isFirst = true; + foreach (var (label, style) in labels) + { + if (!isFirst) + { + DrawLabelWithStyle(ref selectionRect, DefaultLabelStyleProvider.Style, + HierarchyLabelsSettings.instance.Separator); + } + + DrawLabelWithStyle(ref selectionRect, style, label); + isFirst = false; + } + } + + private static void DrawLabelWithStyle(ref Rect selectionRect, GUIStyle style, string label) + { + var labelStyle = new GUIStyle(style); labelStyle.fontSize = Convert.ToInt32(labelStyle.fontSize * HierarchyLabelsSettings.instance.FontSizeFactory); var size = labelStyle.CalcSize(new GUIContent(label)); - selectionRect.y = selectionRect.y + (selectionRect.height - size.y)/2f; + selectionRect.y += (selectionRect.height - size.y) / 2f; selectionRect.width = size.x; selectionRect.height = size.y; @@ -55,28 +72,30 @@ private static void ApplyLabel(Rect selectionRect, string label) selectionRect.x += size.x; } - private static string GetLabel(GameObject gameObject) + private static List> GetLabels(GameObject gameObject) { - var label = new StringBuilder(); + var list = new List>(); + if (HierarchyLabelsSettings.instance.HierarchyLabelRules == null) + { + return list; + } foreach (var component in gameObject.GetComponents()) { - var componentLabel = string.Empty; - if (HierarchyLabelsSettings.instance.HierarchyLabelRules != null && - HierarchyLabelsSettings.instance.HierarchyLabelRules - .Where(e => e != null) - .Any(rule => rule.GetLabel(component, out componentLabel))) + var applicableRules = HierarchyLabelsSettings.instance.HierarchyLabelRules + .Where(e => e != null) + .Select(rule => rule.GetLabel(component, out var componentLabel, out var style) + ? new Tuple(componentLabel, style) + : null) + .Where(e => e is not null) + .ToList(); + if (applicableRules.Any()) { - if (label.Length > 0) - { - label.Append(HierarchyLabelsSettings.instance.Separator); - } - - label.Append(componentLabel); + list.AddRange(applicableRules); } } - return label.ToString(); + return list; } } } \ No newline at end of file diff --git a/Editor/HierarchyLabelRule.cs b/Editor/HierarchyLabelRule.cs index b3004b7..0a2e723 100644 --- a/Editor/HierarchyLabelRule.cs +++ b/Editor/HierarchyLabelRule.cs @@ -1,4 +1,5 @@ using System; +using HierarchyLabels.BuiltInStyles; using UnityEditor; using UnityEngine; @@ -7,7 +8,10 @@ namespace HierarchyLabels [Serializable] public abstract class HierarchyLabelRule : IHierarchyLabelRule, ISerializationCallbackReceiver { - public abstract bool GetLabel(Component component, out string label); + [SerializeReference] private ILabelStyleProvider _styleProviderProvider = new DefaultLabelStyleProvider(); + protected ILabelStyleProvider StyleProvider => _styleProviderProvider; + + public abstract bool GetLabel(Component component, out string label, out GUIStyle style); public virtual void OnBeforeSerialize() { diff --git a/Editor/HierarchyLabelsSettings.cs b/Editor/HierarchyLabelsSettings.cs index fd19980..bbe8eb6 100644 --- a/Editor/HierarchyLabelsSettings.cs +++ b/Editor/HierarchyLabelsSettings.cs @@ -3,6 +3,8 @@ using System.ComponentModel; using System.Linq; using System.Reflection; +using HierarchyLabels.BuiltInStyles; +using HierarchyLabels.Utils; using UnityEditor; using UnityEngine; @@ -25,7 +27,6 @@ private set public Vector2 Alignment => _labelAlignment; public float FontSizeFactory => _fontSizeFactor; - public ILabelStyleProvider StyleProvider => _labelStyleProvider; public string Separator => _separator; public IHierarchyLabelRule[] HierarchyLabelRules => _hierarchyLabelRules.ToArray(); @@ -34,13 +35,12 @@ private set [SerializeField, Range(0, 1)] private float _fontSizeFactor = 0.8f; [SerializeField] private string _separator = "|"; [SerializeReference] private List _hierarchyLabelRules = new(); - [SerializeReference] private ILabelStyleProvider _labelStyleProvider; - [SerializeField] private bool _foldoutStyling; [SerializeField] private bool _foldoutRules; [SerializeField] private int _selectedRuleIndex; private static Dictionary _availableRules; + private static List> _availableStyles; private static SerializedObject _serializedObject; [MenuItem("Tools/Toggle Hierarchy Labels")] @@ -50,7 +50,7 @@ private static void ToggleLabels() } [InitializeOnLoadMethod] - private static void GetOrCreateEditor() + private static void Initialize() { _availableRules = TypeCache.GetTypesDerivedFrom() .Where(e => e.IsClass) @@ -61,6 +61,15 @@ private static void GetOrCreateEditor() ? displayNameAttribute.DisplayName : e.Name, e => e); + + _availableStyles = TypeCache.GetTypesDerivedFrom() + .Where(e => e.IsClass && !e.IsAbstract) + .Select(e => new Tuple( + e, e.GetCustomAttribute(false) + is { } displayNameAttribute + ? displayNameAttribute.DisplayName + : e.Name)) + .ToList(); } public static void DrawSettings() @@ -77,22 +86,20 @@ public static void DrawSettings() EditorGUILayout.Separator(); EditorGUILayout.LabelField("Hierarchy Label Rules:", EditorStyles.boldLabel); - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - DrawActiveRules(); - EditorGUILayout.Separator(); - DrawRulesAdditionUI(); - EditorGUILayout.EndVertical(); - - EditorGUILayout.Separator(); - - DrawStylingUI(); + using (new VerticalLayoutBlock(EditorStyles.helpBox)) + { + DrawActiveRules(); + EditorGUILayout.Separator(); + DrawRulesAdditionUI(); + } instance.Sanitize(); - + if (_serializedObject.hasModifiedProperties) { _serializedObject.ApplyModifiedProperties(); } + if (EditorGUI.EndChangeCheck()) { instance.Save(true); @@ -112,49 +119,83 @@ private static void DrawActiveRules() var index = 0; while (hierarchyRulesEnumerator.MoveNext() && hierarchyRulesEnumerator.Current != null) { - EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); - EditorGUILayout.BeginVertical(); - var currentHierarchyLabelRule = (SerializedProperty)hierarchyRulesEnumerator.Current; - - if (currentHierarchyLabelRule.managedReferenceValue == null) - { - hierarchyRulesSerializedProperty.DeleteArrayElementAtIndex(index); - break; - } - - var hierarchyRuleType = currentHierarchyLabelRule.managedReferenceValue.GetType(); - var displayName = hierarchyRuleType - .GetCustomAttribute() is { } displayNameAttribute - ? displayNameAttribute.DisplayName - : hierarchyRuleType.Name; - EditorGUILayout.LabelField($"Name: {displayName}"); - - var hierarchyRuleChildrenEnumerator = currentHierarchyLabelRule.GetEnumerator(); - while (hierarchyRuleChildrenEnumerator.MoveNext()) + using (new HorizontalLayoutBlock(EditorStyles.helpBox)) { - EditorGUILayout.PropertyField((SerializedProperty)hierarchyRuleChildrenEnumerator.Current, true); - } - - EditorGUILayout.EndVertical(); + using (new VerticalLayoutBlock()) + { + var currentHierarchyLabelRule = (SerializedProperty)hierarchyRulesEnumerator.Current; + + if (currentHierarchyLabelRule.managedReferenceValue == null) + { + hierarchyRulesSerializedProperty.DeleteArrayElementAtIndex(index); + EditorGUILayout.EndVertical(); + break; + } + + var hierarchyRuleType = currentHierarchyLabelRule.managedReferenceValue.GetType(); + var displayName = hierarchyRuleType + .GetCustomAttribute() is { } displayNameAttribute + ? displayNameAttribute.DisplayName + : hierarchyRuleType.Name; + EditorGUILayout.LabelField($"Name: {displayName}"); + + var hierarchyRuleChildrenEnumerator = currentHierarchyLabelRule.GetEnumerator(); + while (hierarchyRuleChildrenEnumerator.MoveNext()) + { + if (hierarchyRuleChildrenEnumerator.Current is not SerializedProperty + childSerializedProperty) + { + continue; + } + + var fullName = typeof(ILabelStyleProvider).FullName ?? nameof(ILabelStyleProvider); + if (childSerializedProperty.propertyType == SerializedPropertyType.ManagedReference + && childSerializedProperty.managedReferenceFieldTypename.Contains(fullName)) + { + childSerializedProperty.managedReferenceValue ??= new DefaultLabelStyleProvider(); + + var currentStyle = + _availableStyles.FirstOrDefault(e => + e.Item1 == childSerializedProperty.managedReferenceValue.GetType()); + var styleIndex = currentStyle != null ? _availableStyles.IndexOf(currentStyle) : 0; + var newStyleIndex = EditorGUILayout.Popup(new GUIContent("Style:"), + styleIndex, + _availableStyles.Select(e => e.Item2).ToArray()); + if (newStyleIndex != styleIndex) + { + childSerializedProperty.managedReferenceValue = + Activator.CreateInstance(_availableStyles[newStyleIndex].Item1); + } + } + else + { + EditorGUILayout.PropertyField(childSerializedProperty, true); + } + } + } - if (GUILayout.Button("Remove")) - { - hierarchyRulesSerializedProperty.DeleteArrayElementAtIndex(index); - break; + if (GUILayout.Button("Remove")) + { + hierarchyRulesSerializedProperty.DeleteArrayElementAtIndex(index); + break; + } } - EditorGUILayout.EndHorizontal(); index++; } } private static void DrawRulesAdditionUI() { - instance._foldoutRules = EditorGUILayout.BeginFoldoutHeaderGroup(instance._foldoutRules, "Label Rules:"); - if (instance._foldoutRules) + using (new FoldoutHeaderGroupBlock(ref instance._foldoutRules, "Available Label Rules:")) { + if (!instance._foldoutRules) + { + return; + } + instance._selectedRuleIndex = EditorGUILayout.Popup(new GUIContent("Select which rule to add:"), - instance._selectedRuleIndex, + Math.Min(instance._selectedRuleIndex, _availableRules.Count - 1), _availableRules.Keys.ToArray()); var selectedRule = _availableRules.ElementAt(instance._selectedRuleIndex); @@ -164,42 +205,18 @@ private static void DrawRulesAdditionUI() EditorGUILayout.LabelField(descriptionAttribute.Description, EditorStyles.wordWrappedLabel); } - EditorGUILayout.BeginHorizontal(); - GUILayout.FlexibleSpace(); - if (GUILayout.Button("Add selected Rule")) + using (new HorizontalLayoutBlock()) { - var newInstance = Activator.CreateInstance(selectedRule.Value); - instance._hierarchyLabelRules.Add(newInstance as IHierarchyLabelRule); - } - - GUILayout.FlexibleSpace(); - EditorGUILayout.EndHorizontal(); - } - - EditorGUILayout.EndFoldoutHeaderGroup(); - } - - private static void DrawStylingUI() - { - var labelStyleProvider = _serializedObject.FindProperty(nameof(_labelStyleProvider)); - EditorGUILayout.PropertyField(labelStyleProvider, true); - - instance._foldoutStyling = EditorGUILayout.BeginFoldoutHeaderGroup(instance._foldoutStyling, "Styling:"); - if (instance._foldoutStyling) - { - foreach (var type in TypeCache.GetTypesDerivedFrom() - .Where(e => e.IsClass) - .Where(e => !e.IsAbstract)) - { - if (GUILayout.Button($"Use {type.Name}")) + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Add selected Rule")) { - var newInstance = Activator.CreateInstance(type); - instance._labelStyleProvider = newInstance as ILabelStyleProvider; + var newInstance = Activator.CreateInstance(selectedRule.Value); + instance._hierarchyLabelRules.Add(newInstance as IHierarchyLabelRule); } + + GUILayout.FlexibleSpace(); } } - - EditorGUILayout.EndFoldoutHeaderGroup(); } private void Sanitize() diff --git a/Editor/IHierarchyLabelRule.cs b/Editor/IHierarchyLabelRule.cs index 4b880dd..0608edc 100644 --- a/Editor/IHierarchyLabelRule.cs +++ b/Editor/IHierarchyLabelRule.cs @@ -4,6 +4,6 @@ namespace HierarchyLabels { public interface IHierarchyLabelRule { - bool GetLabel(Component component, out string label); + bool GetLabel(Component component, out string label, out GUIStyle style); } } \ No newline at end of file diff --git a/Editor/ILabelStyleProvider.cs b/Editor/ILabelStyleProvider.cs index 2dffa3e..322d2d8 100644 --- a/Editor/ILabelStyleProvider.cs +++ b/Editor/ILabelStyleProvider.cs @@ -4,6 +4,6 @@ namespace HierarchyLabels { public interface ILabelStyleProvider { - GUIStyle GetStyle(); + GUIStyle GetStyle(Component component); } } \ No newline at end of file diff --git a/Editor/Utils.meta b/Editor/Utils.meta new file mode 100644 index 0000000..8c4e230 --- /dev/null +++ b/Editor/Utils.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f80e717a2c104b18875b0d35118966be +timeCreated: 1642958441 \ No newline at end of file diff --git a/Editor/Utils/EditorGUIBlocks.cs b/Editor/Utils/EditorGUIBlocks.cs new file mode 100644 index 0000000..4348fbe --- /dev/null +++ b/Editor/Utils/EditorGUIBlocks.cs @@ -0,0 +1,68 @@ +using System; +using UnityEditor; +using UnityEngine; + +namespace HierarchyLabels.Utils +{ + public class HorizontalLayoutBlock : IDisposable + { + public HorizontalLayoutBlock(GUIStyle style, params GUILayoutOption[] options) + { + EditorGUILayout.BeginHorizontal(style, options); + } + + public HorizontalLayoutBlock(params GUILayoutOption[] options) + { + EditorGUILayout.BeginHorizontal(options); + } + + public void Dispose() + { + EditorGUILayout.EndHorizontal(); + } + } + + public class VerticalLayoutBlock : IDisposable + { + public VerticalLayoutBlock(GUIStyle style, params GUILayoutOption[] options) + { + EditorGUILayout.BeginVertical(style, options); + } + + public VerticalLayoutBlock(params GUILayoutOption[] options) + { + EditorGUILayout.BeginVertical(options); + } + + public void Dispose() + { + EditorGUILayout.EndVertical(); + } + } + + public class FoldoutHeaderGroupBlock : IDisposable + { + public FoldoutHeaderGroupBlock(ref bool foldout, + string content, + GUIStyle style = null, + Action menuAction = null, + GUIStyle menuIcon = null) + { + foldout = EditorGUILayout.BeginFoldoutHeaderGroup(foldout, content, style, menuAction, menuIcon); + } + + public FoldoutHeaderGroupBlock(ref bool foldout, + GUIContent content, + GUIStyle style = null, + Action menuAction = null, + GUIStyle menuIcon = null) + { + foldout = EditorGUILayout.BeginFoldoutHeaderGroup(foldout, content, style, menuAction, menuIcon); + } + + public void Dispose() + { + EditorGUILayout.EndFoldoutHeaderGroup(); + } + } +} \ No newline at end of file diff --git a/Editor/Utils/EditorGUIBlocks.cs.meta b/Editor/Utils/EditorGUIBlocks.cs.meta new file mode 100644 index 0000000..18fe179 --- /dev/null +++ b/Editor/Utils/EditorGUIBlocks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3491603fdbb154dc38517c9c6b07fc6b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/README.md b/README.md index 58bb7cd..807bb47 100644 --- a/README.md +++ b/README.md @@ -6,31 +6,32 @@ Custom labels for the Unity Hierarchy window: ![Example image of hierarchy labels](Documentation~/HierarchyExample.png) -- [Installation](#installation) -- [Features](#features) -- [How to use](#how-to-use) -- [Built-in rules](#built-in-rules) -- [Custom Rules](#custom-rules) - - [How to add your own rule](#how-to-add-your-own-rule) - - [How to make your rule configurable](#how-to-make-your-rule-configurable) - - [Add name and description for your rule](#add-name-and-description-for-your-rule) - - [Tips for adding custom rules](#tips-for-adding-custom-rules) -- [Known limitations](#known-limitations) -- [Future Plans](#future-plans) +* [Installation](#installation) +* [Features](#features) +* [How to use](#how-to-use) +* [Built-in rules](#built-in-rules) +* [Built-in styles](#built-in-styles) +* [Custom Rules](#custom-rules) + * [How to add your own rule](#how-to-add-your-own-rule) + * [How to make your rule configurable](#how-to-make-your-rule-configurable) + * [Add name and description for your rule](#add-name-and-description-for-your-rule) + * [Tips for adding custom rules](#tips-for-adding-custom-rules) +* [Custom styling](#custom-styling) +* [Known limitations](#known-limitations) ## Installation -- Via OpenUPM: ```openupm add com.shniqq.hierarchy-labels``` +* Via OpenUPM: ```openupm add com.shniqq.hierarchy-labels``` -- Via PackageManager UI: Go to add package, select `git` and paste ```https://github.com/shniqq/hierarchy-labels.git``` +* Via PackageManager UI: Go to add package, select `git` and paste ```https://github.com/shniqq/hierarchy-labels.git``` -- Manually: Add ```"com.shniqq.hierarchy-labels": "https://github.com/shniqq/hierarchy-labels.git"``` to your `manifest.json` +* Manually: Add ```"com.shniqq.hierarchy-labels": "https://github.com/shniqq/hierarchy-labels.git"``` to your `manifest.json` ## Features -- Display a label (or multiple) for each element in Unity's hierarchy window -- Add custom rules for displaying a label -- Custom label styling +* Display a label (or multiple) for each GameObject in Unity's hierarchy window +* Add custom rules for displaying a label +* Custom label styling per rule ## How to use @@ -44,11 +45,16 @@ Configure your rule in the list above. ## Built-in rules -- `DisplayScriptsFromSpecifiedAssemblyHierarchyRule ("Is From Assembly")`: Displays a label if the component script is from the specified assembly. +* `DisplayScriptsFromSpecifiedAssemblyHierarchyRule ("Is From Assembly")`: Displays a label if the component script is from the specified assembly. You can configure if the name of the assembly should be matched fully or just contain the specfied text, i.e. unchecking `Assembly Full Name` will make `TextMeshPro` match any TMPro component, while checking it will not, since the full assembly name is `Unity.TextMeshPro`. -- `DisplaySpecificComponentTypeHierarchyRule ("Is Type")`: +* `DisplaySpecificComponentTypeHierarchyRule ("Is Type")`: Displays a label if the component script is of the specified type. +## Built-in styles + +* `DefaultLabelStyleProvider ("Default Label Style")`: The default style for a label. +* `ColorLabelStyleProvider ("Colored Label")`: Select a color in the settings and the label will be displayed in that color. + ## Custom Rules ### How to add your own rule @@ -59,15 +65,20 @@ Add a new class that inherits from `HierarchyLabelRule` (recommended) or impleme ```csharp using System; +using System.ComponentModel; using HierarchyLabels; using UnityEngine; using UnityEngine.UI; +using Component = UnityEngine.Component; [Serializable] public class CanvasWithoutRaycastHierarchyLabelRule : HierarchyLabelRule { - public override bool GetLabel(Component component, out string label) + [SerializeField] private bool _includeDisabled; + + public override bool GetLabel(Component component, out string label, out GUIStyle style) { + style = StyleProvider.GetStyle(component); label = string.Empty; if (component is Canvas && !component.GetComponent()) @@ -95,18 +106,21 @@ For this, we add a `[SerializedField]` to the class above: ```csharp using System; +using System.ComponentModel; using HierarchyLabels; using UnityEngine; using UnityEngine.UI; +using Component = UnityEngine.Component; [Serializable] public class CanvasWithoutRaycastHierarchyLabelRule : HierarchyLabelRule { - //All serialized fields will show up in the settings and can be used to configure your rule + //All serialized fields will show up in the settings and can be used to configure your rule [SerializeField] private bool _includeDisabled; - public override bool GetLabel(Component component, out string label) + public override bool GetLabel(Component component, out string label, out GUIStyle style) { + style = StyleProvider.GetStyle(component); label = string.Empty; if (component is Canvas && IsRaycastDisabledOrMissing(component)) @@ -128,9 +142,13 @@ public class CanvasWithoutRaycastHierarchyLabelRule : HierarchyLabelRule Any `[SerializedField]` will show up in the preferences window and can be used to configure your rule. +`[SerializedReference]` fields will also show up, however you need to provide your own logic to assign values to it, as Unity does not have any built-in way of assiging values to such fields. + +A special case is `[SerializeReference]` for `ILabelStyleProvider` fields: In that case a dropdown will appear where you can select out of all classes that implement this interface. + ![Example of custom rule with configuration](Documentation~/CustomRuleExample2.png) -The example above now shows the option to include disabled components, and it properly shows the label on the GameObject in the hierarchy (not the disabled `GraphicalRaycaster` on the selected object). +The example above now shows the option to include disabled components, and it properly shows the label on the GameObject in the hierarchy (not the disabled `GraphicRaycaster` on the selected object). ### Add name and description for your rule @@ -145,14 +163,15 @@ using UnityEngine.UI; using Component = UnityEngine.Component; [Serializable, - DisplayName("Missing Raycaster on Canvas"), - Description("Shows a label if a Canvas is attached but no GraphicalRaycaster is present or it is disabled.")] +DisplayName("Missing Raycaster on Canvas"), +Description("Shows a label if a Canvas is attached but no GraphicRaycaster is present or it is disabled.")] public class CanvasWithoutRaycastHierarchyLabelRule : HierarchyLabelRule { [SerializeField] private bool _includeDisabled; - public override bool GetLabel(Component component, out string label) + public override bool GetLabel(Component component, out string label, out GUIStyle style) { + style = StyleProvider.GetStyle(component); label = string.Empty; if (component is Canvas && IsRaycastDisabledOrMissing(component)) @@ -176,16 +195,46 @@ public class CanvasWithoutRaycastHierarchyLabelRule : HierarchyLabelRule ### Tips for adding custom rules -- Try to keep expensive method calls like `GetComponent()` to a minimum. -- Use `ISerializationCallbackReceiver` or `[InitializeOnLoadMethod]`-attributes to do expensive calls instead of in each `GetLabel()` call. -- If you implement `IHierarchyLabelRule` instead of inheriting from `HierarchyLabelRule`, it is recommended to call `EditorApplication.RepaintHierarchyWindow()` after any settings in your rule have changed, e.g. via the `ISerializationCallbackReceiver.OnBeforeSerialize()` method. -- Make sure to add the `[Serializable]` attribute to your rule implementation class. +* Try to keep expensive method calls like `GetComponent()` to a minimum. +* Use `ISerializationCallbackReceiver` or `[InitializeOnLoadMethod]`-attributes to do expensive calls instead of in each `GetLabel()` call. +* If you implement `IHierarchyLabelRule` instead of inheriting from `HierarchyLabelRule`, it is recommended to call `EditorApplication.RepaintHierarchyWindow()` after any settings in your rule have changed, e.g. via the `ISerializationCallbackReceiver.OnBeforeSerialize()` method. +* If you implement `IHierarchyLabelRule` instead of inheriting from `HierarchyLabelRule`, adding a `[SerializedReference]` field of type `ILabelStyleProvider` to get the styling dropdown menu. Otherwise you can fall back to `DefaultLabelStyleProvider.Style`. +* Make sure to add the `[Serializable]` attribute to your rule/style implementation class. -## Known limitations +## Custom styling + +To implement a styling rule, the process is almost the same as for custom label rules. The `[DisplayName]` attribute is supported as well. + +The following example will generate a unique color for each component (regardless of type). + +```csharp +using System; +using System.ComponentModel; +using HierarchyLabels; +using UnityEngine; +using Component = UnityEngine.Component; +using Random = System.Random; + +[Serializable, DisplayName("Unique Color per Component")] +public class UniqueColorLabelStyleProvider : ILabelStyleProvider +{ + public GUIStyle GetStyle(Component component) + { + var random = new Random(component.GetInstanceID()); + return new GUIStyle(DefaultLabelStyleProvider.Style) + { + normal = + { + textColor = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()) + } + }; + } +} +``` -- If your rule is based on some values of a GameObject, i.e. if a component is disabled or not, changing that value on the GameObject will not immediately trigger a redraw of the hierarchy window, and hence your label might not show/hide immediately, except if this dirties the scene. -- If you rename your class, change it's namespace, or it's assembly is modified, the rule is removed from the list of active rules and has to be re-added (`Missing types referenced` warning in the console). This is a limitation by Unity, and is [possibly addressed in the future](https://issuetracker.unity3d.com/issues/serializereference-serialized-reference-data-lost-when-the-class-name-is-refactored). One way around this issue is adding the [`[MovedFromAttribute]`](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Scripting/APIUpdating/UpdatedFromAttribute.cs) to the class that was changed. +![Example of styling each label with a random color per component](Documentation~/RandomColorStylingExample.png) -## Future Plans +## Known limitations -- Provide better styling options +* If your rule is based on some values of a GameObject, i.e. if a component is disabled or not, changing that value on the GameObject will not immediately trigger a redraw of the hierarchy window, and hence your label might not show/hide immediately, except if this dirties the scene. +* If you rename your class, change it's namespace, or it's assembly is modified, the rule is removed from the list of active rules and has to be re-added (`Missing types referenced` warning in the console). This is a limitation by Unity, and is [possibly addressed in the future](https://issuetracker.unity3d.com/issues/serializereference-serialized-reference-data-lost-when-the-class-name-is-refactored). One way around this issue is adding the [`[MovedFromAttribute]`](https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Scripting/APIUpdating/UpdatedFromAttribute.cs) to the class that was changed. diff --git a/Samples~/CanvasWithoutRaycastHierarchyLabelRule.cs b/Samples~/CanvasWithoutRaycastHierarchyLabelRule.cs index 793ea71..17f76cd 100644 --- a/Samples~/CanvasWithoutRaycastHierarchyLabelRule.cs +++ b/Samples~/CanvasWithoutRaycastHierarchyLabelRule.cs @@ -7,13 +7,14 @@ [Serializable, DisplayName("Missing Raycaster on Canvas"), -Description("Shows a label if a Canvas is attached but no GraphicalRaycaster is present or it is disabled.")] +Description("Shows a label if a Canvas is attached but no GraphicRaycaster is present or it is disabled.")] public class CanvasWithoutRaycastHierarchyLabelRule : HierarchyLabelRule { [SerializeField] private bool _includeDisabled; - public override bool GetLabel(Component component, out string label) + public override bool GetLabel(Component component, out string label, out GUIStyle style) { + style = StyleProvider.GetStyle(component); label = string.Empty; if (component is Canvas && IsRaycastDisabledOrMissing(component)) diff --git a/Samples~/UniqueColorLabelStyleProvider.cs b/Samples~/UniqueColorLabelStyleProvider.cs new file mode 100644 index 0000000..4339661 --- /dev/null +++ b/Samples~/UniqueColorLabelStyleProvider.cs @@ -0,0 +1,22 @@ +using System; +using System.ComponentModel; +using HierarchyLabels; +using UnityEngine; +using Component = UnityEngine.Component; +using Random = System.Random; + +[Serializable, DisplayName("Unique Color per Component")] +public class UniqueColorLabelStyleProvider : ILabelStyleProvider +{ + public GUIStyle GetStyle(Component component) + { + var random = new Random(component.GetInstanceID()); + return new GUIStyle(DefaultLabelStyleProvider.Style) + { + normal = + { + textColor = new Color((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble()) + } + }; + } +} \ No newline at end of file diff --git a/package.json b/package.json index 3f90454..58b78a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.shniqq.hierarchy-labels", - "version": "1.0.0-preview.1", + "version": "1.0.0-preview.2", "description": "Rule based labels for the Unity Editor hierarchy window.", "author": "Ruben Ohnmacht", "license": "MIT", @@ -8,8 +8,8 @@ "documentationUrl": "https://github.com/shniqq/hierarchy-labels#readme", "samples": [ { - "displayName": "Custom Rule Example", - "description": "Contains rule to show a label if a Canvas is missing a GraphicalRaycaster as example on how to create a custom rule.", + "displayName": "Custom Rule & Styling Examples", + "description": "Contains rule to show a label if a Canvas is missing a GraphicalRaycaster as example on how to create a custom rule as well as a custom styling provider to color each component instance uniquely.", "path": "Samples~/" } ]