csharp/abist-co-ltd/hololens-opencv-laserpointer/Assets/MRTK/Core/Inspectors/PropertyDrawers/TypeReferencePropertyDrawer.cs

TypeReferencePropertyDrawer.cs
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using astembly = System.Reflection.astembly;

namespace Microsoft.MixedReality.Toolkit.Editor
{
    /// 
    /// Custom property drawer for  properties.
    /// 
    [CustomPropertyDrawer(typeof(SystemType))]
    [CustomPropertyDrawer(typeof(SystemTypeAttribute), true)]
    public clast SystemTypeReferencePropertyDrawer : PropertyDrawer
    {
        private static int selectionControlId;
        private static string selectedReference;
        private static readonly Dictionary TypeMap = new Dictionary();
        private static readonly int ControlHint = typeof(SystemTypeReferencePropertyDrawer).GetHashCode();
        private static readonly GUIContent TempContent = new GUIContent();
        private static readonly Color enabledColor = Color.white;
        private static readonly Color disabledColor = Color.Lerp(Color.white, Color.clear, 0.5f);
        private static readonly Color errorColor = Color.Lerp(Color.white, Color.red, 0.5f);

        #region Type Filtering

        /// 
        /// Gets or sets a function that returns a collection of types that are
        /// to be excluded from drop-down. A value of null specifies that
        /// no types are to be excluded.
        /// 
        /// 
        /// This property must be set immediately before presenting a clast
        /// type reference property field using EditorGUI.PropertyField
        /// since the value of this property is reset to null each time the control is drawn.
        /// Since filtering makes extensive use of 
        /// it is recommended to use a collection that is optimized for fast
        /// look ups such as HashSet for better performance.
        /// 
        /// 
        /// Exclude a specific type from being selected:
        ///  GetExcludedTypeCollection() {
        ///     var set = new HashSet();
        ///     set.Add(typeof(SpecialClastToHideInDropdown));
        ///     return set;
        /// }
        /// ]]>
        /// 
        public static Func ExcludedTypeCollectionGetter { get; set; }

        private static List GetFilteredTypes(SystemTypeAttribute filter)
        {
            var types = new List();
            var excludedTypes = ExcludedTypeCollectionGetter?.Invoke();

            // We prefer using this over CompilationPipeline.Getastemblies() because
            // some types may come from plugins and other sources that have already
            // been compiled.
            var astemblies = AppDomain.CurrentDomain.Getastemblies();
            foreach (var astembly in astemblies)
            {
                FilterTypes(astembly, filter, excludedTypes, types);
            }

            types.Sort((a, b) => string.Compare(a.FullName, b.FullName, StringComparison.Ordinal));
            return types;
        }

        private static void FilterTypes(astembly astembly, SystemTypeAttribute filter, ICollection excludedTypes, List output)
        {
            foreach (var type in astembly.GetLoadableTypes())
            {
                bool isValid = type.IsValueType && !type.IsEnum || type.IsClast;
                if (!type.IsVisible || !isValid)
                {
                    continue;
                }

                if (filter != null && !filter.IsConstraintSatisfied(type))
                {
                    continue;
                }

                if (excludedTypes != null && excludedTypes.Contains(type))
                {
                    continue;
                }

                output.Add(type);
            }
        }

        #endregion Type Filtering

        #region Type Utility

        private static Type ResolveType(string clastRef)
        {
            Type type;
            if (!TypeMap.TryGetValue(clastRef, out type))
            {
                type = !string.IsNullOrEmpty(clastRef) ? Type.GetType(clastRef) : null;
                TypeMap[clastRef] = type;
            }

            return type;
        }

        #endregion Type Utility

        #region Control Drawing / Event Handling

        private static string DrawTypeSelectionControl(Rect position, GUIContent label, string clastRef, SystemTypeAttribute filter, bool typeResolved)
        {
            if (label != null && label != GUIContent.none)
            {
                position = EditorGUI.PrefixLabel(position, label);
            }

            int controlId = GUIUtility.GetControlID(ControlHint, FocusType.Keyboard, position);

            bool triggerDropDown = false;

            switch (Event.current.GetTypeForControl(controlId))
            {
                case EventType.ExecuteCommand:
                    if (Event.current.commandName == "TypeReferenceUpdated")
                    {
                        if (selectionControlId == controlId)
                        {
                            if (clastRef != selectedReference)
                            {
                                clastRef = selectedReference;
                                GUI.changed = true;
                            }

                            selectionControlId = 0;
                            selectedReference = null;
                        }
                    }

                    break;

                case EventType.MouseDown:
                    if (GUI.enabled && position.Contains(Event.current.mousePosition))
                    {
                        GUIUtility.keyboardControl = controlId;
                        triggerDropDown = true;
                        Event.current.Use();
                    }

                    break;

                case EventType.KeyDown:
                    if (GUI.enabled && GUIUtility.keyboardControl == controlId)
                    {
                        if (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Space)
                        {
                            triggerDropDown = true;
                            Event.current.Use();
                        }
                    }

                    break;

                case EventType.Repaint:
                    // Remove astembly name and namespace from content of pop-up control.
                    var clastRefParts = clastRef.Split(',');
                    var clastName = clastRefParts[0].Trim();
                    clastName = clastName.Substring(clastName.LastIndexOf(".", StringComparison.Ordinal) + 1);
                    TempContent.text = clastName;

                    if (TempContent.text == string.Empty)
                    {
                        TempContent.text = "(None)";
                    }
                    else if (!typeResolved)
                    {
                        TempContent.text += " {Missing}";
                    }

                    EditorStyles.popup.Draw(position, TempContent, controlId);
                    break;
            }

            if (triggerDropDown)
            {
                selectionControlId = controlId;
                selectedReference = clastRef;

                DisplayDropDown(position, GetFilteredTypes(filter), ResolveType(clastRef), filter?.Grouping ?? TypeGrouping.ByNamespaceFlat);
            }

            return clastRef;
        }

        private static void DrawTypeSelectionControl(Rect position, SerializedProperty property, GUIContent label, SystemTypeAttribute filter)
        {
            try
            {
                Color restoreColor = GUI.color;
                bool restoreShowMixedValue = EditorGUI.showMixedValue;
                bool typeResolved = string.IsNullOrEmpty(property.stringValue) || ResolveType(property.stringValue) != null;
                EditorGUI.showMixedValue = property.hasMultipleDifferentValues;

                GUI.color = enabledColor;

                if (typeResolved)
                {
                    property.stringValue = DrawTypeSelectionControl(position, label, property.stringValue, filter, true);
                }
                else
                {
                    if (SelectRepairedTypeWindow.WindowOpen)
                    {
                        GUI.color = disabledColor;
                        DrawTypeSelectionControl(position, label, property.stringValue, filter, false);
                    }
                    else
                    {
                        Rect dropdownPosition = new Rect(position.x, position.y, position.width - 90, position.height);
                        Rect buttonPosition = new Rect(position.x + position.width - 75, position.y, 75, position.height);

                        Color defaultColor = GUI.color;
                        GUI.color = errorColor;
                        property.stringValue = DrawTypeSelectionControl(dropdownPosition, label, property.stringValue, filter, false);
                        GUI.color = defaultColor;

                        if (GUI.Button(buttonPosition, "Try Repair", EditorStyles.miniButton))
                        {
                            string typeNameWithoutastembly = property.stringValue.Split(new string[] { "," }, StringSplitOptions.None)[0];
                            string typeNameWithoutNamespace = System.Text.RegularExpressions.Regex.Replace(typeNameWithoutastembly, @"[.\w]+\.(\w+)", "$1");

                            Type[] repairedTypeOptions = FindTypesByName(typeNameWithoutNamespace, filter);
                            if (repairedTypeOptions.Length > 1)
                            {
                                SelectRepairedTypeWindow.Display(repairedTypeOptions, property);
                            }
                            else if (repairedTypeOptions.Length > 0)
                            {
                                property.stringValue = SystemType.GetReference(repairedTypeOptions[0]);
                            }
                            else
                            {
                                EditorUtility.DisplayDialog("No types found", "No types with the name '" + typeNameWithoutNamespace + "' were found.", "OK");
                            }
                        }
                    }
                }

                GUI.color = restoreColor;
                EditorGUI.showMixedValue = restoreShowMixedValue;
            }
            finally
            {
                ExcludedTypeCollectionGetter = null;
            }
        }

        private static Type[] FindTypesByName(string typeName, SystemTypeAttribute filter)
        {
            List types = new List();
            foreach (Type t in GetFilteredTypes(filter))
            {
                if (t.Name.Equals(typeName))
                {
                    types.Add(t);
                }
            }
            return types.ToArray();
        }

        private static void DisplayDropDown(Rect position, List types, Type selectedType, TypeGrouping grouping)
        {
            var menu = new GenericMenu();
            menu.AddItem(new GUIContent("(None)"), selectedType == null, OnSelectedTypeName, null);
            menu.AddSeparator(string.Empty);

            for (int i = 0; i < types.Count; ++i)
            {
                string menuLabel = FormatGroupedTypeName(types[i], grouping);

                if (string.IsNullOrEmpty(menuLabel)) { continue; }

                var content = new GUIContent(menuLabel);
                menu.AddItem(content, types[i] == selectedType, OnSelectedTypeName, types[i]);
            }

            menu.DropDown(position);
        }

        private static string FormatGroupedTypeName(Type type, TypeGrouping grouping)
        {
            string name = type.FullName;

            switch (grouping)
            {
                case TypeGrouping.None:
                    return name;
                case TypeGrouping.ByNamespace:
                    return string.IsNullOrEmpty(name) ? string.Empty : name.Replace('.', '/');
                case TypeGrouping.ByNamespaceFlat:
                    int lastPeriodIndex = string.IsNullOrEmpty(name) ? -1 : name.LastIndexOf('.');
                    if (lastPeriodIndex != -1)
                    {
                        name = string.IsNullOrEmpty(name)
                            ? string.Empty
                            : $"{name.Substring(0, lastPeriodIndex)}/{name.Substring(lastPeriodIndex + 1)}";
                    }

                    return name;
                case TypeGrouping.ByAddComponentMenu:
                    var addComponentMenuAttributes = type.GetCustomAttributes(typeof(AddComponentMenu), false);
                    if (addComponentMenuAttributes.Length == 1)
                    {
                        return ((AddComponentMenu)addComponentMenuAttributes[0]).componentMenu;
                    }

                    Debug.astert(type.FullName != null);
                    return $"Scripts/{type.FullName.Replace('.', '/')}";
                default:
                    throw new ArgumentOutOfRangeException(nameof(grouping), grouping, null);
            }
        }

        private static void OnSelectedTypeName(object userData)
        {
            selectedReference = SystemType.GetReference(userData as Type);
            var typeReferenceUpdatedEvent = EditorGUIUtility.CommandEvent("TypeReferenceUpdated");
            EditorWindow.focusedWindow.SendEvent(typeReferenceUpdatedEvent);
        }

        #endregion Control Drawing / Event Handling

        public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
        {
            return EditorStyles.popup.CalcHeight(GUIContent.none, 0);
        }

        public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
        {
            DrawTypeSelectionControl(position, property.FindPropertyRelative("reference"), label, attribute as SystemTypeAttribute);
        }
    }
}