using UnityEngine; using UnityEditor; using System; using Cinemachine.Utility; using System.Linq; using System.Collections.Generic; namespace Cinemachine.Editor { [CustomPropertyDrawer(typeof(CinemachineEmbeddedAssetPropertyAttribute))] internal sealed class EmbeddedAssetPropertyDrawer : PropertyDrawer { const float vSpace = 2; const float kIndentAmount = 15; const float kBoxMargin = 3; float HelpBoxHeight { get { return EditorGUIUtility.singleLineHeight * 2.5f; } } bool mExpanded = false; bool WarnIfNull { get { var attr = attribute as CinemachineEmbeddedAssetPropertyAttribute; return attr == null ? false : attr.WarnIfNull; } } float HeaderHeight { get { return EditorGUIUtility.singleLineHeight * 1.5f; } } float DrawHeader(Rect rect, string text) { float delta = HeaderHeight - EditorGUIUtility.singleLineHeight; rect.y += delta; rect.height -= delta; EditorGUI.LabelField(rect, new GUIContent(text), EditorStyles.boldLabel); return HeaderHeight; } string HeaderText(SerializedProperty property) { var attrs = property.serializedObject.targetObject.GetType() .GetCustomAttributes(typeof(HeaderAttribute), false); if (attrs != null && attrs.Length > 0) return ((HeaderAttribute)attrs[0]).header; return null; } bool AssetHasCustomEditor(SerializedProperty property) { ScriptableObject asset = property.objectReferenceValue as ScriptableObject; if (asset != null) return ActiveEditorTracker.HasCustomEditor(asset); return false; } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { bool hasCustomEditor = AssetHasCustomEditor(property); float height = base.GetPropertyHeight(property, label); height += + 2 * (kBoxMargin + vSpace); if (mExpanded && !hasCustomEditor) { height += HelpBoxHeight + kBoxMargin; ScriptableObject asset = property.objectReferenceValue as ScriptableObject; if (asset != null) { SerializedObject so = new SerializedObject(asset); var prop = so.GetIterator(); prop.NextVisible(true); do { if (prop.name == "m_Script") continue; string header = HeaderText(prop); if (header != null) height += HeaderHeight + vSpace; height += EditorGUI.GetPropertyHeight(prop, false) + vSpace; } while (prop.NextVisible(prop.isExpanded)); height += kBoxMargin; } } return height; } public override void OnGUI(Rect rect, SerializedProperty property, GUIContent label) { bool hasCustomEditor = AssetHasCustomEditor(property); rect.y += vSpace; rect.height -= 2 * vSpace; GUI.Box(rect, GUIContent.none, GUI.skin.box); rect.y += kBoxMargin; rect.height = EditorGUIUtility.singleLineHeight; EditorGUIUtility.labelWidth -= kBoxMargin; AssetFieldWithCreateButton( new Rect(rect.x + kBoxMargin, rect.y, rect.width - 2 * kBoxMargin, rect.height), property, WarnIfNull, property.serializedObject.targetObject.name + " " + label.text); ScriptableObject asset = property.objectReferenceValue as ScriptableObject; if (asset != null && !hasCustomEditor) { mExpanded = EditorGUI.Foldout(rect, mExpanded, GUIContent.none, true); if (mExpanded) { rect.y += rect.height + kBoxMargin + vSpace; rect.x += kIndentAmount + kBoxMargin; rect.width -= kIndentAmount + 2 * kBoxMargin; EditorGUIUtility.labelWidth -= kIndentAmount; EditorGUI.HelpBox( new Rect(rect.x, rect.y, rect.width, HelpBoxHeight), "This is a shared asset.\n" + "Changes made here will apply to all users of this asset", MessageType.Info); rect.y += HelpBoxHeight + kBoxMargin; SerializedObject so = new SerializedObject(property.objectReferenceValue); var prop = so.GetIterator(); prop.NextVisible(true); var indent = EditorGUI.indentLevel; do { if (prop.name == "m_Script") continue; string header = HeaderText(prop); if (header != null) { DrawHeader(rect, header); rect.y += HeaderHeight + vSpace; rect.height -= HeaderHeight + vSpace; } rect.height = EditorGUI.GetPropertyHeight(prop, false); EditorGUI.indentLevel = indent + prop.depth; EditorGUI.PropertyField(rect, prop); rect.y += rect.height + vSpace; } while (prop.NextVisible(prop.isExpanded)); if (GUI.changed) so.ApplyModifiedProperties(); } EditorGUIUtility.labelWidth += kIndentAmount; } EditorGUIUtility.labelWidth += kBoxMargin; } Type EmbeddedAssetType(SerializedProperty property) { Type type = property.serializedObject.targetObject.GetType(); var a = property.propertyPath.Split('.'); for (int i = 0; i < a.Length; ++i) { var field = type.GetField(a[i], System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (field == null) continue; type = field.FieldType; } return type; } Type[] mAssetTypes = null; List mAssetPresets; GUIContent[] mAssetPresetNames; void RebuildPresetList() { if (mAssetPresets != null && mAssetPresetNames != null) return; mAssetPresets = new List(); if (mAssetTypes != null) { for (int i = 0; i < mAssetTypes.Length; ++i) InspectorUtility.AddAssetsFromPackageSubDirectory( mAssetTypes[i], mAssetPresets, "Presets/Noise"); } List presetNameList = new List(); foreach (var n in mAssetPresets) presetNameList.Add(new GUIContent("Presets/" + n.name)); mAssetPresetNames = presetNameList.ToArray(); } void AssetFieldWithCreateButton( Rect r, SerializedProperty property, bool warnIfNull, string defaultName) { // Collect all the eligible asset types Type type = EmbeddedAssetType(property); if (mAssetTypes == null) mAssetTypes = ReflectionHelpers.GetTypesInAllDependentAssemblies( (Type t) => type.IsAssignableFrom(t) && !t.IsAbstract).ToArray(); float iconSize = r.height + 4; r.width -= iconSize; GUIContent label = new GUIContent(property.displayName, property.tooltip); if (warnIfNull && property.objectReferenceValue == null) label.image = EditorGUIUtility.IconContent("console.warnicon.sml").image; EditorGUI.PropertyField(r, property, label); r.x += r.width; r.width = iconSize; r.height = iconSize; if (GUI.Button(r, EditorGUIUtility.IconContent("_Popup"), GUI.skin.label)) { GenericMenu menu = new GenericMenu(); if (property.objectReferenceValue != null) { menu.AddItem(new GUIContent("Edit"), false, () => Selection.activeObject = property.objectReferenceValue); menu.AddItem(new GUIContent("Clone"), false, () => { ScriptableObject copyFrom = property.objectReferenceValue as ScriptableObject; if (copyFrom != null) { string title = "Create New " + copyFrom.GetType().Name + " asset"; ScriptableObject asset = CreateAsset( copyFrom.GetType(), copyFrom, defaultName, title); if (asset != null) { property.objectReferenceValue = asset; property.serializedObject.ApplyModifiedProperties(); } } }); menu.AddItem(new GUIContent("Locate"), false, () => EditorGUIUtility.PingObject(property.objectReferenceValue)); } RebuildPresetList(); int i = 0; foreach (var a in mAssetPresets) { menu.AddItem(mAssetPresetNames[i++], false, () => { property.objectReferenceValue = a; property.serializedObject.ApplyModifiedProperties(); }); } foreach (var t in mAssetTypes) { menu.AddItem(new GUIContent("New " + InspectorUtility.NicifyClassName(t.Name)), false, () => { string title = "Create New " + t.Name + " asset"; ScriptableObject asset = CreateAsset(t, null, defaultName, title); if (asset != null) { property.objectReferenceValue = asset; property.serializedObject.ApplyModifiedProperties(); } }); } menu.ShowAsContext(); } } ScriptableObject CreateAsset( Type assetType, ScriptableObject copyFrom, string defaultName, string dialogTitle) { ScriptableObject asset = null; string path = EditorUtility.SaveFilePanelInProject( dialogTitle, defaultName, "asset", string.Empty); if (!string.IsNullOrEmpty(path)) { if (copyFrom != null) { string fromPath = AssetDatabase.GetAssetPath(copyFrom); if (AssetDatabase.CopyAsset(fromPath, path)) asset = AssetDatabase.LoadAssetAtPath(path, assetType) as ScriptableObject; } else { asset = ScriptableObjectUtility.CreateAt(assetType, path); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } return asset; } } }