You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

208 lines
7.5 KiB

#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<string,TypePopupCache> m_TypePopups = new Dictionary<string,TypePopupCache>();
readonly Dictionary<string,GUIContent> m_TypeNameCaches = new Dictionary<string,GUIContent>();
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 = EditorGUI.IndentedRect(foldoutLabelRect);
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);
}
// Draw the foldout.
if (!string.IsNullOrEmpty(property.managedReferenceFullTypename))
{
Rect foldoutRect = new Rect(position);
foldoutRect.height = EditorGUIUtility.singleLineHeight;
#if UNITY_2022_2_OR_NEWER
// NOTE: Position x must be adjusted.
// FIXME: Is there a more essential solution...?
foldoutRect.x -= 12;
#endif
property.isExpanded = EditorGUI.Foldout(foldoutRect, property.isExpanded, GUIContent.none, true);
}
// Draw property if expanded.
if (property.isExpanded)
{
using (new EditorGUI.IndentLevelScope())
{
// 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 indentedRect = position;
float foldoutDifference = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
indentedRect.height = customDrawer.GetPropertyHeight(property, label);
indentedRect.y += foldoutDifference;
customDrawer.OnGUI(indentedRect, property, label);
}
else
{
// Draw the properties of the child elements.
// NOTE: In the following code, since the foldout layout isn't working properly, I'll iterate through the properties of the child elements myself.
// EditorGUI.PropertyField(position, property, GUIContent.none, true);
Rect childPosition = position;
childPosition.y += EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing;
foreach (SerializedProperty childProperty in property.GetChildProperties())
{
float height = EditorGUI.GetPropertyHeight(childProperty, new GUIContent(childProperty.displayName, childProperty.tooltip), true);
childPosition.height = height;
EditorGUI.PropertyField(childPosition, childProperty, true);
childPosition.y += height + EditorGUIUtility.standardVerticalSpacing;
}
}
}
}
}
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