Tanoda
ClassTypeReferencePropertyDrawer.cs
Go to the documentation of this file.
1// Copyright (c) Rotorz Limited. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root.
3
4using System;
5using System.Collections.Generic;
6using System.Reflection;
7using UnityEditor;
8using UnityEngine;
9
10namespace TypeReferences {
11
15 [CustomPropertyDrawer(typeof(ClassTypeReference))]
16 [CustomPropertyDrawer(typeof(ClassTypeConstraintAttribute), true)]
17 public sealed class ClassTypeReferencePropertyDrawer : PropertyDrawer {
18
19 #region Type Filtering
20
56 public static Func<ICollection<Type>> ExcludedTypeCollectionGetter { get; set; }
57
58 private static List<Type> GetFilteredTypes(ClassTypeConstraintAttribute filter) {
59 var types = new List<Type>();
60
61 var excludedTypes = (ExcludedTypeCollectionGetter != null ? ExcludedTypeCollectionGetter() : null);
62
63 var assembly = Assembly.GetExecutingAssembly();
64 FilterTypes(assembly, filter, excludedTypes, types);
65
66 foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
67 FilterTypes(Assembly.Load(referencedAssembly), filter, excludedTypes, types);
68
69 types.Sort((a, b) => a.FullName.CompareTo(b.FullName));
70
71 return types;
72 }
73
74 private static void FilterTypes(Assembly assembly, ClassTypeConstraintAttribute filter, ICollection<Type> excludedTypes, List<Type> output) {
75 foreach (var type in assembly.GetTypes()) {
76 if (!type.IsVisible || !type.IsClass)
77 continue;
78
79 if (filter != null && !filter.IsConstraintSatisfied(type))
80 continue;
81
82 if (excludedTypes != null && excludedTypes.Contains(type))
83 continue;
84
85 output.Add(type);
86 }
87 }
88
89 #endregion
90
91 #region Type Utility
92
93 private static Dictionary<string, Type> s_TypeMap = new Dictionary<string, Type>();
94
95 private static Type ResolveType(string classRef) {
96 Type type;
97 if (!s_TypeMap.TryGetValue(classRef, out type)) {
98 type = !string.IsNullOrEmpty(classRef) ? Type.GetType(classRef) : null;
99 s_TypeMap[classRef] = type;
100 }
101 return type;
102 }
103
104 #endregion
105
106 #region Control Drawing / Event Handling
107
108 private static readonly int s_ControlHint = typeof(ClassTypeReferencePropertyDrawer).GetHashCode();
109 private static GUIContent s_TempContent = new GUIContent();
110
111 private static string DrawTypeSelectionControl(Rect position, GUIContent label, string classRef, ClassTypeConstraintAttribute filter) {
112 if (label != null && label != GUIContent.none)
113 position = EditorGUI.PrefixLabel(position, label);
114
115 int controlID = GUIUtility.GetControlID(s_ControlHint, FocusType.Keyboard, position);
116
117 bool triggerDropDown = false;
118
119 switch (Event.current.GetTypeForControl(controlID)) {
120 case EventType.ExecuteCommand:
121 if (Event.current.commandName == "TypeReferenceUpdated") {
122 if (s_SelectionControlID == controlID) {
123 if (classRef != s_SelectedClassRef) {
124 classRef = s_SelectedClassRef;
125 GUI.changed = true;
126 }
127
128 s_SelectionControlID = 0;
129 s_SelectedClassRef = null;
130 }
131 }
132 break;
133
134 case EventType.MouseDown:
135 if (GUI.enabled && position.Contains(Event.current.mousePosition)) {
136 GUIUtility.keyboardControl = controlID;
137 triggerDropDown = true;
138 Event.current.Use();
139 }
140 break;
141
142 case EventType.KeyDown:
143 if (GUI.enabled && GUIUtility.keyboardControl == controlID) {
144 if (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Space) {
145 triggerDropDown = true;
146 Event.current.Use();
147 }
148 }
149 break;
150
151 case EventType.Repaint:
152 // Remove assembly name from content of popup control.
153 var classRefParts = classRef.Split(',');
154
155 s_TempContent.text = classRefParts[0].Trim();
156 if (s_TempContent.text == "")
157 s_TempContent.text = "(None)";
158 else if (ResolveType(classRef) == null)
159 s_TempContent.text += " {Missing}";
160
161 EditorStyles.popup.Draw(position, s_TempContent, controlID);
162 break;
163 }
164
165 if (triggerDropDown) {
166 s_SelectionControlID = controlID;
167 s_SelectedClassRef = classRef;
168
169 var filteredTypes = GetFilteredTypes(filter);
170 DisplayDropDown(position, filteredTypes, ResolveType(classRef), filter.Grouping);
171 }
172
173 return classRef;
174 }
175
176 private static void DrawTypeSelectionControl(Rect position, SerializedProperty property, GUIContent label, ClassTypeConstraintAttribute filter) {
177 try {
178 bool restoreShowMixedValue = EditorGUI.showMixedValue;
179 EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
180
181 property.stringValue = DrawTypeSelectionControl(position, label, property.stringValue, filter);
182
183 EditorGUI.showMixedValue = restoreShowMixedValue;
184 }
185 finally {
187 }
188 }
189
190 private static void DisplayDropDown(Rect position, List<Type> types, Type selectedType, ClassGrouping grouping) {
191 var menu = new GenericMenu();
192
193 menu.AddItem(new GUIContent("(None)"), selectedType == null, s_OnSelectedTypeName, null);
194 menu.AddSeparator("");
195
196 for (int i = 0; i < types.Count; ++i) {
197 var type = types[i];
198
199 string menuLabel = FormatGroupedTypeName(type, grouping);
200 if (string.IsNullOrEmpty(menuLabel))
201 continue;
202
203 var content = new GUIContent(menuLabel);
204 menu.AddItem(content, type == selectedType, s_OnSelectedTypeName, type);
205 }
206
207 menu.DropDown(position);
208 }
209
210 private static string FormatGroupedTypeName(Type type, ClassGrouping grouping) {
211 string name = type.FullName;
212
213 switch (grouping) {
214 default:
215 case ClassGrouping.None:
216 return name;
217
218 case ClassGrouping.ByNamespace:
219 return name.Replace('.', '/');
220
221 case ClassGrouping.ByNamespaceFlat:
222 int lastPeriodIndex = name.LastIndexOf('.');
223 if (lastPeriodIndex != -1)
224 name = name.Substring(0, lastPeriodIndex) + "/" + name.Substring(lastPeriodIndex + 1);
225
226 return name;
227
228 case ClassGrouping.ByAddComponentMenu:
229 var addComponentMenuAttributes = type.GetCustomAttributes(typeof(AddComponentMenu), false);
230 if (addComponentMenuAttributes.Length == 1)
231 return ((AddComponentMenu)addComponentMenuAttributes[0]).componentMenu;
232
233 return "Scripts/" + type.FullName.Replace('.', '/');
234 }
235 }
236
237 private static int s_SelectionControlID;
238 private static string s_SelectedClassRef;
239
240 private static readonly GenericMenu.MenuFunction2 s_OnSelectedTypeName = OnSelectedTypeName;
241
242 private static void OnSelectedTypeName(object userData) {
243 var selectedType = userData as Type;
244
245 s_SelectedClassRef = ClassTypeReference.GetClassRef(selectedType);
246
247 var typeReferenceUpdatedEvent = EditorGUIUtility.CommandEvent("TypeReferenceUpdated");
248 EditorWindow.focusedWindow.SendEvent(typeReferenceUpdatedEvent);
249 }
250
251 #endregion
252
253 public override float GetPropertyHeight(SerializedProperty property, GUIContent label) {
254 return EditorStyles.popup.CalcHeight(GUIContent.none, 0);
255 }
256
257 public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
258 DrawTypeSelectionControl(position, property.FindPropertyRelative("_classRef"), label, attribute as ClassTypeConstraintAttribute);
259 }
260
261 }
262
263}
Base class for class selection constraints that can be applied when selecting a ClassTypeReference wi...
Custom property drawer for ClassTypeReference properties.
override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
static Func< ICollection< Type > > ExcludedTypeCollectionGetter
Gets or sets a function that returns a collection of types that are to be excluded from drop-down....
override float GetPropertyHeight(SerializedProperty property, GUIContent label)
ClassGrouping
Indicates how selectable classes should be collated in drop-down menu.