// 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 UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using WallstopStudios.UnityHelpers.Core.Attributes;
using WallstopStudios.UnityHelpers.Editor.CustomDrawers.Utils;
///
/// Property drawer for that displays a warning or error
/// in the inspector when the decorated field is invalid (null, empty string, or empty collection).
///
[CustomPropertyDrawer(typeof(ValidateAssignmentAttribute))]
public sealed class ValidateAssignmentPropertyDrawer : PropertyDrawer
{
///
/// Gets the total property height including the help box when the field is invalid.
///
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
float baseHeight = EditorGUI.GetPropertyHeight(property, label, true);
if (ValidationShared.IsPropertyInvalid(property))
{
string message = GetMessage(property);
float helpBoxHeight = ValidationShared.GetHelpBoxHeight(message);
return baseHeight + helpBoxHeight + ValidationShared.HelpBoxPadding;
}
return baseHeight;
}
///
/// Draws the property field and displays a help box warning/error when the field is invalid.
///
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
try
{
bool isInvalid = ValidationShared.IsPropertyInvalid(property);
float propertyHeight = EditorGUI.GetPropertyHeight(property, label, true);
if (isInvalid)
{
string message = GetMessage(property);
float helpBoxHeight = ValidationShared.GetHelpBoxHeight(message);
Rect helpBoxRect = new(position.x, position.y, position.width, helpBoxHeight);
Rect propertyRect = new(
position.x,
position.y + helpBoxHeight + ValidationShared.HelpBoxPadding,
position.width,
propertyHeight
);
MessageType messageType = GetMessageType();
EditorGUI.HelpBox(helpBoxRect, message, messageType);
EditorGUI.PropertyField(propertyRect, property, label, true);
}
else
{
EditorGUI.PropertyField(position, property, label, true);
}
}
finally
{
EditorGUI.EndProperty();
}
}
///
/// Creates a UI Toolkit visual element for the property, including help box when invalid.
///
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
VisualElement container = new();
container.style.flexDirection = FlexDirection.Column;
HelpBox helpBox = new(GetMessage(property), GetHelpBoxMessageType());
helpBox.style.display = ValidationShared.IsPropertyInvalid(property)
? DisplayStyle.Flex
: DisplayStyle.None;
PropertyField propertyField = new(property);
propertyField.label = property.displayName;
propertyField.RegisterValueChangeCallback(evt =>
{
bool isInvalid = ValidationShared.IsPropertyInvalid(property);
helpBox.text = GetMessage(property);
helpBox.style.display = isInvalid ? DisplayStyle.Flex : DisplayStyle.None;
});
container.Add(helpBox);
container.Add(propertyField);
return container;
}
///
/// Checks if the property value is invalid (null, empty string, or empty collection).
///
/// The property to check.
/// True if the property value is invalid.
internal static bool IsPropertyInvalid(SerializedProperty property)
{
return ValidationShared.IsPropertyInvalid(property);
}
private string GetMessage(SerializedProperty property)
{
ValidateAssignmentAttribute validateAttribute =
attribute as ValidateAssignmentAttribute;
return ValidationShared.GetValidateAssignmentMessage(property, validateAttribute);
}
private MessageType GetMessageType()
{
ValidateAssignmentAttribute validateAttribute =
attribute as ValidateAssignmentAttribute;
return ValidationShared.GetMessageType(validateAttribute);
}
private HelpBoxMessageType GetHelpBoxMessageType()
{
ValidateAssignmentAttribute validateAttribute =
attribute as ValidateAssignmentAttribute;
return ValidationShared.GetHelpBoxMessageType(validateAttribute);
}
///
/// Draws a validation HelpBox for the property if it is invalid.
/// Call this from custom editors for array/list properties that won't have
/// their PropertyDrawer invoked at the array level.
///
/// True if a HelpBox was drawn, false otherwise.
internal static bool DrawValidationHelpBoxIfNeeded(
SerializedProperty property,
ValidateAssignmentAttribute validateAttribute
)
{
return ValidationShared.DrawValidateAssignmentHelpBoxIfNeeded(
property,
validateAttribute
);
}
///
/// Clears the height cache. Useful for tests or when font settings change.
///
internal static void ClearHeightCache()
{
ValidationShared.ClearHeightCache();
}
}
#endif
}