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
This commit is contained in:
Anders Ejlersen 2024-03-10 14:11:49 +01:00
parent f609ba6f51
commit c87dd743f6
8 changed files with 189 additions and 63 deletions

View file

@ -1,4 +1,5 @@
using Module.Inspector.Editor.Utilities; using System;
using Module.Inspector.Editor.Utilities;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
@ -8,79 +9,107 @@ namespace Module.Inspector.Editor
{ {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{ {
EditorPropertyUtility.Result result = EditorPropertyUtility.Query(fieldInfo); EditorPropertyUtility.Result result = null;
EAccessType accessType = GetAccessType(property, result); int propertyHash = EditorPropertyUtility.CalculateHash(property);
bool isRootDrawer = false;
if (accessType == EAccessType.Hidden) try
return;
bool prevEnabled = GUI.enabled;
GUI.enabled = prevEnabled && accessType == EAccessType.Enabled;
if (Event.current.type == EventType.Repaint)
{ {
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<HandleDrawerPropertyAttribute, HandlePropertyDrawer> value = result.handleDrawers[i]; EditorGUI.PropertyField(position, property, label);
HandleDrawerEditor.Add(value.attribute, property, value.drawer); return;
} }
}
label.tooltip = result.tooltip; isRootDrawer = true;
result.AddUsedBy(propertyHash);
bool prevEnabled = GUI.enabled;
GUI.enabled = prevEnabled && accessType == EAccessType.Enabled;
for (var i = 0; i < result.predrawerModifiers.Count; i++) if (Event.current.type == EventType.Repaint)
{ {
EditorPropertyUtility.ResultValue<PredrawerModifierPropertyAttribute, PredrawerModifierPropertyDrawer> value = result.predrawerModifiers[i]; for (var i = 0; i < result.handleDrawers.Count; i++)
value.drawer.Modify(value.attribute, property, label); {
} EditorPropertyUtility.ResultValue<HandleDrawerPropertyAttribute, HandlePropertyDrawer> value =
result.handleDrawers[i];
HandleDrawerEditor.Add(value.attribute, property, value.drawer);
}
}
var isValid = true; label.tooltip = result.tooltip;
var validationError = string.Empty;
for (var i = 0; i < result.validators.Count; i++) for (var i = 0; i < result.predrawerModifiers.Count; i++)
{ {
EditorPropertyUtility.ResultValue<ValidatePropertyAttribute, ValidatePropertyDrawer> value = result.validators[i]; EditorPropertyUtility.ResultValue<PredrawerModifierPropertyAttribute,
PredrawerModifierPropertyDrawer> value = result.predrawerModifiers[i];
value.drawer.Modify(value.attribute, property, label);
}
if (value.drawer.Validate(value.attribute, property)) var isValid = true;
continue; var validationError = string.Empty;
validationError += value.drawer.GetValidationError(value.attribute, property); for (var i = 0; i < result.validators.Count; i++)
isValid = false; {
} EditorPropertyUtility.ResultValue<ValidatePropertyAttribute, ValidatePropertyDrawer> value =
result.validators[i];
Color prevColor = GUI.color; if (value.drawer.Validate(value.attribute, property))
continue;
if (!isValid) validationError += value.drawer.GetValidationError(value.attribute, property);
{ isValid = false;
if (string.IsNullOrEmpty(label.tooltip)) }
label.tooltip = validationError;
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 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; EditorGUI.PropertyField(position, property, label, true);
var errorContent = new GUIContent(result.draw.drawer.GetErrorMessage(property)); }
EditorGUI.LabelField(position, label, errorContent);
GUI.color = prevColor;
GUI.enabled = prevEnabled;
for (var i = 0; i < result.valueModifiers.Count; i++)
{
EditorPropertyUtility.ResultValue<ValueModifierPropertyAttribute, ValueModifierPropertyDrawer>
value = result.valueModifiers[i];
value.drawer.Modify(value.attribute, property);
} }
} }
else catch (Exception)
{ {
EditorGUI.PropertyField(position, property, label, true); // Ignore
} }
finally
GUI.color = prevColor;
GUI.enabled = prevEnabled;
for (var i = 0; i < result.valueModifiers.Count; i++)
{ {
EditorPropertyUtility.ResultValue<ValueModifierPropertyAttribute, ValueModifierPropertyDrawer> value = result.valueModifiers[i]; if (isRootDrawer && result != null)
value.drawer.Modify(value.attribute, property); result.RemoveUsedBy(propertyHash);
} }
} }
@ -100,7 +129,8 @@ namespace Module.Inspector.Editor
for (var i = 0; i < result.accessModifiers.Count; i++) for (var i = 0; i < result.accessModifiers.Count; i++)
{ {
EditorPropertyUtility.ResultValue<AccessModifierPropertyAttribute, AccessModifierPropertyDrawer> value = result.accessModifiers[i]; EditorPropertyUtility.ResultValue<AccessModifierPropertyAttribute, AccessModifierPropertyDrawer> value =
result.accessModifiers[i];
accessType = value.drawer.GetAccessType(value.attribute, property, accessType); accessType = value.drawer.GetAccessType(value.attribute, property, accessType);
} }

View file

@ -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()));
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5fbf47456b2f487aa93a36acf40ad59b
timeCreated: 1710067726

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection; using System.Reflection;
using UnityEditor;
using UnityEngine; using UnityEngine;
namespace Module.Inspector.Editor.Utilities namespace Module.Inspector.Editor.Utilities
@ -64,6 +65,11 @@ namespace Module.Inspector.Editor.Utilities
return result; return result;
} }
public static int CalculateHash(SerializedProperty property)
{
return property.propertyPath.GetHashCode();
}
private static Result InternalFetchProperties(FieldInfo fieldInfo) private static Result InternalFetchProperties(FieldInfo fieldInfo)
{ {
ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer> drawer = null; ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer> drawer = null;
@ -192,6 +198,7 @@ namespace Module.Inspector.Editor.Utilities
public readonly List<ResultValue<HandleDrawerPropertyAttribute, HandlePropertyDrawer>> handleDrawers; public readonly List<ResultValue<HandleDrawerPropertyAttribute, HandlePropertyDrawer>> handleDrawers;
public readonly string tooltip; public readonly string tooltip;
public readonly bool isObsolete; public readonly bool isObsolete;
private List<int> usedBy = new List<int>();
public Result(ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer> draw, public Result(ResultValue<DrawerPropertyAttribute, DrawerPropertyDrawer> draw,
List<ResultValue<PredrawerModifierPropertyAttribute, PredrawerModifierPropertyDrawer>> predrawerModifiers, List<ResultValue<PredrawerModifierPropertyAttribute, PredrawerModifierPropertyDrawer>> predrawerModifiers,
@ -212,6 +219,21 @@ namespace Module.Inspector.Editor.Utilities
this.isObsolete = isObsolete; 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<T>() where T : PredrawerModifierPropertyAttribute public T GetPredrawerModifier<T>() where T : PredrawerModifierPropertyAttribute
{ {
for (var i = 0; i < predrawerModifiers.Count; i++) for (var i = 0; i < predrawerModifiers.Count; i++)

View file

@ -83,7 +83,7 @@ List of all available drawer attributes:
* `IntToAnimatorParameter` * `IntToAnimatorParameter`
* Adds popup with all animator parameter names provided by animator field and converts to hash id * Adds popup with all animator parameter names provided by animator field and converts to hash id
* `IntToEnum` * `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` * `IntToLayer`
* Adds a popup with a single layer selection and converts it to an integer * Adds a popup with a single layer selection and converts it to an integer
* `IntToLayerMask` * `IntToLayerMask`
@ -93,6 +93,9 @@ List of all available drawer attributes:
* `Naming` * `Naming`
* Adds button to apply naming scheme to string value * Adds button to apply naming scheme to string value
* Types: Camel, Pascal, Snake, Snake (All caps), Kebab, Kebab (All caps) * 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` * `Percentage`
* Convert float value to percentage and back again (1% = 0.01f) * Convert float value to percentage and back again (1% = 0.01f)
* `PopupFromConst` * `PopupFromConst`

View file

@ -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

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d28d8d7fb04e4318989ef8e27fcb71ee
timeCreated: 1710067651

View file

@ -1,6 +1,6 @@
{ {
"name": "com.module.inspector", "name": "com.module.inspector",
"version": "1.9.1", "version": "1.9.2",
"displayName": "Module.Inspector", "displayName": "Module.Inspector",
"description": "Custom inspector with various useful property drawers", "description": "Custom inspector with various useful property drawers",
"unity": "2019.2", "unity": "2019.2",