// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Editor.CustomDrawers { #if UNITY_EDITOR using UnityEditor; using UnityEngine; using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters; [CustomPropertyDrawer(typeof(SerializableNullable<>), true)] public sealed class SerializableNullablePropertyDrawer : PropertyDrawer { /// /// Calculates the vertical space the drawer needs, accounting for the nullable value field. /// /// The serialized nullable wrapper. /// The label rendered for the field. /// The height required to draw the control. /// /// /// public SerializableNullable<float> speed; /// /// public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { SerializedProperty hasValueProperty = property.FindPropertyRelative( SerializableNullableSerializedPropertyNames.HasValue ); SerializedProperty valueProperty = property.FindPropertyRelative( SerializableNullableSerializedPropertyNames.Value ); if (hasValueProperty == null) { return EditorGUI.GetPropertyHeight(property, label, true); } float lineHeight = EditorGUIUtility.singleLineHeight; if (!hasValueProperty.boolValue || valueProperty == null) { return lineHeight; } float valueHeight = EditorGUI.GetPropertyHeight(valueProperty, true); return Mathf.Max(lineHeight, valueHeight); } /// /// Draws the nullable toggle alongside the wrapped property field inside the inspector. /// /// The rectangle provided by Unity. /// The serialized nullable wrapper. /// The label shown for the field. /// /// /// EditorGUILayout.PropertyField(serializedObject.FindProperty(nameof(speed))); /// /// public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { SerializedProperty hasValueProperty = property.FindPropertyRelative( SerializableNullableSerializedPropertyNames.HasValue ); SerializedProperty valueProperty = property.FindPropertyRelative( SerializableNullableSerializedPropertyNames.Value ); if (hasValueProperty == null || valueProperty == null) { EditorGUI.PropertyField(position, property, label, true); return; } EditorGUI.BeginProperty(position, label, property); Rect fieldRect = EditorGUI.PrefixLabel( position, GUIUtility.GetControlID(FocusType.Passive), label ); int previousIndent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; float toggleWidth = EditorGUIUtility.singleLineHeight; float toggleHeight = EditorGUIUtility.singleLineHeight; float spacing = EditorGUIUtility.standardVerticalSpacing; float toggleY = position.y + (position.height - toggleHeight) * 0.5f; Rect toggleRect = new(fieldRect.x, toggleY, toggleWidth, toggleHeight); EditorGUI.BeginChangeCheck(); bool hasValue = hasValueProperty.boolValue; bool updatedHasValue = EditorGUI.Toggle(toggleRect, hasValue); if (EditorGUI.EndChangeCheck()) { hasValueProperty.boolValue = updatedHasValue; } if (updatedHasValue) { float valueWidth = Mathf.Max(0f, fieldRect.width - toggleWidth - spacing); Rect valueRect = new( toggleRect.xMax + spacing, position.y, valueWidth, position.height ); EditorGUI.PropertyField(valueRect, valueProperty, GUIContent.none, true); } EditorGUI.indentLevel = previousIndent; EditorGUI.EndProperty(); } } #endif }