예제 #1
0
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            InspectorUIUtility.DrawTitle("States");
            EditorGUILayout.HelpBox("Manage state configurations to drive Interactables or Transitions", MessageType.None);

            SerializedProperty stateModelClassName   = serializedObject.FindProperty("StateModelClassName");
            SerializedProperty assemblyQualifiedName = serializedObject.FindProperty("AssemblyQualifiedName");

            var stateModelTypes      = TypeCacheUtility.GetSubClasses <BaseStateModel>();
            var stateModelClassNames = stateModelTypes.Select(t => t?.Name).ToArray();
            int id = Array.IndexOf(stateModelClassNames, stateModelClassName.stringValue);

            Rect stateModelPos = EditorGUILayout.GetControlRect();

            using (new EditorGUI.PropertyScope(stateModelPos, new GUIContent("State Model"), stateModelClassName))
            {
                int newId = EditorGUILayout.Popup("State Model", id, stateModelClassNames);
                if (id != newId)
                {
                    Type newType = stateModelTypes[newId];
                    stateModelClassName.stringValue   = newType.Name;
                    assemblyQualifiedName.stringValue = newType.AssemblyQualifiedName;
                }
            }

            for (int i = 0; i < stateList.arraySize; i++)
            {
                using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
                {
                    SerializedProperty stateItem = stateList.GetArrayElementAtIndex(i);

                    SerializedProperty name        = stateItem.FindPropertyRelative("Name");
                    SerializedProperty activeIndex = stateItem.FindPropertyRelative("ActiveIndex");
                    SerializedProperty bit         = stateItem.FindPropertyRelative("Bit");
                    SerializedProperty index       = stateItem.FindPropertyRelative("Index");

                    // assign the bitcount based on location in the list as power of 2
                    bit.intValue = 1 << i;

                    activeIndex.intValue = i;

                    Rect position = EditorGUILayout.GetControlRect();
                    using (new EditorGUILayout.HorizontalScope())
                    {
                        var label = new GUIContent(name.stringValue + " (" + bit.intValue + ")");
                        using (new EditorGUI.PropertyScope(position, new GUIContent(), name))
                        {
                            string[] stateEnums = Enum.GetNames(typeof(InteractableStates.InteractableStateEnum));
                            int      enumIndex  = Array.IndexOf(stateEnums, name.stringValue);

                            int newEnumIndex = EditorGUILayout.Popup(label, enumIndex, stateEnums);
                            if (newEnumIndex == -1)
                            {
                                newEnumIndex = 0;
                            }

                            name.stringValue = stateEnums[newEnumIndex];
                            index.intValue   = newEnumIndex;
                        }

                        if (InspectorUIUtility.SmallButton(RemoveStateLabel))
                        {
                            stateList.DeleteArrayElementAtIndex(i);
                            break;
                        }
                    }
                }
            }

            if (InspectorUIUtility.FlexButton(AddStateLabel))
            {
                stateList.InsertArrayElementAtIndex(stateList.arraySize);
            }

            serializedObject.ApplyModifiedProperties();
        }
예제 #2
0
        public override void OnInspectorGUI()
        {
            //base.OnInspectorGUI();
            serializedObject.Update();

            InspectorUIUtility.DrawTitle("States");
            InspectorUIUtility.DrawNotice("Manage state configurations to drive Interactables or Tansitions");

            // get the list of options and InteractableStates
            stateOptions = instance.StateOptions;
            stateTypes   = instance.StateTypes;

            SerializedProperty stateLogicName = serializedObject.FindProperty("StateLogicName");
            int option = States.ReverseLookup(stateLogicName.stringValue, stateOptions);

            int newLogic = EditorGUILayout.Popup("State Model", option, stateOptions);

            if (option != newLogic)
            {
                stateLogicName.stringValue = stateOptions[newLogic];
            }

            int bitCount = 0;

            for (int i = 0; i < stateList.arraySize; i++)
            {
                if (i == 0)
                {
                    bitCount += 1;
                }
                else
                {
                    bitCount += bitCount;
                }

                EditorGUILayout.BeginVertical("Box");
                SerializedProperty stateItem = stateList.GetArrayElementAtIndex(i);

                SerializedProperty name  = stateItem.FindPropertyRelative("Name");
                SerializedProperty index = stateItem.FindPropertyRelative("ActiveIndex");
                SerializedProperty bit   = stateItem.FindPropertyRelative("Bit");

                index.intValue = i;

                EditorGUILayout.BeginHorizontal();
                string[] stateEnums = GetStateOptions();
                int      enumIndex  = States.ReverseLookup(name.stringValue, stateEnums);

                int newEnumIndex = EditorGUILayout.Popup(name.stringValue + " (" + bitCount + ")", enumIndex, stateEnums);
                if (enumIndex != newEnumIndex)
                {
                    name.stringValue = stateEnums[newEnumIndex];
                }

                InspectorUIUtility.SmallButton(new GUIContent(InspectorUIUtility.Minus, "Remove State"), i, RemoveState);

                EditorGUILayout.EndHorizontal();

                // assign the bitcount based on location in the list
                bit.intValue = bitCount;

                EditorGUILayout.EndVertical();
            }

            InspectorUIUtility.FlexButton(new GUIContent("+", "Add Theme Property"), 0, AddState);

            serializedObject.ApplyModifiedProperties();
        }
        public override void OnInspectorGUI()
        {
            if (target != null)
            {
                serializedObject.Update();

                // Help url
                InspectorUIUtility.RenderHelpURL(target.GetType());

                // Data section
                using (var check = new EditorGUI.ChangeCheckScope())
                {
                    EditorGUILayout.Space();

                    EditorGUILayout.HelpBox(autoConstraintSelection.boolValue == true ? autoMsg : manualMsg
                                            , UnityEditor.MessageType.Info);
                    EditorGUILayout.Space();

                    int tab = autoConstraintSelection.boolValue == true ? 0 : 1;
                    tab = GUILayout.Toolbar(tab, new string[] { "Auto Constraint Selection", "Manual Constraint Selection" });
                    EditorGUILayout.Space();
                    switch (tab)
                    {
                    case 0:
                        autoConstraintSelection.boolValue = true;
                        RenderAutoConstraintMenu();
                        break;

                    case 1:
                        bool oldAutoConstraintSelection = autoConstraintSelection.boolValue;
                        autoConstraintSelection.boolValue = false;
                        bool newAutoConstraintSelection = autoConstraintSelection.boolValue;

                        // manual constraint selection was enabled
                        if (newAutoConstraintSelection == false && oldAutoConstraintSelection != newAutoConstraintSelection)
                        {
                            // manual selection is active and manual list is empty -> auto populate with
                            // existing constraints so user has a base to work on
                            if (selectedConstraints.arraySize == 0)
                            {
                                var constraints = constraintManager.gameObject.GetComponents <TransformConstraint>();
                                foreach (var constraint in constraints)
                                {
                                    int currentId = selectedConstraints.arraySize;
                                    selectedConstraints.InsertArrayElementAtIndex(currentId);
                                    selectedConstraints.GetArrayElementAtIndex(currentId).objectReferenceValue = constraint;
                                }
                            }
                        }

                        RenderManualConstraintMenu();
                        break;
                    }

                    // we render the instance id of this component so our highlighting function can distinguish between
                    // the different instances of constraint manager - highlighting in the inspector is currently
                    // only available for string search which causes problems with multiple components of the same type
                    // attached to the same gameobject.
                    EditorGUILayout.Space();
                    EditorGUILayout.LabelField("ComponentId: " + constraintManager.GetInstanceID(), EditorStyles.miniLabel);

                    // deferred delete elements from array to not break unity layout
                    for (int i = indicesToRemove.Count - 1; i > -1; i--)
                    {
                        var currentArraySize = selectedConstraints.arraySize;
                        selectedConstraints.DeleteArrayElementAtIndex(indicesToRemove[i]);
                        if (currentArraySize == selectedConstraints.arraySize)
                        {
                            selectedConstraints.DeleteArrayElementAtIndex(indicesToRemove[i]);
                        }
                    }

                    indicesToRemove.Clear();

                    if (check.changed)
                    {
                        serializedObject.ApplyModifiedProperties();
                    }
                }
            }
        }
예제 #4
0
        private void RenderProfileSettings()
        {
            if (!ProfilesSetup && !showProfiles)
            {
                InspectorUIUtility.DrawWarning("Profiles (Optional) have not been set up or has errors.");
            }

            bool isProfilesOpen = InspectorUIUtility.DrawSectionFoldout("Profiles", showProfiles, FontStyle.Bold, InspectorUIUtility.TitleFontSize);

            if (showProfiles != isProfilesOpen)
            {
                showProfiles = isProfilesOpen;
                EditorPrefs.SetBool(ShowProfilesPrefKey, showProfiles);
            }

            if (profileList.arraySize < 1)
            {
                AddProfile(0);
            }

            int validProfileCnt = 0;
            int themeCnt        = 0;

            if (showProfiles)
            {
                // Render all profile items
                for (int i = 0; i < profileList.arraySize; i++)
                {
                    using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
                    {
                        SerializedProperty profileItem = profileList.GetArrayElementAtIndex(i);
                        SerializedProperty gameObject  = profileItem.FindPropertyRelative("Target");

                        using (new EditorGUILayout.HorizontalScope())
                        {
                            EditorGUILayout.PropertyField(gameObject, new GUIContent("Target", "Target gameObject for this theme properties to manipulate"));
                            if (InspectorUIUtility.SmallButton(new GUIContent(InspectorUIUtility.Minus, "Remove Profile"), i, RemoveProfile))
                            {
                                continue;
                            }
                        }

                        SerializedProperty themes = profileItem.FindPropertyRelative("Themes");
                        ValidateThemes(dimensions, themes);

                        // Render all themes for current target
                        for (int t = 0; t < themes.arraySize; t++)
                        {
                            SerializedProperty themeItem  = themes.GetArrayElementAtIndex(t);
                            string             themeLabel = BuildThemeTitle(dimensions.intValue, t);
                            EditorGUILayout.PropertyField(themeItem, new GUIContent(themeLabel, "Theme properties for interaction feedback"));

                            if (themeItem.objectReferenceValue != null && gameObject.objectReferenceValue)
                            {
                                RenderDefaultThemeWarning(profileItem, themeItem);

                                SerializedProperty hadDefault = profileItem.FindPropertyRelative("HadDefaultTheme");
                                hadDefault.boolValue = true;

                                string prefKey          = themeItem.objectReferenceValue.name + "Profiles" + i + "_Theme" + t + "_Edit";
                                bool   showSettingsPref = EditorPrefs.GetBool(prefKey, true);
                                bool   show             = InspectorUIUtility.DrawSectionFoldout(themeItem.objectReferenceValue.name + " (Click to edit)", showSettingsPref, FontStyle.Normal);
                                if (show != showSettingsPref)
                                {
                                    EditorPrefs.SetBool(prefKey, show);
                                }

                                if (show)
                                {
                                    SerializedObject   themeObj         = new SerializedObject(themeItem.objectReferenceValue);
                                    SerializedProperty themeObjSettings = themeObj.FindProperty("Settings");

                                    GUILayout.Space(5);

                                    if (InspectorUIUtility.FlexButton(AddThemePropertyLabel))
                                    {
                                        AddThemeProperty(profileItem, themeItem);
                                    }

                                    State[] states = GetStates();

                                    themeObj.Update();
                                    ThemeInspector.RenderThemeSettings(themeObjSettings, themeOptions, gameObject, states, ThemePropertiesBoxMargin);
                                    ThemeInspector.RenderThemeStates(themeObjSettings, states, ThemePropertiesBoxMargin);
                                    themeObj.ApplyModifiedProperties();
                                }

                                validProfileCnt++;
                            }
                            else
                            {
                                // show message about profile setup
                                const string       themeMsg   = "Assign a Target and/or Theme above to add visual effects";
                                SerializedProperty hadDefault = profileItem.FindPropertyRelative("HadDefaultTheme");

                                if (!hadDefault.boolValue && t == 0)
                                {
                                    string[] themeLocations = AssetDatabase.FindAssets("DefaultTheme");
                                    if (themeLocations.Length > 0)
                                    {
                                        for (int j = 0; j < themeLocations.Length; j++)
                                        {
                                            string path         = AssetDatabase.GUIDToAssetPath(themeLocations[0]);
                                            Theme  defaultTheme = (Theme)AssetDatabase.LoadAssetAtPath(path, typeof(Theme));
                                            if (defaultTheme != null)
                                            {
                                                themeItem.objectReferenceValue = defaultTheme;
                                                break;
                                            }
                                        }

                                        if (themeItem.objectReferenceValue != null)
                                        {
                                            hadDefault.boolValue = true;
                                        }
                                    }
                                    else
                                    {
                                        InspectorUIUtility.DrawError("DefaultTheme missing from project!");
                                    }
                                }
                                InspectorUIUtility.DrawError(themeMsg);
                            }

                            themeCnt += themes.arraySize;
                        }
                    }
                }// profile for loop

                if (GUILayout.Button(new GUIContent("Add Profile")))
                {
                    AddProfile(profileList.arraySize);
                }
            }
            else
            {
                // make sure profiles are setup if closed by default
                for (int i = 0; i < profileList.arraySize; i++)
                {
                    SerializedProperty sItem      = profileList.GetArrayElementAtIndex(i);
                    SerializedProperty gameObject = sItem.FindPropertyRelative("Target");
                    SerializedProperty themes     = sItem.FindPropertyRelative("Themes");

                    if (gameObject.objectReferenceValue != null)
                    {
                        validProfileCnt++;
                    }

                    for (int t = 0; t < themes.arraySize; t++)
                    {
                        SerializedProperty themeItem = themes.GetArrayElementAtIndex(themes.arraySize - 1);
                        if (themeItem.objectReferenceValue != null && gameObject.objectReferenceValue)
                        {
                            validProfileCnt++;
                            SerializedProperty hadDefault = sItem.FindPropertyRelative("HadDefaultTheme");
                            hadDefault.boolValue = true;
                        }
                    }

                    themeCnt += themes.arraySize;
                }
            }

            ProfilesSetup = validProfileCnt == profileList.arraySize + themeCnt;
        }
예제 #5
0
        protected void RenderGeneralSettings()
        {
            Rect position;
            bool isPlayMode = EditorApplication.isPlaying || EditorApplication.isPaused;

            using (new EditorGUILayout.HorizontalScope())
            {
                InspectorUIUtility.DrawTitle("General");

                if (target != null)
                {
                    var helpURL = target.GetType().GetCustomAttribute <HelpURLAttribute>();
                    if (helpURL != null)
                    {
                        InspectorUIUtility.RenderDocumentationButton(helpURL.URL);
                    }
                }
            }

            EditorGUILayout.BeginVertical(EditorStyles.helpBox);
            // States
            // If states value is not provided, try to use Default states type
            if (statesProperty.objectReferenceValue == null)
            {
                statesProperty.objectReferenceValue = ThemeInspector.GetDefaultInteractableStates();
            }

            GUI.enabled = !isPlayMode;
            EditorGUILayout.PropertyField(statesProperty, new GUIContent("States", "The States this Interactable is based on"));
            GUI.enabled = true;

            if (statesProperty.objectReferenceValue == null)
            {
                InspectorUIUtility.DrawError("Please assign a States object!");
                serializedObject.ApplyModifiedProperties();
                return;
            }

            EditorGUILayout.PropertyField(enabledProperty, new GUIContent("Enabled", "Is this Interactable Enabled?"));

            // Input Actions
            bool validActionOptions = inputActionOptions != null;

            GUI.enabled = validActionOptions && !isPlayMode;

            var actionOptions = validActionOptions ? inputActionOptions : new string[] { "Missing Mixed Reality Toolkit" };

            DrawDropDownProperty(EditorGUILayout.GetControlRect(), actionId, actionOptions, InputActionsLabel);

            GUI.enabled = true;

            using (new EditorGUI.IndentLevelScope())
            {
                EditorGUILayout.PropertyField(isGlobal, new GUIContent("Is Global", "Like a modal, does not require focus"));
            }

            // Speech keywords
            bool validSpeechKeywords = speechKeywordOptions != null;

            GUI.enabled = validSpeechKeywords && !isPlayMode;

            string[] keywordOptions = validSpeechKeywords ? speechKeywordOptions : new string[] { "Missing Speech Commands" };
            int      currentIndex   = validSpeechKeywords ? SpeechKeywordLookup(voiceCommands.stringValue, speechKeywordOptions) : 0;

            position = EditorGUILayout.GetControlRect();

            //BeginProperty allows tracking of serialized properties for bolding prefab changes etc
            EditorGUI.BeginProperty(position, SpeechComamndsLabel, voiceCommands);
            {
                currentIndex = EditorGUI.Popup(position, SpeechComamndsLabel.text, currentIndex, keywordOptions);
                if (validSpeechKeywords)
                {
                    voiceCommands.stringValue = currentIndex > 0 ? speechKeywordOptions[currentIndex] : string.Empty;
                }
            }
            EditorGUI.EndProperty();
            GUI.enabled = true;

            // show requires gaze because voice command has a value
            if (!string.IsNullOrEmpty(voiceCommands.stringValue))
            {
                using (new EditorGUI.IndentLevelScope())
                {
                    SerializedProperty requireGaze = serializedObject.FindProperty("RequiresFocus");
                    EditorGUILayout.PropertyField(requireGaze, new GUIContent("Requires Focus", "Does the voice command require gazing at this interactable?"));
                }
            }

            // should be 1 or more
            dimensions.intValue = Mathf.Clamp(dimensions.intValue, 1, 9);
            string[] selectionModeNames = Enum.GetNames(typeof(SelectionModes));
            // clamp to values in the enum
            int selectionModeIndex = Mathf.Clamp(dimensions.intValue, 1, selectionModeNames.Length) - 1;

            // user-friendly dimension settings
            SelectionModes selectionMode = SelectionModes.Button;

            position    = EditorGUILayout.GetControlRect();
            GUI.enabled = !isPlayMode;
            EditorGUI.BeginProperty(position, selectionModeLabel, dimensions);
            {
                selectionMode = (SelectionModes)EditorGUI.EnumPopup(position, selectionModeLabel, (SelectionModes)(selectionModeIndex));

                switch (selectionMode)
                {
                case SelectionModes.Button:
                    dimensions.intValue = 1;
                    break;

                case SelectionModes.Toggle:
                    dimensions.intValue = 2;
                    break;

                case SelectionModes.MultiDimension:
                    // multi dimension mode - set min value to 3
                    dimensions.intValue = Mathf.Max(3, dimensions.intValue);
                    position            = EditorGUILayout.GetControlRect();
                    dimensions.intValue = EditorGUI.IntField(position, dimensionsLabel, dimensions.intValue);
                    break;

                default:
                    break;
                }
            }
            EditorGUI.EndProperty();

            if (dimensions.intValue > 1)
            {
                // toggle or multi dimensional button
                using (new EditorGUI.IndentLevelScope())
                {
                    EditorGUILayout.PropertyField(canSelect, new GUIContent("Can Select", "The user can toggle this button"));
                    EditorGUILayout.PropertyField(canDeselect, new GUIContent("Can Deselect", "The user can untoggle this button, set false for a radial interaction."));

                    position = EditorGUILayout.GetControlRect();
                    EditorGUI.BeginProperty(position, startDimensionLabel, startDimensionIndex);
                    {
                        if (dimensions.intValue >= selectionModeNames.Length)
                        {
                            // multi dimensions
                            if (!isPlayMode)
                            {
                                startDimensionIndex.intValue = EditorGUI.IntField(position, startDimensionLabel, startDimensionIndex.intValue);
                            }
                            else
                            {
                                EditorGUI.IntField(position, CurrentDimensionLabel, dimensionIndex.intValue);
                            }
                        }
                        else if (dimensions.intValue == (int)SelectionModes.Toggle + 1)
                        {
                            if (!isPlayMode)
                            {
                                bool isToggled = EditorGUI.Toggle(position, isToggledLabel, startDimensionIndex.intValue > 0);
                                startDimensionIndex.intValue = isToggled ? 1 : 0;
                            }
                            else
                            {
                                bool isToggled = EditorGUI.Toggle(position, isToggledLabel, dimensionIndex.intValue > 0);
                            }
                        }

                        startDimensionIndex.intValue = Mathf.Clamp(startDimensionIndex.intValue, 0, dimensions.intValue - 1);
                    }
                    EditorGUI.EndProperty();
                }
            }
            GUI.enabled = true;

            EditorGUILayout.EndVertical();
        }
예제 #6
0
        public void RenderThemeDefinitions()
        {
            GUIStyle box = InspectorUIUtility.HelpBox(EditorGUI.indentLevel * ThemeBoxMargin);

            // Loop through all InteractableThemePropertySettings of Theme
            for (int index = 0; index < themeDefinitions.arraySize; index++)
            {
                using (new EditorGUILayout.VerticalScope(box))
                {
                    SerializedProperty themeDefinition = themeDefinitions.GetArrayElementAtIndex(index);
                    SerializedProperty className       = themeDefinition.FindPropertyRelative("ClassName");

                    string themeDefinition_prefKey = theme.name + "_Definitions" + index;
                    bool   show = false;
                    using (new EditorGUILayout.HorizontalScope())
                    {
                        show = InspectorUIUtility.DrawSectionFoldoutWithKey(className.stringValue, themeDefinition_prefKey, MixedRealityStylesUtility.BoldFoldoutStyle);

                        if (RenderDeleteButton(index))
                        {
                            return;
                        }
                    }

                    if (show)
                    {
                        EditorGUILayout.Space();

                        using (new EditorGUI.IndentLevelScope())
                        {
                            EditorGUILayout.LabelField("General Properties", EditorStyles.boldLabel);

                            using (new EditorGUILayout.HorizontalScope())
                            {
                                var themeTypes      = TypeCacheUtility.GetSubClasses <InteractableThemeBase>();
                                var themeClassNames = themeTypes.Select(t => t?.Name).ToArray();
                                int id    = Array.IndexOf(themeClassNames, className.stringValue);
                                int newId = EditorGUILayout.Popup("Theme Runtime", id, themeClassNames);

                                // Some old Themes did not properly save a value here
                                SerializedProperty assemblyQualifiedName = themeDefinition.FindPropertyRelative("AssemblyQualifiedName");
                                if (string.IsNullOrEmpty(assemblyQualifiedName.stringValue) && newId != -1)
                                {
                                    assemblyQualifiedName.stringValue = themeTypes[newId].AssemblyQualifiedName;
                                }

                                // If user changed the theme type for current themeDefinition
                                if (id != newId && newId != -1)
                                {
                                    Type oldType = id != -1 ? themeTypes[id] : null;
                                    Type newType = themeTypes[newId];
                                    ChangeThemeDefinitionType(index, oldType, newType);
                                    return;
                                }
                            }

                            var themeType = theme.Definitions[index].ThemeType;
                            if (themeType != null)
                            {
                                SerializedProperty customProperties = themeDefinition.FindPropertyRelative("customProperties");
                                RenderCustomProperties(customProperties);

                                var themeExample = (InteractableThemeBase)Activator.CreateInstance(themeType);

                                if (themeExample.IsEasingSupported)
                                {
                                    RenderEasingProperties(themeDefinition);
                                }

                                if (themeExample.AreShadersSupported)
                                {
                                    RenderShaderProperties(themeDefinition);
                                }

                                EditorGUILayout.Space();

                                RenderThemeStates(themeDefinition);
                            }
                            else
                            {
                                InspectorUIUtility.DrawError("Theme Runtime Type is not valid");
                            }
                        }
                    }
                }
            }

            // If no theme properties assigned, add a default one
            if (themeDefinitions.arraySize < 1 || GUILayout.Button(AddThemePropertyLabel))
            {
                AddThemeDefinition();
            }
        }
예제 #7
0
        private void RenderControllerList(SerializedProperty controllerList)
        {
            if (thisProfile.MixedRealityControllerMappingProfiles.Length != controllerList.arraySize)
            {
                return;
            }

            if (InspectorUIUtility.RenderIndentedButton(ControllerAddButtonContent, EditorStyles.miniButton))
            {
                AddController(controllerList, typeof(GenericJoystickController));
                return;
            }

            controllerRenderList.Clear();

            showControllerDefinitions = EditorGUILayout.Foldout(showControllerDefinitions, "Controller Definitions");
            if (showControllerDefinitions)
            {
                using (var outerVerticalScope = new GUILayout.VerticalScope())
                {
                    GUILayout.HorizontalScope horizontalScope = null;

                    for (int i = 0; i < thisProfile.MixedRealityControllerMappingProfiles.Length; i++)
                    {
                        MixedRealityControllerMapping controllerMapping = thisProfile.MixedRealityControllerMappingProfiles[i];
                        Type controllerType = controllerMapping.ControllerType;
                        if (controllerType == null)
                        {
                            continue;
                        }

                        Handedness handedness = controllerMapping.Handedness;
                        bool       useCustomInteractionMappings         = controllerMapping.HasCustomInteractionMappings;
                        SupportedControllerType supportedControllerType = controllerMapping.SupportedControllerType;

                        var controllerMappingProperty = controllerList.GetArrayElementAtIndex(i);
                        var handednessProperty        = controllerMappingProperty.FindPropertyRelative("handedness");

                        if (!useCustomInteractionMappings)
                        {
                            bool skip = false;

                            // Merge controllers with the same supported controller type.
                            for (int j = 0; j < controllerRenderList.Count; j++)
                            {
                                if (controllerRenderList[j].SupportedControllerType == supportedControllerType &&
                                    controllerRenderList[j].Handedness == handedness)
                                {
                                    try
                                    {
                                        thisProfile.MixedRealityControllerMappingProfiles[i].SynchronizeInputActions(controllerRenderList[j].Interactions);
                                    }
                                    catch (ArgumentException e)
                                    {
                                        Debug.LogError($"Controller mappings between {thisProfile.MixedRealityControllerMappingProfiles[i].Description} and {controllerMapping.Description} do not match. Error message: {e.Message}");
                                    }
                                    serializedObject.ApplyModifiedProperties();
                                    skip = true;
                                }
                            }

                            if (skip)
                            {
                                continue;
                            }
                        }

                        controllerRenderList.Add(new ControllerRenderProfile(supportedControllerType, handedness, thisProfile.MixedRealityControllerMappingProfiles[i].Interactions));

                        string controllerTitle      = thisProfile.MixedRealityControllerMappingProfiles[i].Description;
                        var    interactionsProperty = controllerMappingProperty.FindPropertyRelative("interactions");

                        if (useCustomInteractionMappings)
                        {
                            if (horizontalScope != null)
                            {
                                horizontalScope.Dispose(); horizontalScope = null;
                            }

                            GUILayout.Space(24f);

                            using (var verticalScope = new GUILayout.VerticalScope())
                            {
                                using (horizontalScope = new GUILayout.HorizontalScope())
                                {
                                    EditorGUILayout.LabelField(controllerTitle, EditorStyles.boldLabel);

                                    if (GUILayout.Button(ControllerMinusButtonContent, EditorStyles.miniButtonRight, GUILayout.Width(24f)))
                                    {
                                        controllerList.DeleteArrayElementAtIndex(i);
                                        return;
                                    }
                                }

                                EditorGUI.BeginChangeCheck();

                                // Generic Type dropdown
                                Type[] genericTypes           = MixedRealityControllerMappingProfile.CustomControllerMappingTypes;
                                var    genericTypeListContent = new GUIContent[genericTypes.Length];
                                var    genericTypeListIds     = new int[genericTypes.Length];
                                int    currentGenericType     = -1;
                                for (int genericTypeIdx = 0; genericTypeIdx < genericTypes.Length; genericTypeIdx++)
                                {
                                    var attribute = MixedRealityControllerAttribute.Find(genericTypes[genericTypeIdx]);
                                    if (attribute != null)
                                    {
                                        genericTypeListContent[genericTypeIdx] = new GUIContent(attribute.SupportedControllerType.ToString().Replace("Generic", "").ToProperCase() + " Controller");
                                    }
                                    else
                                    {
                                        genericTypeListContent[genericTypeIdx] = new GUIContent("Unknown Controller");
                                    }

                                    genericTypeListIds[genericTypeIdx] = genericTypeIdx;

                                    if (controllerType == genericTypes[genericTypeIdx])
                                    {
                                        currentGenericType = genericTypeIdx;
                                    }
                                }
                                Debug.Assert(currentGenericType != -1);

                                currentGenericType = EditorGUILayout.IntPopup(GenericTypeContent, currentGenericType, genericTypeListContent, genericTypeListIds);
                                controllerType     = genericTypes[currentGenericType];

                                {
                                    // Handedness dropdown
                                    var attribute = MixedRealityControllerAttribute.Find(controllerType);
                                    if (attribute != null && attribute.SupportedHandedness.Length >= 1)
                                    {
                                        // Make sure handedness is valid for the selected controller type.
                                        if (Array.IndexOf(attribute.SupportedHandedness, (Handedness)handednessProperty.intValue) < 0)
                                        {
                                            handednessProperty.intValue = (int)attribute.SupportedHandedness[0];
                                        }

                                        if (attribute.SupportedHandedness.Length >= 2)
                                        {
                                            var handednessListContent = new GUIContent[attribute.SupportedHandedness.Length];
                                            var handednessListIds     = new int[attribute.SupportedHandedness.Length];
                                            for (int handednessIdx = 0; handednessIdx < attribute.SupportedHandedness.Length; handednessIdx++)
                                            {
                                                handednessListContent[handednessIdx] = new GUIContent(attribute.SupportedHandedness[handednessIdx].ToString());
                                                handednessListIds[handednessIdx]     = (int)attribute.SupportedHandedness[handednessIdx];
                                            }

                                            handednessProperty.intValue = EditorGUILayout.IntPopup(HandednessTypeContent, handednessProperty.intValue, handednessListContent, handednessListIds);
                                        }
                                    }
                                    else
                                    {
                                        handednessProperty.intValue = (int)Handedness.None;
                                    }
                                }

                                if (EditorGUI.EndChangeCheck())
                                {
                                    interactionsProperty.ClearArray();
                                    serializedObject.ApplyModifiedProperties();
                                    thisProfile.MixedRealityControllerMappingProfiles[i].ControllerType.Type = genericTypes[currentGenericType];
                                    thisProfile.MixedRealityControllerMappingProfiles[i].SetDefaultInteractionMapping(true);
                                    serializedObject.ApplyModifiedProperties();
                                    return;
                                }

                                if (InspectorUIUtility.RenderIndentedButton("Edit Input Action Map"))
                                {
                                    ControllerPopupWindow.Show(controllerMapping, interactionsProperty, handedness);
                                }

                                if (InspectorUIUtility.RenderIndentedButton("Reset Input Actions"))
                                {
                                    interactionsProperty.ClearArray();
                                    serializedObject.ApplyModifiedProperties();
                                    thisProfile.MixedRealityControllerMappingProfiles[i].SetDefaultInteractionMapping(true);
                                    serializedObject.ApplyModifiedProperties();
                                }
                            }
                        }
                        else
                        {
                            if (supportedControllerType == SupportedControllerType.WindowsMixedReality &&
                                handedness == Handedness.None)
                            {
                                controllerTitle = "HoloLens Voice and Clicker";
                            }

                            if (handedness != Handedness.Right)
                            {
                                if (horizontalScope != null)
                                {
                                    horizontalScope.Dispose(); horizontalScope = null;
                                }
                                horizontalScope = new GUILayout.HorizontalScope();
                            }

                            var buttonContent = new GUIContent(controllerTitle, ControllerMappingLibrary.GetControllerTextureScaled(controllerType, handedness));

                            if (GUILayout.Button(buttonContent, MixedRealityStylesUtility.ControllerButtonStyle, GUILayout.Height(128f), GUILayout.MinWidth(32f), GUILayout.ExpandWidth(true)))
                            {
                                ControllerPopupWindow.Show(controllerMapping, interactionsProperty, handedness);
                            }
                        }
                    }

                    if (horizontalScope != null)
                    {
                        horizontalScope.Dispose(); horizontalScope = null;
                    }
                }
            }
        }
        private void RenderStateContainers()
        {
            InspectorUIUtility.DrawTitle("State Animations");

            for (int i = 0; i < stateContainers.arraySize; i++)
            {
                SerializedProperty stateContainer              = stateContainers.GetArrayElementAtIndex(i);
                SerializedProperty stateContainerName          = stateContainer.FindPropertyRelative("stateName");
                SerializedProperty animationTargetsList        = stateContainer.FindPropertyRelative("animationTargets");
                SerializedProperty stateContainerAnimationClip = stateContainer.FindPropertyRelative("animationClip");
                SerializedProperty animationTransitionDuration = stateContainer.FindPropertyRelative("animationTransitionDuration");

                Color previousGUIColor = GUI.color;

                using (new EditorGUILayout.HorizontalScope())
                {
                    string stateFoldoutID = stateContainerName.stringValue + "StateContainer" + "_" + target.name;

                    if (inPlayMode)
                    {
                        BaseInteractiveElement baseInteractiveElement = interactiveElement.objectReferenceValue as BaseInteractiveElement;

                        if (baseInteractiveElement.isActiveAndEnabled)
                        {
                            if (baseInteractiveElement.IsStateActive(stateContainerName.stringValue))
                            {
                                GUI.color = Color.cyan;
                            }
                        }
                    }

                    using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
                    {
                        EditorGUILayout.Space();

                        if (InspectorUIUtility.DrawSectionFoldoutWithKey(stateContainerName.stringValue, stateFoldoutID, MixedRealityStylesUtility.TitleFoldoutStyle, false))
                        {
                            using (new EditorGUI.IndentLevelScope())
                            {
                                using (var check = new EditorGUI.ChangeCheckScope())
                                {
                                    EditorGUILayout.PropertyField(stateContainerAnimationClip);
                                    EditorGUILayout.PropertyField(animationTransitionDuration);

                                    if (check.changed)
                                    {
                                        instance.SetAnimationTransitionDuration(stateContainerName.stringValue, animationTransitionDuration.floatValue);
                                        instance.SetAnimationClip(stateContainerName.stringValue, stateContainerAnimationClip.objectReferenceValue as AnimationClip);
                                    }
                                }

                                RenderAnimationTargetList(animationTargetsList, stateContainerName);
                            }
                        }

                        EditorGUILayout.Space();
                    }

                    GUI.color = previousGUIColor;

                    if (!inPlayMode)
                    {
                        if (InspectorUIUtility.SmallButton(RemoveButtonLabel))
                        {
                            instance.RemoveAnimatorState(instance.RootStateMachine, stateContainerName.stringValue);
                            stateContainers.DeleteArrayElementAtIndex(i);
                            break;
                        }
                    }
                }
            }
        }
        private void RenderControllerList(SerializedProperty controllerList)
        {
            if (thisProfile.ControllerVisualizationSettings.Length != controllerList.arraySize)
            {
                return;
            }

            EditorGUILayout.Space();

            if (InspectorUIUtility.RenderIndentedButton(ControllerAddButtonContent, EditorStyles.miniButton))
            {
                controllerList.InsertArrayElementAtIndex(controllerList.arraySize);
                var index             = controllerList.arraySize - 1;
                var controllerSetting = controllerList.GetArrayElementAtIndex(index);

                var mixedRealityControllerMappingDescription = controllerSetting.FindPropertyRelative("description");
                mixedRealityControllerMappingDescription.stringValue = typeof(GenericJoystickController).Name;

                var mixedRealityControllerHandedness = controllerSetting.FindPropertyRelative("handedness");
                mixedRealityControllerHandedness.intValue = 1;

                serializedObject.ApplyModifiedProperties();

                thisProfile.ControllerVisualizationSettings[index].ControllerType.Type = typeof(GenericJoystickController);
                return;
            }

            for (int i = 0; i < controllerList.arraySize; i++)
            {
                EditorGUILayout.Space();
                EditorGUILayout.BeginHorizontal();

                var  controllerSetting = controllerList.GetArrayElementAtIndex(i);
                var  mixedRealityControllerMappingDescription = controllerSetting.FindPropertyRelative("description");
                bool hasValidType = thisProfile.ControllerVisualizationSettings[i].ControllerType != null &&
                                    thisProfile.ControllerVisualizationSettings[i].ControllerType.Type != null;

                mixedRealityControllerMappingDescription.stringValue = hasValidType
                    ? thisProfile.ControllerVisualizationSettings[i].ControllerType.Type.Name.ToProperCase()
                    : "Undefined Controller";

                serializedObject.ApplyModifiedProperties();
                var mixedRealityControllerHandedness = controllerSetting.FindPropertyRelative("handedness");
                EditorGUILayout.LabelField($"{mixedRealityControllerMappingDescription.stringValue} {((Handedness)mixedRealityControllerHandedness.intValue).ToString().ToProperCase()} Hand", EditorStyles.boldLabel);

                if (GUILayout.Button(ControllerMinusButtonContent, EditorStyles.miniButtonRight, GUILayout.Width(24f)))
                {
                    controllerList.DeleteArrayElementAtIndex(i);
                    EditorGUILayout.EndHorizontal();
                    GUILayout.EndVertical();
                    return;
                }

                EditorGUILayout.EndHorizontal();

                EditorGUILayout.PropertyField(controllerSetting.FindPropertyRelative("controllerType"));
                EditorGUILayout.PropertyField(controllerSetting.FindPropertyRelative("controllerVisualizationType"));

                if (!hasValidType)
                {
                    EditorGUILayout.HelpBox("A controller type must be defined!", MessageType.Error);
                }

                var handednessValue = mixedRealityControllerHandedness.intValue - 1;

                // Reset in case it was set to something other than left or right.
                if (handednessValue < 0 || handednessValue > 1)
                {
                    handednessValue = 0;
                }

                EditorGUI.BeginChangeCheck();
                handednessValue = EditorGUILayout.IntPopup(new GUIContent(mixedRealityControllerHandedness.displayName, mixedRealityControllerHandedness.tooltip), handednessValue, HandednessSelections, null);

                if (EditorGUI.EndChangeCheck())
                {
                    mixedRealityControllerHandedness.intValue = handednessValue + 1;
                }

                var overrideModel       = controllerSetting.FindPropertyRelative("overrideModel");
                var overrideModelPrefab = overrideModel.objectReferenceValue as GameObject;

                var controllerUseDefaultModelOverride = controllerSetting.FindPropertyRelative("useDefaultModel");

                using (new GUILayout.HorizontalScope())
                {
                    EditorGUILayout.PropertyField(controllerUseDefaultModelOverride);

                    var defaultModelMaterial = controllerSetting.FindPropertyRelative("defaultModelMaterial");
                    EditorGUILayout.PropertyField(defaultModelMaterial);
                }

                if (controllerUseDefaultModelOverride.boolValue && overrideModelPrefab != null)
                {
                    EditorGUILayout.HelpBox("When default model is used, the override model will only be used if the default model cannot be loaded from the driver.", MessageType.Warning);
                }

                EditorGUI.BeginChangeCheck();
                overrideModelPrefab = EditorGUILayout.ObjectField(new GUIContent(overrideModel.displayName, "If no override model is set, the global model is used."), overrideModelPrefab, typeof(GameObject), false) as GameObject;

                if (EditorGUI.EndChangeCheck() && CheckVisualizer(overrideModelPrefab))
                {
                    overrideModel.objectReferenceValue = overrideModelPrefab;
                }
            }
        }
        private void RenderControllerList(SerializedProperty controllerList)
        {
            if (thisProfile.ControllerVisualizationSettings.Length != controllerList.arraySize)
            {
                return;
            }

            EditorGUILayout.Space();

            if (InspectorUIUtility.RenderIndentedButton(ControllerAddButtonContent, EditorStyles.miniButton))
            {
                controllerList.InsertArrayElementAtIndex(controllerList.arraySize);
                var index             = controllerList.arraySize - 1;
                var controllerSetting = controllerList.GetArrayElementAtIndex(index);

                var mixedRealityControllerMappingDescription = controllerSetting.FindPropertyRelative("description");
                mixedRealityControllerMappingDescription.stringValue = typeof(GenericJoystickController).Name;

                var mixedRealityControllerHandedness = controllerSetting.FindPropertyRelative("handedness");
                mixedRealityControllerHandedness.intValue = 1;

                serializedObject.ApplyModifiedProperties();

                thisProfile.ControllerVisualizationSettings[index].ControllerType.Type = typeof(GenericJoystickController);
                return;
            }

#if UNITY_2019
            xrPipelineUtility.RenderXRPipelineTabs();
#endif // UNITY_2019

            for (int i = 0; i < controllerList.arraySize; i++)
            {
                var        controllerSetting = controllerList.GetArrayElementAtIndex(i);
                var        mixedRealityControllerMappingDescription = controllerSetting.FindPropertyRelative("description");
                SystemType controllerType = thisProfile.ControllerVisualizationSettings[i].ControllerType;
                bool       hasValidType   = controllerType != null &&
                                            controllerType.Type != null;

                if (hasValidType)
                {
                    MixedRealityControllerAttribute controllerAttribute = MixedRealityControllerAttribute.Find(controllerType.Type);
                    if (controllerAttribute != null && !controllerAttribute.SupportedUnityXRPipelines.HasFlag(xrPipelineUtility.SelectedPipeline))
                    {
                        continue;
                    }
                }
                else if (!MixedRealityProjectPreferences.ShowNullDataProviders)
                {
                    continue;
                }

                EditorGUILayout.Space();

                mixedRealityControllerMappingDescription.stringValue = hasValidType
                    ? controllerType.Type.Name.ToProperCase()
                    : "Undefined Controller";

                serializedObject.ApplyModifiedProperties();
                SerializedProperty mixedRealityControllerHandedness = controllerSetting.FindPropertyRelative("handedness");

                using (new EditorGUILayout.HorizontalScope())
                {
                    EditorGUILayout.LabelField($"{mixedRealityControllerMappingDescription.stringValue} {((Handedness)mixedRealityControllerHandedness.intValue).ToString().ToProperCase()} Hand{(mixedRealityControllerHandedness.intValue == (int)(Handedness.Both) ? "s" : "")}", EditorStyles.boldLabel);

                    if (GUILayout.Button(ControllerMinusButtonContent, EditorStyles.miniButtonRight, GUILayout.Width(24f)))
                    {
                        controllerList.DeleteArrayElementAtIndex(i);
                        return;
                    }
                }

                EditorGUILayout.PropertyField(controllerSetting.FindPropertyRelative("controllerType"));
                EditorGUILayout.PropertyField(controllerSetting.FindPropertyRelative("controllerVisualizationType"));

                if (!hasValidType)
                {
                    EditorGUILayout.HelpBox("A controller type must be defined!", MessageType.Error);
                }

                var handednessValue = mixedRealityControllerHandedness.intValue - 1;

                // Reset in case it was set to something other than left, right, or both.
                if (handednessValue < 0 || handednessValue > 2)
                {
                    handednessValue = 0;
                }

                EditorGUI.BeginChangeCheck();
                handednessValue = EditorGUILayout.IntPopup(new GUIContent(mixedRealityControllerHandedness.displayName, mixedRealityControllerHandedness.tooltip), handednessValue, HandednessSelections, null);
                if (EditorGUI.EndChangeCheck())
                {
                    mixedRealityControllerHandedness.intValue = handednessValue + 1;
                }

                var overrideModel       = controllerSetting.FindPropertyRelative("overrideModel");
                var overrideModelPrefab = overrideModel.objectReferenceValue as GameObject;

                var controllerUsePlatformModelOverride = controllerSetting.FindPropertyRelative("usePlatformModels");
                EditorGUILayout.PropertyField(controllerUsePlatformModelOverride);
                if (controllerUsePlatformModelOverride.boolValue)
                {
                    var platformModelMaterial = controllerSetting.FindPropertyRelative("platformModelMaterial");
                    EditorGUILayout.PropertyField(platformModelMaterial);
                }

                if (controllerUsePlatformModelOverride.boolValue && overrideModelPrefab != null)
                {
                    EditorGUILayout.HelpBox("When platform model is used, the override model will only be used if the default model cannot be loaded from the driver.", MessageType.Warning);
                }

                EditorGUI.BeginChangeCheck();
                overrideModelPrefab = EditorGUILayout.ObjectField(new GUIContent(overrideModel.displayName, "If no override model is set, the global model is used."), overrideModelPrefab, typeof(GameObject), false) as GameObject;
                if (overrideModelPrefab == null && !controllerUsePlatformModelOverride.boolValue)
                {
                    EditorGUILayout.HelpBox("No override model was assigned and this controller will not attempt to use the platform's model, the global model will be used instead", MessageType.Warning);
                }

                if (EditorGUI.EndChangeCheck() && CheckVisualizer(overrideModelPrefab))
                {
                    overrideModel.objectReferenceValue = overrideModelPrefab;
                }
            }
        }
        private void RenderAnimationTargetList(SerializedProperty animationTargetList, SerializedProperty stateContainerName)
        {
            using (new EditorGUI.IndentLevelScope())
            {
                for (int j = 0; j < animationTargetList.arraySize; j++)
                {
                    SerializedProperty animationTarget        = animationTargetList.GetArrayElementAtIndex(j);
                    SerializedProperty targetObj              = animationTarget.FindPropertyRelative("target");
                    SerializedProperty animatablePropertyList = animationTarget.FindPropertyRelative("stateAnimatableProperties");

                    EditorGUILayout.Space();

                    using (new EditorGUILayout.VerticalScope(GUI.skin.box))
                    {
                        EditorGUILayout.Space();

                        using (new EditorGUILayout.HorizontalScope())
                        {
                            EditorGUILayout.PropertyField(targetObj);

                            if (InspectorUIUtility.SmallButton(RemoveButtonLabel))
                            {
                                // Clear keyframes of a deleted target
                                for (int k = 0; k < animatablePropertyList.arraySize; k++)
                                {
                                    SerializedProperty animatableProperty     = animatablePropertyList.GetArrayElementAtIndex(k);
                                    SerializedProperty animatablePropertyName = animatableProperty.FindPropertyRelative("animatablePropertyName");

                                    if (animatableProperty != null)
                                    {
                                        RemoveKeyFrames(stateContainerName.stringValue, animatablePropertyName.stringValue, j);
                                    }
                                }

                                animationTargetList.DeleteArrayElementAtIndex(j);
                                break;
                            }
                        }

                        using (new EditorGUILayout.VerticalScope())
                        {
                            if (targetObj.objectReferenceValue != null)
                            {
                                InspectorUIUtility.DrawDivider();

                                GameObject targetGameObject = targetObj.objectReferenceValue as GameObject;

                                // Ensure the target game object has a State Visualizer attached or is a child of an
                                // object with State Visualizer attached
                                if (targetGameObject.transform.FindAncestorComponent <StateVisualizer>(true))
                                {
                                    string animatablePropertiesFoldoutID = stateContainerName.stringValue + "AnimatableProperties" + "_" + targetGameObject.name + target.name;

                                    if (InspectorUIUtility.DrawSectionFoldoutWithKey(targetGameObject.name + " Animatable Properties", animatablePropertiesFoldoutID, MixedRealityStylesUtility.BoldFoldoutStyle, false))
                                    {
                                        using (new EditorGUI.IndentLevelScope())
                                        {
                                            RenderAnimatablePropertyList(animatablePropertyList, stateContainerName, j);
                                        }
                                    }
                                }
                                else
                                {
                                    targetObj.objectReferenceValue = null;
                                    Debug.LogError("The target object must be itself or a child object");
                                }
                            }

                            EditorGUILayout.Space();
                        }
                    }
                }

                EditorGUILayout.Space();

                RenderAddTargetButton(animationTargetList);
            }
        }
        private void RenderList(SerializedProperty list)
        {
            // Disable gestures list if we could not initialize successfully
            using (new EditorGUI.DisabledGroupScope(!isInitialized))
            {
                EditorGUILayout.Space();
                using (new EditorGUILayout.VerticalScope())
                {
                    if (InspectorUIUtility.RenderIndentedButton(AddButtonContent, EditorStyles.miniButton))
                    {
                        list.arraySize += 1;
                        var speechCommand = list.GetArrayElementAtIndex(list.arraySize - 1);
                        var keyword       = speechCommand.FindPropertyRelative("description");
                        keyword.stringValue = string.Empty;
                        var gestureType = speechCommand.FindPropertyRelative("gestureType");
                        gestureType.intValue = (int)GestureInputType.None;
                        var action   = speechCommand.FindPropertyRelative("action");
                        var actionId = action.FindPropertyRelative("id");
                        actionId.intValue = 0;
                        var actionDescription = action.FindPropertyRelative("description");
                        actionDescription.stringValue = string.Empty;
                        var actionConstraint = action.FindPropertyRelative("axisConstraint");
                        actionConstraint.intValue = 0;
                    }

                    if (list == null || list.arraySize == 0)
                    {
                        EditorGUILayout.HelpBox("Define a new Gesture.", MessageType.Warning);
                        UpdateGestureLabels();
                        return;
                    }

                    using (new EditorGUILayout.HorizontalScope())
                    {
                        var labelWidth = EditorGUIUtility.labelWidth;
                        EditorGUIUtility.labelWidth = 24f;
                        EditorGUILayout.LabelField(DescriptionContent, GUILayout.ExpandWidth(true));
                        EditorGUILayout.LabelField(GestureTypeContent, GUILayout.Width(80f));
                        EditorGUILayout.LabelField(ActionContent, GUILayout.Width(64f));
                        EditorGUILayout.LabelField(string.Empty, GUILayout.Width(24f));
                        EditorGUIUtility.labelWidth = labelWidth;
                    }

                    var inputActions = GetInputActions();

                    for (int i = 0; i < list.arraySize; i++)
                    {
                        using (new EditorGUILayout.HorizontalScope())
                        {
                            SerializedProperty gesture = list.GetArrayElementAtIndex(i);
                            var keyword           = gesture.FindPropertyRelative("description");
                            var gestureType       = gesture.FindPropertyRelative("gestureType");
                            var action            = gesture.FindPropertyRelative("action");
                            var actionId          = action.FindPropertyRelative("id");
                            var actionDescription = action.FindPropertyRelative("description");
                            var actionConstraint  = action.FindPropertyRelative("axisConstraint");

                            EditorGUILayout.PropertyField(keyword, GUIContent.none, GUILayout.ExpandWidth(true));

                            Debug.Assert(allGestureLabels.Length == allGestureIds.Length);

                            var gestureLabels = new GUIContent[allGestureLabels.Length + 1];
                            var gestureIds    = new int[allGestureIds.Length + 1];

                            gestureLabels[0] = new GUIContent(((GestureInputType)gestureType.intValue).ToString());
                            gestureIds[0]    = gestureType.intValue;

                            for (int j = 0; j < allGestureLabels.Length; j++)
                            {
                                gestureLabels[j + 1] = allGestureLabels[j];
                                gestureIds[j + 1]    = allGestureIds[j];
                            }

                            EditorGUI.BeginChangeCheck();
                            gestureType.intValue = EditorGUILayout.IntPopup(GUIContent.none, gestureType.intValue, gestureLabels, gestureIds, GUILayout.Width(80f));

                            if (EditorGUI.EndChangeCheck())
                            {
                                serializedObject.ApplyModifiedProperties();
                                UpdateGestureLabels();
                            }

                            EditorGUI.BeginChangeCheck();

                            actionId.intValue = EditorGUILayout.IntPopup(GUIContent.none, actionId.intValue, actionLabels, actionIds, GUILayout.Width(64f));

                            if (EditorGUI.EndChangeCheck())
                            {
                                MixedRealityInputAction inputAction = MixedRealityInputAction.None;
                                int idx = actionId.intValue - 1;
                                if (idx >= 0 && idx < inputActions.Length)
                                {
                                    inputAction = inputActions[idx];
                                }

                                actionDescription.stringValue = inputAction.Description;
                                actionConstraint.intValue     = (int)inputAction.AxisConstraint;
                                serializedObject.ApplyModifiedProperties();
                            }

                            if (GUILayout.Button(MinusButtonContent, EditorStyles.miniButtonRight, GUILayout.Width(24f)))
                            {
                                list.DeleteArrayElementAtIndex(i);
                                serializedObject.ApplyModifiedProperties();
                                UpdateGestureLabels();
                            }
                        }
                    }
                }
            }
        }
        private void DrawDebugSection()
        {
            if (InspectorUIUtility.DrawSectionFoldoutWithKey("Debug Options", ShowDebugOptionsPrefKey, MixedRealityStylesUtility.BoldFoldoutStyle))
            {
                using (new EditorGUI.IndentLevelScope())
                {
                    using (var check = new EditorGUI.ChangeCheckScope())
                    {
                        EditorGUILayout.PropertyField(maskEnabled);
                        if (check.changed)
                        {
                            scrollView.MaskEnabled = maskEnabled.boolValue;
                        }
                    }

                    using (new EditorGUI.DisabledGroupScope(EditorApplication.isPlaying))
                    {
                        visibleDebugPlanes = EditorGUILayout.Toggle("Show Threshold Planes", visibleDebugPlanes);
                        EditorGUILayout.Space();
                    }
                }

                using (new EditorGUI.IndentLevelScope())
                {
                    using (new EditorGUI.DisabledGroupScope(!EditorApplication.isPlaying))
                    {
                        if (ShowDebugPagination = EditorGUILayout.Foldout(ShowDebugPagination, new GUIContent("Debug Pagination", "Pagination is only available during playmode."), MixedRealityStylesUtility.BoldFoldoutStyle))
                        {
                            using (new EditorGUI.IndentLevelScope())
                            {
                                animateTransition = EditorGUILayout.Toggle(new GUIContent("Animate", "Toggling will use animation to move scroller to new position."), animateTransition);

                                using (new EditorGUILayout.HorizontalScope())
                                {
                                    debugPaginationMode  = (PaginationMode)EditorGUILayout.EnumPopup(new GUIContent("Pagination Mode"), debugPaginationMode, GUILayout.Width(400.0f));
                                    paginationMoveNumber = EditorGUILayout.IntField(paginationMoveNumber);

                                    if (GUILayout.Button("Move"))
                                    {
                                        switch (debugPaginationMode)
                                        {
                                        case PaginationMode.ByTier:
                                        default:
                                            scrollView.MoveByTiers(paginationMoveNumber, animateTransition);
                                            break;

                                        case PaginationMode.ByPage:
                                            scrollView.MoveByPages(paginationMoveNumber, animateTransition);
                                            break;

                                        case PaginationMode.ToCellIndex:
                                            scrollView.MoveToIndex(paginationMoveNumber, animateTransition);
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        private void RenderSection(ToolboxCategory bucket)
        {
            using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
            {
                string key = $"MixedRealityToolboxWindow_{bucket.CategoryName}";
                if (InspectorUIUtility.DrawSectionFoldoutWithKey(bucket.CategoryName, key, MixedRealityStylesUtility.BoldTitleFoldoutStyle))
                {
                    InspectorUIUtility.DrawDivider();
                    EditorGUILayout.Space();

                    bool isCategoryNameSearchMatch = IsSearchMatch(bucket.CategoryName, searchString);

                    List <ToolboxItem> validItems = new List <ToolboxItem>();
                    foreach (var item in bucket.Items)
                    {
                        if (item != null && item.Prefab != null &&
                            (isCategoryNameSearchMatch || IsSearchMatch(item, searchString)))
                        {
                            validItems.Add(item);
                        }
                    }

                    bool requiresCanvas = bucket.CategoryName.Contains(RequiresCanvas);
                    if (requiresCanvas)
                    {
                        using (new EditorGUILayout.HorizontalScope())
                        {
                            dropdownIndex = EditorGUILayout.Popup(CanvasDropdownContent, dropdownIndex, dropdownValues);

                            if (GUILayout.Button(RefreshButtonContent, EditorStyles.miniButton, GUILayout.Width(ReloadButtonWidth)))
                            {
                                FindAllMRTKCanvases();
                            }
                        }

                        if (canvasUtilities.Length == 0)
                        {
                            GUIStyle CanvasWarningStyle = new GUIStyle(EditorStyles.textField)
                            {
                                wordWrap = true
                            };
                            GUILayout.TextField("These MRTK components require an MRTK Canvas Utility on one of the Unity UI Canvases in the scene.\nNone were detected. Press refresh if you recently added any.\nTo create a MRTK Canvas: GameObject > UI > Canvas, and under the Canvas component in the inspector, click 'Convert to MRTK Canvas' button.", CanvasWarningStyle);
                        }
                    }

                    EditorGUILayout.Space();

                    // Render grid of toolbox items
                    int itemsPerRow = Mathf.Max((int)(position.width / ToolboxItemWidth), 1);

                    for (int row = 0; row <= validItems.Count / itemsPerRow; row++)
                    {
                        using (new EditorGUILayout.HorizontalScope())
                        {
                            int startIndex = row * itemsPerRow;
                            for (int col = 0; col < itemsPerRow && startIndex + col < validItems.Count; col++)
                            {
                                var item = validItems[startIndex + col];
                                RenderToolboxItem(item, requiresCanvas);
                            }
                        }
                    }
                }
            }
        }
        public static void RenderEventSettings(SerializedProperty eventItem, int index, InteractableTypesContainer options, InspectorUIUtility.MultiListButtonEvent changeEvent, InspectorUIUtility.ListButtonEvent removeEvent)
        {
            EditorGUILayout.BeginVertical(EditorStyles.helpBox);

            SerializedProperty uEvent                = eventItem.FindPropertyRelative("Event");
            SerializedProperty eventName             = eventItem.FindPropertyRelative("Name");
            SerializedProperty className             = eventItem.FindPropertyRelative("ClassName");
            SerializedProperty assemblyQualifiedName = eventItem.FindPropertyRelative("AssemblyQualifiedName");
            SerializedProperty hideEvents            = eventItem.FindPropertyRelative("HideUnityEvents");

            // show event dropdown
            int id = InspectorUIUtility.ReverseLookup(className.stringValue, options.ClassNames);

            EditorGUILayout.BeginHorizontal();

            Rect       position    = EditorGUILayout.GetControlRect();
            GUIContent selectLabel = new GUIContent("Select Event Type", "Select the event type from the list");

            EditorGUI.BeginProperty(position, selectLabel, className);
            {
                //int newId = EditorGUI.Popup(position, selectLabel.text, id, options.ClassNames);
                int newId = EditorGUI.Popup(position, id, options.ClassNames);

                if (id != newId || String.IsNullOrEmpty(className.stringValue))
                {
                    className.stringValue             = options.ClassNames[newId];
                    assemblyQualifiedName.stringValue = options.AssemblyQualifiedNames[newId];

                    changeEvent(new int[] { index, newId }, eventItem);
                }
            }
            EditorGUI.EndProperty();

            if (removeEvent != null)
            {
                InspectorUIUtility.FlexButton(new GUIContent("Remove Event"), index, removeEvent);
            }

            EditorGUILayout.EndHorizontal();
            EditorGUILayout.Space();

            if (!hideEvents.boolValue)
            {
                EditorGUILayout.PropertyField(uEvent, new GUIContent(eventName.stringValue));
            }

            // show event properties
            SerializedProperty eventSettings = eventItem.FindPropertyRelative("Settings");

            for (int j = 0; j < eventSettings.arraySize; j++)
            {
                SerializedProperty propertyField = eventSettings.GetArrayElementAtIndex(j);
                bool isEvent = InspectorFieldsUtility.IsPropertyType(propertyField, InspectorField.FieldTypes.Event);

                if (!hideEvents.boolValue || !isEvent)
                {
                    InspectorFieldsUtility.DisplayPropertyField(eventSettings.GetArrayElementAtIndex(j));
                }
            }

            EditorGUILayout.EndVertical();
        }
        /// <summary>
        /// Helper function to render header correctly for all profiles
        /// </summary>
        /// <param name="title">Title of profile</param>
        /// <param name="description">profile tooltip describing purpose</param>
        /// <param name="selectionObject">The profile object. Used to re-select the object after MRTK instance is created.</param>
        /// <param name="isProfileInitialized">profile properties are full initialized for rendering</param>
        /// <param name="backText">Text for back button if not rendering as sub-profile</param>
        /// <param name="backProfile">Target profile to return to if not rendering as sub-profile</param>
        /// <returns>True if the rest of the profile should be rendered.</returns>
        protected bool RenderProfileHeader(string title, string description, Object selectionObject, bool isProfileInitialized = true, BackProfileType returnProfileTarget = BackProfileType.Configuration)
        {
            if (!RenderMRTKLogoAndSearch())
            {
                CheckEditorPlayMode();
                return(false);
            }

            var profile = target as BaseMixedRealityProfile;

            if (!RenderAsSubProfile)
            {
                CheckEditorPlayMode();

                if (!profile.IsCustomProfile)
                {
                    EditorGUILayout.HelpBox("Default MRTK profiles cannot be edited. Create a clone of this profile to modify settings.", MessageType.Warning);
                    if (GUILayout.Button(new GUIContent("Clone")))
                    {
                        MixedRealityProfileCloneWindow.OpenWindow(null, (BaseMixedRealityProfile)target, null);
                    }
                }

                if (IsProfileInActiveInstance())
                {
                    DrawBacktrackProfileButton(returnProfileTarget);
                }

                if (!isProfileInitialized)
                {
                    if (!MixedRealityToolkit.IsInitialized)
                    {
                        EditorGUILayout.HelpBox("There is not a MRTK instance in your scene. Some properties may not be editable", MessageType.Error);
                        if (InspectorUIUtility.RenderIndentedButton(new GUIContent("Add Mixed Reality Toolkit instance to scene"), EditorStyles.miniButton))
                        {
                            MixedRealityInspectorUtility.AddMixedRealityToolkitToScene(MixedRealityInspectorUtility.GetDefaultConfigProfile());
                            // After the toolkit has been created, set the selection back to this item so the user doesn't get lost
                            Selection.activeObject = selectionObject;
                        }
                    }
                    else if (!MixedRealityToolkit.Instance.HasActiveProfile)
                    {
                        EditorGUILayout.HelpBox("There is no active profile assigned in the current MRTK instance. Some properties may not be editable.", MessageType.Error);
                    }
                }
            }
            else
            {
                if (!isProfileInitialized && profile.IsCustomProfile)
                {
                    EditorGUILayout.HelpBox("Some properties may not be editable in this profile. Please refer to the error messages below to resolve editing.", MessageType.Warning);
                }
            }

            using (new EditorGUILayout.HorizontalScope())
            {
                EditorGUILayout.LabelField(new GUIContent(title, description), EditorStyles.boldLabel, GUILayout.ExpandWidth(true));
                RenderDocumentation(selectionObject);
            }

            EditorGUILayout.LabelField(string.Empty, GUI.skin.horizontalSlider);

            return(true);
        }