namespace Zinnia.Utility { using System; using System.Linq; using UnityEditor; using UnityEngine; /// /// Displays an InterfaceContainer property in the Unity inspector. /// [CustomPropertyDrawer(typeof(InterfaceContainer), true)] public class InterfaceContainerDrawer : PropertyDrawer { /// /// The PickedWindow to display to show the available choices. /// public class PickerWindow : PickerWindow { } /// public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { using (new EditorGUI.PropertyScope(position, label, property)) { label.tooltip = EditorHelper.GetTooltipAttribute(fieldInfo)?.tooltip ?? string.Empty; DrawPicker(property.FindPropertyRelative("field"), GetTargetType(), position, label); } } /// /// Gets the Target of the current field info. /// /// The type associated with the field info. protected virtual Type GetTargetType() { Type type = fieldInfo.FieldType; if (type.IsGenericType) { type = type.GenericTypeArguments[0]; } if (type.HasElementType) { type = type.GetElementType(); } while (type != null && type.BaseType != typeof(InterfaceContainer)) { type = type.BaseType; } if (type?.BaseType != typeof(InterfaceContainer)) { throw new ArgumentException(); } type = type.GenericTypeArguments[0]; return type; } /// /// Draws the Picker window. /// /// The property to draw for. /// The data type. /// The default drawing location for the control. /// The default label for the control. protected virtual void DrawPicker(SerializedProperty property, Type type, Rect rect, GUIContent label) { Event currentEvent = Event.current; bool isMouseDragging = currentEvent.type == EventType.DragUpdated || currentEvent.type == EventType.DragPerform; bool isMouseHovering = rect.Contains(currentEvent.mousePosition); bool drawAsComponent; if (isMouseDragging) { // Only allow dropping dragged objects if they have at least one component of the correct type. drawAsComponent = DragAndDrop.objectReferences .Where(draggedObject => !EditorUtility.IsPersistent(draggedObject)) .Select( draggedObject => { switch (draggedObject) { case Component component: return component.gameObject; case GameObject gameObject: return gameObject; default: return null; } }) .Where(gameObject => gameObject != null) .Select(gameObject => gameObject.GetComponents(type)) .All(components => components.Length > 0); } else { // Regular object picks should just lookup all components. drawAsComponent = currentEvent.type == EventType.MouseDown && isMouseHovering; } string controlName = $"{nameof(InterfaceContainerDrawer)}_{nameof(EditorGUI.ObjectField)}_{property.propertyPath}"; GUI.SetNextControlName(controlName); UnityEngine.Object pickedObject = EditorGUI.ObjectField( rect, label, property.objectReferenceValue, drawAsComponent ? typeof(Component) : type, true); if (pickedObject != property.objectReferenceValue && isMouseDragging && isMouseHovering) { // The object has been changed by dropping a dragged object. HandlePickedObject(property, type, rect, pickedObject); return; } if (currentEvent.type == EventType.ExecuteCommand && currentEvent.commandName == "ObjectSelectorClosed" && GUI.GetNameOfFocusedControl() == controlName && GUIUtility.keyboardControl == EditorGUIUtility.GetObjectPickerControlID()) { // The picker window we opened was closed. pickedObject = EditorGUIUtility.GetObjectPickerObject(); HandlePickedObject(property, type, rect, pickedObject); } } /// /// Determines what to do with the picked object from the ObjectPicker. /// /// The property to handle. /// The data type. /// The default drawing location for the control. /// The picked object. protected virtual void HandlePickedObject(SerializedProperty property, Type type, Rect rect, UnityEngine.Object pickedObject) { if (pickedObject == property.objectReferenceValue) { return; } if (pickedObject == null) { property.objectReferenceValue = null; property.serializedObject.ApplyModifiedProperties(); return; } GameObject pickedGameObject = null; switch (pickedObject) { case Component component: pickedGameObject = component.gameObject; break; case GameObject gameObject: pickedGameObject = gameObject; break; } if (pickedGameObject == null) { property.objectReferenceValue = null; property.serializedObject.ApplyModifiedProperties(); return; } Component[] components = pickedGameObject.GetComponents(type); switch (components.Length) { case 0: property.objectReferenceValue = null; property.serializedObject.ApplyModifiedProperties(); break; case 1: property.objectReferenceValue = components[0]; property.serializedObject.ApplyModifiedProperties(); break; default: PickerWindow.Show( new Rect { min = GUIUtility.GUIToScreenPoint(rect.min + (Vector2.right * EditorGUIUtility.labelWidth)), max = GUIUtility.GUIToScreenPoint(rect.max) }, components, selectedComponent => { pickedObject = selectedComponent; property.objectReferenceValue = selectedComponent; property.serializedObject.ApplyModifiedProperties(); }, searchedComponent => searchedComponent.ToString(), drawnComponent => { Type drawnType = drawnComponent.GetType(); return new GUIContent( ObjectNames.NicifyVariableName(drawnType.Name), AssetPreview.GetMiniTypeThumbnail(drawnType), drawnType.FullName); }); break; } } } }