// Crest Water System // Copyright © 2024 Wave Harmonic. All rights reserved. // This file is subject to the Unity Companion License: // https://github.com/Unity-Technologies/com.unity.cinemachine/blob/593fa283bee378322337e5d9f5a7b91331a45799/LICENSE.md // Lovingly adapted from Cinemachine: // https://github.com/Unity-Technologies/com.unity.cinemachine/blob/593fa283bee378322337e5d9f5a7b91331a45799/Editor/Utility/EmbeddedAssetHelpers.cs using System.Reflection; using UnityEditor; using UnityEditor.VersionControl; using UnityEngine; namespace WaveHarmonic.Crest.Editor { /// /// Interface for editors that receive an argument /// interface IEmbeddableEditor { void SetTypeOfHostComponent(System.Type hostType); } /// /// Helper for drawing embedded asset editors /// sealed class EmbeddedAssetEditor { /// /// Create in OnEnable() /// public EmbeddedAssetEditor() { _CreateButtonGUIContent = new("Create Asset", "Create a new shared settings asset"); } /// /// Called after the asset editor is created, in case it needs /// to be customized /// public OnCreateEditorDelegate _OnCreateEditor; public delegate void OnCreateEditorDelegate(UnityEditor.Editor editor); /// /// Called when the asset being edited was changed by the user. /// public OnChangedDelegate _OnChanged; public delegate void OnChangedDelegate(System.Type type, Object obj); /// /// Free the resources in OnDisable() /// public void OnDisable() { DestroyEditor(); Helpers.Destroy(_DefaultTarget); } /// /// Customize this after creation if you want /// public GUIContent _CreateButtonGUIContent; UnityEditor.Editor _Editor = null; System.Type _Type; Object _DefaultTarget; FieldInfo _DefaultTargetField; const int k_IndentOffset = 3; public void DrawEditorCombo(Embedded embedded, GUIContent label, PropertyDrawer drawer, SerializedProperty property, string extension) { _Type = drawer.fieldInfo.FieldType; DrawEditorCombo ( embedded, label, $"Create {property.displayName} Asset", $"{property.displayName.Replace(' ', '_')}", extension, string.Empty, false, property ); } /// /// Call this from OnInspectorGUI. Will draw the asset reference field, and /// the embedded editor, or a Create Asset button, if no asset is set. /// public void DrawEditorCombo ( Embedded embedded, GUIContent label, string title, string defaultName, string extension, string message, bool indent, SerializedProperty property ) { UpdateEditor(property, embedded); EditorGUI.BeginChangeCheck(); var rect = AssetField(label, property, title, defaultName, extension, message); if (EditorGUI.EndChangeCheck()) { property.serializedObject.ApplyModifiedProperties(); UpdateEditor(property, embedded); } // Display embedded editor. if (_Editor != null) { var foldoutRect = new Rect(rect.x - k_IndentOffset, rect.y, rect.width + k_IndentOffset, EditorGUIUtility.singleLineHeight); property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, GUIContent.none, true); var canEditAsset = AssetDatabase.IsOpenForEdit(_Editor.target, StatusQueryOptions.UseCachedIfPossible); // We take the current GUI state into account to support attribute stacking. var guiEnabled = GUI.enabled; GUI.enabled = guiEnabled && canEditAsset; if (property.isExpanded) { var level = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; EditorGUILayout.BeginHorizontal(); // NOTE: Tweaked for current usage but probably will not work everywhere. if (level > 0) GUILayout.Space(8 * (level + 2)); EditorGUILayout.BeginVertical(GUI.skin.box); if ((_Editor.target.hideFlags & HideFlags.NotEditable) == 0) { EditorGUILayout.HelpBox("This is a shared asset. Changes made here will apply to all users of this asset.", MessageType.Info); } EditorGUI.BeginChangeCheck(); _Editor.OnInspectorGUI(); if (EditorGUI.EndChangeCheck() && (_OnChanged != null)) _OnChanged(_Type, property.objectReferenceValue); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); if (embedded.BottomMargin > 0) { EditorGUILayout.Space(embedded.BottomMargin); } } // Enable GUI so the checkout button works. GUI.enabled = true; if (_Editor.target != null) { if (!canEditAsset && GUILayout.Button("Check out")) { var task = Provider.Checkout(AssetDatabase.GetAssetPath(_Editor.target), CheckoutMode.Asset); task.Wait(); } } // Restore stacked GUI enabled state. GUI.enabled = guiEnabled; } } Rect AssetField ( GUIContent label, SerializedProperty property, string title, string defaultName, string extension, string message ) { return EditorHelpers.AssetField ( _Type, label, property, EditorGUILayout.GetControlRect(true), title, defaultName, extension, message, x => ScriptableObject.CreateInstance(_Type) ); } public void DestroyEditor() { if (_Editor != null) { Object.DestroyImmediate(_Editor); _Editor = null; } } public void UpdateEditor(SerializedProperty property, Embedded embedded) { var target = property.objectReferenceValue; var hasDefaultField = !string.IsNullOrEmpty(embedded.DefaultPropertyName); if (target == null) { if (!hasDefaultField) { if (_DefaultTarget == null) { _DefaultTarget = ScriptableObject.CreateInstance(_Type); _DefaultTarget.hideFlags = HideFlags.DontSave | HideFlags.NotEditable; } } else { if (_DefaultTargetField == null) { _DefaultTargetField = property.serializedObject.targetObject.GetType().GetField(embedded.DefaultPropertyName, Helpers.s_AnyMethod); } // Always call, as it is dynamic. _DefaultTarget = (Object)_DefaultTargetField.GetValue(property.serializedObject.targetObject); } } if (target == null) { target = _DefaultTarget; } // Destroy the editor if target has changed. if (_Editor != null && _Editor.target != target) { DestroyEditor(); } if (_Editor != null) { return; } // NOTE: This is triggered twice on asset switch for some reason. // Create editor if need one. if (target != null) { _Editor = UnityEditor.Editor.CreateEditor(target); // Pass through argument for editors that receive it if (property.serializedObject.targetObject != null) { (_Editor as IEmbeddableEditor)?.SetTypeOfHostComponent(property.serializedObject.targetObject.GetType()); } _OnCreateEditor?.Invoke(_Editor); } } } }