using UnityEditor; using UnityEngine; namespace Dialogue.Editor { /// /// Custom property drawer for DialogueContent that displays either text or image fields based on content type /// [CustomPropertyDrawer(typeof(DialogueContent))] public class DialogueContentDrawer : PropertyDrawer { // Height constants private const float TypeSelectorHeight = 20f; private const float PropertySpacing = 2f; // Reduced spacing for tighter layout private const float TextFieldHeight = 40f; // Taller for multi-line text private const float ImageFieldHeight = 18f; private const float AudioFieldHeight = 18f; private const float PreviewHeight = 64f; // Track the last assigned sprite to detect changes private static Sprite lastAssignedSprite; private static string lastAssignedPropertyPath; public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { var contentTypeProperty = property.FindPropertyRelative("_contentType"); var imageProperty = property.FindPropertyRelative("_image"); // Start with base height for type selector var height = TypeSelectorHeight + PropertySpacing; // Add height based on content type if (contentTypeProperty.enumValueIndex == (int)DialogueContentType.Text) { height += TextFieldHeight + PropertySpacing; } else // Image { height += ImageFieldHeight + PropertySpacing; // Add preview height if an image is assigned - make sure it's AFTER the image field // but BEFORE the audio field if (imageProperty.objectReferenceValue != null) { height += PreviewHeight + PropertySpacing; } } // Add height for audio field (always displayed) height += AudioFieldHeight; return height; } public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { EditorGUI.BeginProperty(position, label, property); // Create a property field and indent it var contentRect = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); var indent = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; // Get properties var contentTypeProperty = property.FindPropertyRelative("_contentType"); var textProperty = property.FindPropertyRelative("_text"); var imageProperty = property.FindPropertyRelative("_image"); var audioProperty = property.FindPropertyRelative("_audio"); // Check for sprite changes and force repaint if needed var currentSprite = imageProperty.objectReferenceValue as Sprite; if (currentSprite != lastAssignedSprite && property.propertyPath == lastAssignedPropertyPath) { // Sprite changed, force a layout recalculation EditorUtility.SetDirty(property.serializedObject.targetObject); GUI.changed = true; } // Track current y position as we add controls float currentY = contentRect.y; // Calculate type rect var typeRect = new Rect(contentRect.x, currentY, contentRect.width, TypeSelectorHeight); currentY += TypeSelectorHeight + PropertySpacing; // Calculate content field rect based on the content type var contentHeight = contentTypeProperty.enumValueIndex == (int)DialogueContentType.Text ? TextFieldHeight : ImageFieldHeight; var contentFieldRect = new Rect( contentRect.x, currentY, contentRect.width, contentHeight); currentY += contentHeight + PropertySpacing; // If we have an image and it's selected, calculate preview rect Rect previewRect = Rect.zero; if (contentTypeProperty.enumValueIndex == (int)DialogueContentType.Image && imageProperty.objectReferenceValue != null) { previewRect = new Rect( contentRect.x, currentY, contentRect.width, PreviewHeight); currentY += PreviewHeight + PropertySpacing; } // Calculate audio field rect var audioFieldRect = new Rect( contentRect.x, currentY, contentRect.width, AudioFieldHeight); // Now draw all the controls // Draw the content type dropdown EditorGUI.PropertyField(typeRect, contentTypeProperty, GUIContent.none); // Draw the appropriate field based on content type if (contentTypeProperty.enumValueIndex == (int)DialogueContentType.Text) { // Create a custom style with word wrap enabled GUIStyle wordWrapStyle = new GUIStyle(EditorStyles.textArea); wordWrapStyle.wordWrap = true; // Text field with word wrap for multi-line input textProperty.stringValue = EditorGUI.TextArea(contentFieldRect, textProperty.stringValue, wordWrapStyle); } else // Image { // Store the sprite before drawing the field to detect changes var previousSprite = imageProperty.objectReferenceValue as Sprite; // Draw the image field EditorGUI.BeginChangeCheck(); EditorGUI.PropertyField(contentFieldRect, imageProperty, GUIContent.none); if (EditorGUI.EndChangeCheck()) { // Image changed, store the property path so we can detect which property changed lastAssignedPropertyPath = property.propertyPath; lastAssignedSprite = imageProperty.objectReferenceValue as Sprite; // Force an inspector update to recalculate layout EditorUtility.SetDirty(property.serializedObject.targetObject); // Mark the scene as dirty to ensure serialization and proper layout refresh if (!EditorApplication.isPlaying) { UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty( UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene()); } } // Draw a preview if an image is assigned if (imageProperty.objectReferenceValue != null) { var sprite = imageProperty.objectReferenceValue as Sprite; if (sprite != null) { // Draw the preview with preserved aspect ratio DrawSpritePreview(previewRect, sprite); } } } // Draw the audio field (always displayed regardless of content type) EditorGUI.PropertyField(audioFieldRect, audioProperty, new GUIContent("Audio")); // Restore indent EditorGUI.indentLevel = indent; EditorGUI.EndProperty(); } // Helper method to draw a sprite preview with preserved aspect ratio private void DrawSpritePreview(Rect rect, Sprite sprite) { if (sprite == null || sprite.texture == null) return; var texture = sprite.texture; var spriteRect = sprite.rect; var aspectRatio = spriteRect.width / spriteRect.height; // Calculate preview rect while preserving aspect ratio within our fixed area Rect previewRect = rect; // Limit the display size to the allocated space while maintaining aspect ratio if (aspectRatio > 1f) // Wider than tall { previewRect.height = Mathf.Min(rect.width / aspectRatio, rect.height); previewRect.y += (rect.height - previewRect.height) * 0.5f; } else // Taller than wide or square { previewRect.width = Mathf.Min(rect.height * aspectRatio, rect.width); previewRect.x += (rect.width - previewRect.width) * 0.5f; } // Constrain the preview to the allocated space previewRect.height = Mathf.Min(previewRect.height, rect.height); previewRect.width = Mathf.Min(previewRect.width, rect.width); // Draw preview with a dark background for better visibility EditorGUI.DrawRect(rect, new Color(0.1f, 0.1f, 0.1f, 1f)); GUI.DrawTexture(previewRect, texture, ScaleMode.ScaleToFit); // Draw sprite bounds EditorGUI.DrawRect(new Rect(previewRect.x, previewRect.y, previewRect.width, 1), Color.gray); EditorGUI.DrawRect(new Rect(previewRect.x, previewRect.y + previewRect.height - 1, previewRect.width, 1), Color.gray); EditorGUI.DrawRect(new Rect(previewRect.x, previewRect.y, 1, previewRect.height), Color.gray); EditorGUI.DrawRect(new Rect(previewRect.x + previewRect.width - 1, previewRect.y, 1, previewRect.height), Color.gray); } } }