using System; using System.Linq; using System.Reflection; using UnityEngine; using UnityEditor; using System.Collections.Generic; using Rokid.UXR.Interaction; namespace Rokid.UXR.Editor { /// /// This property drawer is the meat of the interface support implementation. When /// the value of field with this attribute is modified, the new value is tested /// against the interface expected. If the component matches, the new value is /// accepted. Otherwise, the old value is maintained. /// [CustomPropertyDrawer(typeof(InterfaceAttribute))] public class InterfaceDrawer : PropertyDrawer { private int _filteredObjectPickerID; private static readonly Type[] _singleMonoBehaviourType = new Type[1] { typeof(MonoBehaviour) }; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { _filteredObjectPickerID = GUIUtility.GetControlID(FocusType.Passive); if (property.serializedObject.isEditingMultipleObjects) return; if (property.propertyType != SerializedPropertyType.ObjectReference) { EditorGUI.LabelField(position, label.text, "InterfaceType Attribute can only be used with MonoBehaviour Components."); return; } EditorGUI.BeginProperty(position, label, property); Type[] attTypes = GetInterfaceTypes(property); // Pick a specific component MonoBehaviour oldComponent = property.objectReferenceValue as MonoBehaviour; string oldComponentName = ""; GameObject temporaryGameObject = null; string attTypesName = GetTypesName(attTypes); if (Event.current.type == EventType.Repaint) { if (oldComponent == null) { temporaryGameObject = new GameObject("None (" + attTypesName + ")"); oldComponent = temporaryGameObject.AddComponent(); } else { oldComponentName = oldComponent.name; oldComponent.name = oldComponentName + " (" + attTypesName + ")"; } } Component currentComponent = EditorGUI.ObjectField(position, label, oldComponent, typeof(Component), true) as Component; int objectPickerID = GUIUtility.GetControlID(FocusType.Passive) - 1; ReplaceObjectPickerForControl(attTypes, objectPickerID); if (Event.current.commandName == "ObjectSelectorUpdated" && EditorGUIUtility.GetObjectPickerControlID() == _filteredObjectPickerID) { UnityEngine.Object pickedObject = EditorGUIUtility.GetObjectPickerObject(); if (pickedObject is GameObject) { currentComponent = (pickedObject as GameObject).transform; } else { currentComponent = pickedObject as Component; } } MonoBehaviour currentMono = currentComponent as MonoBehaviour; if (Event.current.type == EventType.Repaint) { if (temporaryGameObject != null) GameObject.DestroyImmediate(temporaryGameObject); else oldComponent.name = oldComponentName; } // If a component is assigned, make sure it is the interface we are looking for. if (currentMono != null) { // Make sure component is of the right interface if (!IsAssignableFromTypes(currentMono.GetType(), attTypes)) // Component failed. Check game object. foreach (Type attType in attTypes) { currentMono = currentMono.gameObject.GetComponent(attType) as MonoBehaviour; if (currentMono == null) { break; } } // Item failed test. Do not override old component if (currentMono == null) { if (oldComponent != null && !IsAssignableFromTypes(oldComponent.GetType(), attTypes)) { temporaryGameObject = new GameObject("None (" + attTypesName + ")"); MonoBehaviour temporaryComponent = temporaryGameObject.AddComponent(); currentMono = EditorGUI.ObjectField(position, label, temporaryComponent, typeof(MonoBehaviour), true) as MonoBehaviour; GameObject.DestroyImmediate(temporaryGameObject); } } } else if (currentComponent is Transform) { // If assigned component is a Transform, this means a GameObject was dragged into the property field. // Find all matching components on the transform's GameObject and open the picker window. List monos = new List(); monos.AddRange(currentComponent.gameObject.GetComponents(). Where((mono) => IsAssignableFromTypes(mono.GetType(), attTypes))); if (monos.Count > 1) { EditorApplication.delayCall += () => InterfacePicker.Show(property, monos); } else { currentMono = monos.Count == 1 ? monos[0] : null; } } if (currentComponent == null || currentMono != null) { property.objectReferenceValue = currentMono; } EditorGUI.EndProperty(); } private bool IsAssignableFromTypes(Type source, Type[] targets) { foreach (Type t in targets) { if (!t.IsAssignableFrom(source)) { return false; } } return true; } private static string GetTypesName(Type[] attTypes) { if (attTypes.Length == 1) { return GetTypeName(attTypes[0]); } string typesString = ""; for (int i = 0; i < attTypes.Length; i++) { if (i > 0) { typesString += ", "; } typesString += GetTypeName(attTypes[i]); } return typesString; } private static string GetTypeName(Type attType) { if (!attType.IsGenericType) { return attType.Name; } var genericTypeNames = attType.GenericTypeArguments.Select(GetTypeName); return $"{attType.Name}<{string.Join(", ", genericTypeNames)}>"; } private Type[] GetInterfaceTypes(SerializedProperty property) { InterfaceAttribute att = (InterfaceAttribute)attribute; Type[] t = att.Types; if (!String.IsNullOrEmpty(att.TypeFromFieldName)) { var thisType = property.serializedObject.targetObject.GetType(); while (thisType != null) { var referredFieldInfo = thisType.GetField(att.TypeFromFieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (referredFieldInfo != null) { t = new Type[1] { referredFieldInfo.FieldType }; break; } thisType = thisType.BaseType; } } return t ?? _singleMonoBehaviourType; } void ReplaceObjectPickerForControl(Type[] attTypes, int replacePickerID) { var currentObjectPickerID = EditorGUIUtility.GetObjectPickerControlID(); if (currentObjectPickerID != replacePickerID) { return; } var derivedTypes = TypeCache.GetTypesDerivedFrom(attTypes[0]); HashSet validTypes = new HashSet(derivedTypes); for (int i = 1; i < attTypes.Length; i++) { var derivedTypesIntersect = TypeCache.GetTypesDerivedFrom(attTypes[i]); validTypes.IntersectWith(derivedTypesIntersect); } //start filter with a long empty area to allow for easy clicking and typing var filterBuilder = new System.Text.StringBuilder(" "); foreach (Type type in validTypes) { if (type.IsGenericType) { continue; } filterBuilder.Append("t:" + type.FullName + " "); } string filter = filterBuilder.ToString(); EditorGUIUtility.ShowObjectPicker(null, true, filter, _filteredObjectPickerID); } } public sealed class InterfaceMono : MonoBehaviour { } }