-
-
Notifications
You must be signed in to change notification settings - Fork 89
Expand file tree
/
Copy pathSubclassSelectorDrawer.cs
More file actions
224 lines (190 loc) · 8.2 KB
/
SubclassSelectorDrawer.cs
File metadata and controls
224 lines (190 loc) · 8.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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 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;
// NOTE: IndentedRect should be disabled as it causes extra indentation.
//foldoutLabelRect = EditorGUI.IndentedRect(foldoutLabelRect);
Rect popupPosition = EditorGUI.PrefixLabel(foldoutLabelRect, label);
#if UNITY_2021_3_OR_NEWER
// Override the label text with the ToString() of the managed reference.
var subclassSelectorAttribute = (SubclassSelectorAttribute)attribute;
if (subclassSelectorAttribute.UseToStringAsLabel && !property.hasMultipleDifferentValues)
{
object managedReferenceValue = property.managedReferenceValue;
if (managedReferenceValue != null)
{
label.text = managedReferenceValue.ToString();
}
}
#endif
// 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 && !UNITY_6000_0_OR_NEWER && !UNITY_2022_3
// NOTE: Position x must be adjusted.
// FIXME: Is there a more essential solution...?
// The most promising is UI Toolkit, but it is currently unable to reproduce all of SubclassSelector features. (Complete provision of contextual menu, e.g.)
// 2021.3: No adjustment
// 2022.1: No adjustment
// 2022.2: Adjustment required
// 2022.3: Adjustment required
// 2023.1: Adjustment required
// 2023.2: Adjustment required
// 6000.0: No adjustment
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 types = TypeSearchService.TypeCandiateService.GetDisplayableTypes(baseType);
var popup = new AdvancedTypePopup(
types,
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;
}
}
}
}