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