diff --git a/Editor/AbstractPropertyDrawer.cs b/Editor/AbstractPropertyDrawer.cs index 2dbde4e..e0b5ccf 100644 --- a/Editor/AbstractPropertyDrawer.cs +++ b/Editor/AbstractPropertyDrawer.cs @@ -15,7 +15,7 @@ namespace Module.Inspector.Editor return; bool prevEnabled = GUI.enabled; - GUI.enabled = accessType == EAccessType.Enabled; + GUI.enabled = prevEnabled && accessType == EAccessType.Enabled; label.tooltip = result.tooltip; diff --git a/Editor/Drawers/ReadableScriptableObjectAttributeDrawer.cs b/Editor/Drawers/ReadableScriptableObjectAttributeDrawer.cs new file mode 100644 index 0000000..812da6a --- /dev/null +++ b/Editor/Drawers/ReadableScriptableObjectAttributeDrawer.cs @@ -0,0 +1,159 @@ +using Module.Inspector.Editor.Utilities; +using UnityEditor; +using UnityEngine; + +namespace Module.Inspector.Editor +{ + [CustomPropertyDrawer(typeof(ReadableScriptableObjectAttribute))] + internal sealed class ReadableScriptableObjectAttributeDrawer : DrawerPropertyDrawer + { + public override bool Draw(Rect position, DrawerPropertyAttribute attribute, SerializedProperty property, GUIContent label, EditorPropertyUtility.Result result) + { + if (!IsSupported(property)) + return false; + + var att = (ReadableScriptableObjectAttribute)attribute; + + EditorGUI.BeginChangeCheck(); + EditorGUI.BeginProperty(position, label, property); + + { + if (property.objectReferenceValue != null) + { + property.isExpanded = DrawFoldout(position, property, label); + + if (property.isExpanded) + DrawContent(position, property.objectReferenceValue, att.editable); + } + else + { + // Note: Why not use EditorGUI.PropertyField? + // The reason is that the foldout uses Toggle and ObjectField and if if we don't use + // Label and ObjectField here, then ObjectField will be given a new control id when changing + // objectReferenceValue from an existing object to null and back again. The new object will + // not be set, due to null giving a new control id. + var rectObj = new Rect + { + x = position.x + EditorGUIUtility.labelWidth + 2.0f, + y = position.y, + width = position.width - EditorGUIUtility.labelWidth - 2.0f, + height = position.height + }; + + GUI.Label(position, label); + property.objectReferenceValue = EditorGUI.ObjectField(rectObj, property.objectReferenceValue, property.GetValueType(), false); + } + } + + EditorGUI.EndProperty(); + bool hasChanged = EditorGUI.EndChangeCheck(); + + if (hasChanged) + property.serializedObject.ApplyModifiedProperties(); + + return true; + } + + private static bool DrawFoldout(Rect position, SerializedProperty property, GUIContent label) + { + // Note: Why not use EditorGUI.BeginFoldoutHeaderGroup? + // FoldoutHeaderGroup doesn't support nesting, so any ScriptableObject with a field that uses + // a FoldoutHeaderGroup will not be drawn, e.g. arrays. + + const float FOLDOUT_OFFSET = 14.0f; + const float FIELD_OFFSET = 16.0f; + + position.x -= FOLDOUT_OFFSET; + position.width += FOLDOUT_OFFSET; + position.height = EditorGUIUtility.singleLineHeight; + + var rectObj = new Rect + { + x = position.x + EditorGUIUtility.labelWidth + FIELD_OFFSET, + y = position.y, + width = position.width - EditorGUIUtility.labelWidth - FIELD_OFFSET, + height = position.height + }; + + GUIStyle style = property.objectReferenceValue != null ? EditorStyles.foldoutHeader : EditorStyles.label; + bool foldout = property.isExpanded; + + if (Event.current.type == EventType.Repaint) + { + GUI.Toggle(position, foldout, label, style); + EditorGUI.ObjectField(rectObj, property.objectReferenceValue, property.GetValueType(), false); + } + else + { + property.objectReferenceValue = EditorGUI.ObjectField(rectObj, property.objectReferenceValue, property.GetValueType(), false); + foldout = GUI.Toggle(position, foldout, label, style); + } + + return foldout; + } + + private static void DrawContent(Rect rect, Object obj, bool editable) + { + var serializedObject = new SerializedObject(obj); + SerializedProperty it = serializedObject.GetIterator(); + + it.NextVisible(true); + rect.y += EditorGUIUtility.singleLineHeight; + + EditorGUI.indentLevel++; + EditorGUI.BeginChangeCheck(); + + { + bool prevEnabled = GUI.enabled; + GUI.enabled = prevEnabled && editable; + + while (it.NextVisible(false)) + { + rect.height = EditorGUI.GetPropertyHeight(it); + EditorGUI.PropertyField(rect, it, new GUIContent(it.displayName), true); + rect.y += rect.height; + } + + GUI.enabled = prevEnabled; + } + + bool hasChanged = EditorGUI.EndChangeCheck(); + EditorGUI.indentLevel--; + + if (hasChanged) + serializedObject.ApplyModifiedProperties(); + } + + public override string GetErrorMessage(SerializedProperty property) + { + return IsSupported(property) + ? "Unknown error" + : "Only supports types which inherits from ScriptableObject type"; + } + + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) + { + if (property.objectReferenceValue == null || !property.isExpanded) + return base.GetPropertyHeight(property, label); + + var so = new SerializedObject(property.objectReferenceValue); + SerializedProperty it = so.GetIterator(); + var height = 0.0f; + it.NextVisible(true); + height += EditorGUI.GetPropertyHeight(it); + + while (it.NextVisible(false)) + { + height += EditorGUI.GetPropertyHeight(it); + } + + return height; + } + + private static bool IsSupported(SerializedProperty property) + { + return property.propertyType == SerializedPropertyType.ObjectReference && + typeof(ScriptableObject).IsAssignableFrom(property.GetValueType()); + } + } +} \ No newline at end of file diff --git a/Editor/Drawers/ReadableScriptableObjectAttributeDrawer.cs.meta b/Editor/Drawers/ReadableScriptableObjectAttributeDrawer.cs.meta new file mode 100644 index 0000000..633b50b --- /dev/null +++ b/Editor/Drawers/ReadableScriptableObjectAttributeDrawer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 63160a5efc0643de9a3cb8058efb1038 +timeCreated: 1639910668 \ No newline at end of file diff --git a/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs b/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs index 2a71359..d1ff0a0 100644 --- a/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs +++ b/Editor/Drawers/SerializableReferenceToAttributeDrawer.cs @@ -114,32 +114,8 @@ namespace Module.Inspector.Editor return false; if (typeof(Object).IsAssignableFrom(type)) return false; - if (type.IsGenericType) - return false; - - return true; - } - - private static string ConvertUnityTypeFullnameToCSharpFullname(string unityNaming) - { - if (string.IsNullOrEmpty(unityNaming)) - return string.Empty; - int index = unityNaming.LastIndexOf(' '); - string str; - - if (index != -1) - { - str = unityNaming.Substring(0, index).Trim(); - str += ", "; - str += unityNaming.Substring(index).Trim(); - } - else - { - str = unityNaming; - } - - return str.Replace('/', '+'); + return !type.IsGenericType; } private static string ConvertUnityTypeFullnameToCSharpName(string unityNaming) diff --git a/README.md b/README.md index 4a25df5..e09003b 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,12 @@ List of all available drawer attributes: * Adds popup with all const fields of type string in provided type * `QuaternionToEuler` * Converts quaternion value to Euler angles and back again +* `ReadableScriptableObject` + * Creates an inline editor in the inspector window, so you can edit `ScriptableObjects` without finding the object first * `SceneDropdown` * Adds a popup with all scenes in EditorBuildSettings scenes +* `SerializeReferenceTo` + * Adds a popup for `SerializeReference` fields with types inheriting from assigned type or field type * `Slider` * Adds a min & max slider and clamps value (Requirement: MinValue and MaxValue) * `StringToAnimatorParameter` @@ -105,8 +109,7 @@ List of all available drawer attributes: * Adds popup with all tag values for field of type string * `UrlGoTo` * Adds a button to the field that calls Application.OpenUrl with string value -* `SerializeReferenceTo` - * Adds a popup for `SerializeReference` fields with types inheriting from assigned type or field type + ## Value @@ -115,6 +118,8 @@ List of all value attributes: * `ArrayIndex` * Clamps value between other fields array size `[0;size[` +* `AssignIfNull` + * If value is null will either use field or custom type to get from self or children * `LargerThanField` * If value is greater than other fields value, then set value to other fields value * `MaxValue` @@ -123,8 +128,7 @@ List of all value attributes: * If value is less than min value, then set value to min value * `SmallerThanField` * If value is less than other fields value, then set value to other fields value -* `AssignIfNull` - * If value is null will either use field or custom type to get from self or children + ## Validate diff --git a/Runtime/Drawers/ReadableScriptableObjectAttribute.cs b/Runtime/Drawers/ReadableScriptableObjectAttribute.cs new file mode 100644 index 0000000..8bcfeba --- /dev/null +++ b/Runtime/Drawers/ReadableScriptableObjectAttribute.cs @@ -0,0 +1,22 @@ +#if UNITY_2019_3_OR_NEWER +using System; + +namespace Module.Inspector +{ + [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = true)] + public sealed class ReadableScriptableObjectAttribute : DrawerPropertyAttribute + { + public readonly bool editable; + + public ReadableScriptableObjectAttribute() + { + editable = true; + } + + public ReadableScriptableObjectAttribute(bool editable) + { + this.editable = editable; + } + } +} +#endif \ No newline at end of file diff --git a/Runtime/Drawers/ReadableScriptableObjectAttribute.cs.meta b/Runtime/Drawers/ReadableScriptableObjectAttribute.cs.meta new file mode 100644 index 0000000..62a2ba1 --- /dev/null +++ b/Runtime/Drawers/ReadableScriptableObjectAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 319dfc0b017247d69fbe91575384ccf8 +timeCreated: 1639910607 \ No newline at end of file diff --git a/package.json b/package.json index 3172722..55ac4b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "com.module.inspector", - "version": "1.3.2", + "version": "1.4.0", "displayName": "Module.Inspector", "description": "Custom inspector with various useful property drawers", "unity": "2019.2",