From c87dd743f67f314725151f4bc5e990561785309b Mon Sep 17 00:00:00 2001 From: Anders Ejlersen Date: Sun, 10 Mar 2024 14:11:49 +0100 Subject: [PATCH] 1.9.2: Added `OpenPropertyEditor` and fixed some issues regarding using multiple drawers with `EditorGUI.PropertyField` - Property: `OpenPropertyEditor` adds a "show"-button next to a `ScriptableObject` or `Component` to open a property window - Property: Fixed issue, where x PropertyDrawers would invoke each other x times --- Editor/AbstractPropertyDrawer.cs | 150 +++++++++++------- .../OpenPropertyEditorAttributeDrawer.cs | 49 ++++++ .../OpenPropertyEditorAttributeDrawer.cs.meta | 3 + Editor/Utilities/EditorPropertyUtility.cs | 24 ++- README.md | 5 +- .../Drawers/OpenPropertyEditorAttribute.cs | 16 ++ .../OpenPropertyEditorAttribute.cs.meta | 3 + package.json | 2 +- 8 files changed, 189 insertions(+), 63 deletions(-) create mode 100644 Editor/Drawers/OpenPropertyEditorAttributeDrawer.cs create mode 100644 Editor/Drawers/OpenPropertyEditorAttributeDrawer.cs.meta create mode 100644 Runtime/Drawers/OpenPropertyEditorAttribute.cs create mode 100644 Runtime/Drawers/OpenPropertyEditorAttribute.cs.meta diff --git a/Editor/AbstractPropertyDrawer.cs b/Editor/AbstractPropertyDrawer.cs index 1fc447b..1706b72 100644 --- a/Editor/AbstractPropertyDrawer.cs +++ b/Editor/AbstractPropertyDrawer.cs @@ -1,4 +1,5 @@ -using Module.Inspector.Editor.Utilities; +using System; +using Module.Inspector.Editor.Utilities; using UnityEditor; using UnityEngine; @@ -8,79 +9,107 @@ namespace Module.Inspector.Editor { public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { - EditorPropertyUtility.Result result = EditorPropertyUtility.Query(fieldInfo); - EAccessType accessType = GetAccessType(property, result); - - if (accessType == EAccessType.Hidden) - return; - - bool prevEnabled = GUI.enabled; - GUI.enabled = prevEnabled && accessType == EAccessType.Enabled; - - if (Event.current.type == EventType.Repaint) + EditorPropertyUtility.Result result = null; + int propertyHash = EditorPropertyUtility.CalculateHash(property); + bool isRootDrawer = false; + + try { - for (var i = 0; i < result.handleDrawers.Count; i++) + result = EditorPropertyUtility.Query(fieldInfo); + EAccessType accessType = GetAccessType(property, result); + + if (accessType == EAccessType.Hidden) + return; + + if (result.IsUsedBy(propertyHash)) { - EditorPropertyUtility.ResultValue value = result.handleDrawers[i]; - HandleDrawerEditor.Add(value.attribute, property, value.drawer); + EditorGUI.PropertyField(position, property, label); + return; } - } - label.tooltip = result.tooltip; - - for (var i = 0; i < result.predrawerModifiers.Count; i++) - { - EditorPropertyUtility.ResultValue value = result.predrawerModifiers[i]; - value.drawer.Modify(value.attribute, property, label); - } + isRootDrawer = true; + result.AddUsedBy(propertyHash); + bool prevEnabled = GUI.enabled; + GUI.enabled = prevEnabled && accessType == EAccessType.Enabled; - var isValid = true; - var validationError = string.Empty; + if (Event.current.type == EventType.Repaint) + { + for (var i = 0; i < result.handleDrawers.Count; i++) + { + EditorPropertyUtility.ResultValue value = + result.handleDrawers[i]; + HandleDrawerEditor.Add(value.attribute, property, value.drawer); + } + } - for (var i = 0; i < result.validators.Count; i++) - { - EditorPropertyUtility.ResultValue value = result.validators[i]; + label.tooltip = result.tooltip; - if (value.drawer.Validate(value.attribute, property)) - continue; + for (var i = 0; i < result.predrawerModifiers.Count; i++) + { + EditorPropertyUtility.ResultValue value = result.predrawerModifiers[i]; + value.drawer.Modify(value.attribute, property, label); + } - validationError += value.drawer.GetValidationError(value.attribute, property); - isValid = false; - } - - Color prevColor = GUI.color; + var isValid = true; + var validationError = string.Empty; - if (!isValid) - { - if (string.IsNullOrEmpty(label.tooltip)) - label.tooltip = validationError; + for (var i = 0; i < result.validators.Count; i++) + { + EditorPropertyUtility.ResultValue value = + result.validators[i]; + + if (value.drawer.Validate(value.attribute, property)) + continue; + + validationError += value.drawer.GetValidationError(value.attribute, property); + isValid = false; + } + + Color prevColor = GUI.color; + + if (!isValid) + { + if (string.IsNullOrEmpty(label.tooltip)) + label.tooltip = validationError; + else + label.tooltip += "\n" + validationError; + + EditorIcons.SetErrorIcon(label); + } + + if (result.draw != null) + { + if (!result.draw.drawer.Draw(position, result.draw.attribute, property, label, result)) + { + GUI.color = Color.red; + var errorContent = new GUIContent(result.draw.drawer.GetErrorMessage(property)); + EditorGUI.LabelField(position, label, errorContent); + } + } else - label.tooltip += "\n" + validationError; - - EditorIcons.SetErrorIcon(label); - } - - if (result.draw != null) - { - if (!result.draw.drawer.Draw(position, result.draw.attribute, property, label, result)) { - GUI.color = Color.red; - var errorContent = new GUIContent(result.draw.drawer.GetErrorMessage(property)); - EditorGUI.LabelField(position, label, errorContent); + EditorGUI.PropertyField(position, property, label, true); + } + + GUI.color = prevColor; + GUI.enabled = prevEnabled; + + for (var i = 0; i < result.valueModifiers.Count; i++) + { + EditorPropertyUtility.ResultValue + value = result.valueModifiers[i]; + value.drawer.Modify(value.attribute, property); } } - else + catch (Exception) { - EditorGUI.PropertyField(position, property, label, true); + // Ignore } - - GUI.color = prevColor; - GUI.enabled = prevEnabled; - - for (var i = 0; i < result.valueModifiers.Count; i++) + finally { - EditorPropertyUtility.ResultValue value = result.valueModifiers[i]; - value.drawer.Modify(value.attribute, property); + if (isRootDrawer && result != null) + result.RemoveUsedBy(propertyHash); } } @@ -97,10 +126,11 @@ namespace Module.Inspector.Editor return EAccessType.Enabled; EAccessType accessType = 0; - + for (var i = 0; i < result.accessModifiers.Count; i++) { - EditorPropertyUtility.ResultValue value = result.accessModifiers[i]; + EditorPropertyUtility.ResultValue value = + result.accessModifiers[i]; accessType = value.drawer.GetAccessType(value.attribute, property, accessType); } diff --git a/Editor/Drawers/OpenPropertyEditorAttributeDrawer.cs b/Editor/Drawers/OpenPropertyEditorAttributeDrawer.cs new file mode 100644 index 0000000..12d7d06 --- /dev/null +++ b/Editor/Drawers/OpenPropertyEditorAttributeDrawer.cs @@ -0,0 +1,49 @@ +using Module.Inspector.Editor.Utilities; +using UnityEditor; +using UnityEngine; + +namespace Module.Inspector.Editor +{ + [CustomPropertyDrawer(typeof(OpenPropertyEditorAttribute))] + internal sealed class OpenPropertyEditorAttributeDrawer : DrawerPropertyDrawer + { + public override bool Draw(Rect position, DrawerPropertyAttribute attribute, SerializedProperty property, GUIContent label, EditorPropertyUtility.Result result) + { + if (!IsSupported(property)) + return false; + + EditorGUI.BeginProperty(position, label, property); + { + const float WIDTH = 50; + + var rect0 = new Rect(position.x, position.y, position.width - WIDTH, position.height); + var rect1 = new Rect(rect0.xMax, position.y, WIDTH, position.height); + EditorGUI.PropertyField(rect0, property, label); + + var prevValue = GUI.enabled; + GUI.enabled = prevValue && property.objectReferenceValue != null; + + if (GUI.Button(rect1, "Show")) + EditorUtility.OpenPropertyEditor(property.objectReferenceValue); + + GUI.enabled = prevValue; + } + EditorGUI.EndProperty(); + return true; + } + + public override string GetErrorMessage(SerializedProperty property) + { + return IsSupported(property) + ? "Unknown error" + : "Only supports types which inherits from ScriptableObject/Component type"; + } + + private static bool IsSupported(SerializedProperty property) + { + return property.propertyType == SerializedPropertyType.ObjectReference && + (typeof(ScriptableObject).IsAssignableFrom(property.GetValueType()) || + typeof(Component).IsAssignableFrom(property.GetValueType())); + } + } +} \ No newline at end of file diff --git a/Editor/Drawers/OpenPropertyEditorAttributeDrawer.cs.meta b/Editor/Drawers/OpenPropertyEditorAttributeDrawer.cs.meta new file mode 100644 index 0000000..0a26d98 --- /dev/null +++ b/Editor/Drawers/OpenPropertyEditorAttributeDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5fbf47456b2f487aa93a36acf40ad59b +timeCreated: 1710067726 \ No newline at end of file diff --git a/Editor/Utilities/EditorPropertyUtility.cs b/Editor/Utilities/EditorPropertyUtility.cs index 753d8f8..26a51e4 100644 --- a/Editor/Utilities/EditorPropertyUtility.cs +++ b/Editor/Utilities/EditorPropertyUtility.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using UnityEditor; using UnityEngine; namespace Module.Inspector.Editor.Utilities @@ -64,6 +65,11 @@ namespace Module.Inspector.Editor.Utilities return result; } + public static int CalculateHash(SerializedProperty property) + { + return property.propertyPath.GetHashCode(); + } + private static Result InternalFetchProperties(FieldInfo fieldInfo) { ResultValue drawer = null; @@ -192,7 +198,8 @@ namespace Module.Inspector.Editor.Utilities public readonly List> handleDrawers; public readonly string tooltip; public readonly bool isObsolete; - + private List usedBy = new List(); + public Result(ResultValue draw, List> predrawerModifiers, List> valueModifiers, @@ -212,6 +219,21 @@ namespace Module.Inspector.Editor.Utilities this.isObsolete = isObsolete; } + public bool IsUsedBy(int propertyHash) + { + return usedBy.Contains(propertyHash); + } + + public void AddUsedBy(int propertyHash) + { + usedBy.Add(propertyHash); + } + + public void RemoveUsedBy(int propertyHash) + { + usedBy.Remove(propertyHash); + } + public T GetPredrawerModifier() where T : PredrawerModifierPropertyAttribute { for (var i = 0; i < predrawerModifiers.Count; i++) diff --git a/README.md b/README.md index eca2c5d..6d072a2 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ List of all available drawer attributes: * `IntToAnimatorParameter` * Adds popup with all animator parameter names provided by animator field and converts to hash id * `IntToEnum` - * Adds a popup with enum type provided and converts it to an integer + * Adds a popup with enum type provided and converts it to an integer * `IntToLayer` * Adds a popup with a single layer selection and converts it to an integer * `IntToLayerMask` @@ -93,6 +93,9 @@ List of all available drawer attributes: * `Naming` * Adds button to apply naming scheme to string value * Types: Camel, Pascal, Snake, Snake (All caps), Kebab, Kebab (All caps) +* `OpenPropertyEditor` + * Adds a button to open a property window, if object reference is not null and either a `ScriptableObject` or `Component` + * _Note: `OpenPropertyEditor` and `ReadableScriptableObject` currently doesn't support each other_ * `Percentage` * Convert float value to percentage and back again (1% = 0.01f) * `PopupFromConst` diff --git a/Runtime/Drawers/OpenPropertyEditorAttribute.cs b/Runtime/Drawers/OpenPropertyEditorAttribute.cs new file mode 100644 index 0000000..ae7b836 --- /dev/null +++ b/Runtime/Drawers/OpenPropertyEditorAttribute.cs @@ -0,0 +1,16 @@ +#if UNITY_2019_3_OR_NEWER +using System; +using UnityEngine.Scripting; + +namespace Module.Inspector +{ + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class OpenPropertyEditorAttribute : DrawerPropertyAttribute + { + [Preserve] + public OpenPropertyEditorAttribute() + { + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Drawers/OpenPropertyEditorAttribute.cs.meta b/Runtime/Drawers/OpenPropertyEditorAttribute.cs.meta new file mode 100644 index 0000000..a7c8a82 --- /dev/null +++ b/Runtime/Drawers/OpenPropertyEditorAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d28d8d7fb04e4318989ef8e27fcb71ee +timeCreated: 1710067651 \ No newline at end of file diff --git a/package.json b/package.json index c764f50..82c7f0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.module.inspector", - "version": "1.9.1", + "version": "1.9.2", "displayName": "Module.Inspector", "description": "Custom inspector with various useful property drawers", "unity": "2019.2",