#if UNITY_2019_3_OR_NEWER using System; using System.Linq; using System.Collections.Generic; using UnityEngine; using UnityEditor; using UnityEditor.IMGUI.Controls; namespace MackySoft.SerializeReferenceExtensions.Editor { [CustomPropertyDrawer(typeof(SubclassSelectorAttribute))] public class SubclassSelectorDrawer : PropertyDrawer { struct TypePopupCache { public AdvancedTypePopup TypePopup { get; } public AdvancedDropdownState State { get; } public TypePopupCache (AdvancedTypePopup typePopup,AdvancedDropdownState state) { TypePopup = typePopup; State = state; } } const int k_MaxTypePopupLineCount = 13; static readonly Type k_UnityObjectType = typeof(UnityEngine.Object); static readonly GUIContent k_NullDisplayName = new GUIContent(TypeMenuUtility.k_NullDisplayName); static readonly GUIContent k_IsNotManagedReferenceLabel = new GUIContent("The property type is not manage reference."); readonly Dictionary m_TypePopups = new Dictionary(); readonly Dictionary m_TypeNameCaches = new Dictionary(); SerializedProperty m_TargetProperty; public override void OnGUI (Rect position,SerializedProperty property,GUIContent label) { EditorGUI.BeginProperty(position,label,property); if (property.propertyType == SerializedPropertyType.ManagedReference) { // render label first to avoid label overlap for lists Rect foldoutLabelRect = new Rect(position); foldoutLabelRect.height = EditorGUIUtility.singleLineHeight; foldoutLabelRect.x += EditorGUI.indentLevel * 12; Rect popupPosition = EditorGUI.PrefixLabel(foldoutLabelRect, label); // Draw the subclass selector popup. if (EditorGUI.DropdownButton(popupPosition,GetTypeName(property),FocusType.Keyboard)) { TypePopupCache popup = GetTypePopup(property); m_TargetProperty = property; popup.TypePopup.Show(popupPosition); } // Check if a custom property drawer exists for this type. PropertyDrawer customDrawer = GetCustomPropertyDrawer(property); if (customDrawer != null) { // Draw the property with custom property drawer. Rect foldoutRect = new Rect(position); foldoutRect.height = EditorGUIUtility.singleLineHeight; property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, GUIContent.none, true); if (property.isExpanded) { using (new EditorGUI.IndentLevelScope()) { Rect indentedRect = position; float foldoutDifference = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; indentedRect.height = customDrawer.GetPropertyHeight(property, label); indentedRect.y += foldoutDifference; customDrawer.OnGUI(indentedRect, property, label); } } } else { EditorGUI.PropertyField(position, property, GUIContent.none, true); } } else { EditorGUI.LabelField(position,label,k_IsNotManagedReferenceLabel); } EditorGUI.EndProperty(); } PropertyDrawer GetCustomPropertyDrawer (SerializedProperty property) { Type propertyType = ManagedReferenceUtility.GetType(property.managedReferenceFullTypename); if (propertyType != null && PropertyDrawerCache.TryGetPropertyDrawer(propertyType, out PropertyDrawer drawer)) { return drawer; } return null; } TypePopupCache GetTypePopup (SerializedProperty property) { // Cache this string. This property internally call Assembly.GetName, which result in a large allocation. string managedReferenceFieldTypename = property.managedReferenceFieldTypename; if (!m_TypePopups.TryGetValue(managedReferenceFieldTypename,out TypePopupCache result)) { var state = new AdvancedDropdownState(); Type baseType = ManagedReferenceUtility.GetType(managedReferenceFieldTypename); var popup = new AdvancedTypePopup( TypeCache.GetTypesDerivedFrom(baseType).Append(baseType).Where(p => (p.IsPublic || p.IsNestedPublic) && !p.IsAbstract && !p.IsGenericType && !k_UnityObjectType.IsAssignableFrom(p) && Attribute.IsDefined(p,typeof(SerializableAttribute)) ), k_MaxTypePopupLineCount, state ); popup.OnItemSelected += item => { Type type = item.Type; // Apply changes to individual serialized objects. foreach (var targetObject in m_TargetProperty.serializedObject.targetObjects) { SerializedObject individualObject = new SerializedObject(targetObject); SerializedProperty individualProperty = individualObject.FindProperty(m_TargetProperty.propertyPath); object obj = individualProperty.SetManagedReference(type); individualProperty.isExpanded = (obj != null); individualObject.ApplyModifiedProperties(); individualObject.Update(); } }; result = new TypePopupCache(popup, state); m_TypePopups.Add(managedReferenceFieldTypename, result); } return result; } GUIContent GetTypeName (SerializedProperty property) { // Cache this string. string managedReferenceFullTypename = property.managedReferenceFullTypename; if (string.IsNullOrEmpty(managedReferenceFullTypename)) { return k_NullDisplayName; } if (m_TypeNameCaches.TryGetValue(managedReferenceFullTypename,out GUIContent cachedTypeName)) { return cachedTypeName; } Type type = ManagedReferenceUtility.GetType(managedReferenceFullTypename); string typeName = null; AddTypeMenuAttribute typeMenu = TypeMenuUtility.GetAttribute(type); if (typeMenu != null) { typeName = typeMenu.GetTypeNameWithoutPath(); if (!string.IsNullOrWhiteSpace(typeName)) { typeName = ObjectNames.NicifyVariableName(typeName); } } if (string.IsNullOrWhiteSpace(typeName)) { typeName = ObjectNames.NicifyVariableName(type.Name); } GUIContent result = new GUIContent(typeName); m_TypeNameCaches.Add(managedReferenceFullTypename,result); return result; } public override float GetPropertyHeight (SerializedProperty property,GUIContent label) { PropertyDrawer customDrawer = GetCustomPropertyDrawer(property); if (customDrawer != null) { return property.isExpanded ? EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing + customDrawer.GetPropertyHeight(property,label):EditorGUIUtility.singleLineHeight; } else { return property.isExpanded ? EditorGUI.GetPropertyHeight(property,true) : EditorGUIUtility.singleLineHeight; } } } } #endif