Пример #1
0
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            if (target != null)
            {
                InspectorUIUtility.RenderHelpURL(target.GetType());
            }

            bool trackedObjectChanged = false;

            EditorGUI.BeginChangeCheck();

            InspectorUIUtility.DrawEnumSerializedProperty(trackedTargetProperty, TrackedTypeLabel, solverHandler.TrackedTargetType);

            if (!SolverHandler.IsValidTrackedObjectType(solverHandler.TrackedTargetType))
            {
                InspectorUIUtility.DrawWarning(" Current Tracked Target Type value of \""
                                               + Enum.GetName(typeof(TrackedObjectType), solverHandler.TrackedTargetType)
                                               + "\" is obsolete. Select MotionController or HandJoint values instead");
            }

            if (trackedTargetProperty.enumValueIndex == (int)TrackedObjectType.HandJoint ||
                trackedTargetProperty.enumValueIndex == (int)TrackedObjectType.MotionController)
            {
                EditorGUILayout.PropertyField(trackedHandnessProperty);
                if (trackedHandnessProperty.enumValueIndex > (int)Handedness.Both)
                {
                    InspectorUIUtility.DrawWarning("Only Handedness values of None, Left, Right, and Both are valid");
                }
            }

            if (trackedTargetProperty.enumValueIndex == (int)TrackedObjectType.HandJoint)
            {
                EditorGUILayout.PropertyField(trackedHandJointProperty);
            }
            else if (trackedTargetProperty.enumValueIndex == (int)TrackedObjectType.CustomOverride)
            {
                EditorGUILayout.PropertyField(transformOverrideProperty);
            }

            EditorGUILayout.PropertyField(additionalOffsetProperty);
            EditorGUILayout.PropertyField(additionalRotationProperty);

            trackedObjectChanged = EditorGUI.EndChangeCheck();

            EditorGUILayout.PropertyField(updateSolversProperty);

            serializedObject.ApplyModifiedProperties();

            if (EditorApplication.isPlaying && trackedObjectChanged)
            {
                solverHandler.RefreshTrackedObject();
            }
        }
        private void RenderDefaultThemeWarning(SerializedProperty profileItem, SerializedProperty themeItem)
        {
            if (themeItem.objectReferenceValue.name == "DefaultTheme")
            {
                using (new EditorGUILayout.HorizontalScope())
                {
                    InspectorUIUtility.DrawWarning("DefaultTheme should not be edited.  ");

                    if (InspectorUIUtility.FlexButton(CreateThemeLabel))
                    {
                        CreateTheme(profileItem, themeItem);
                    }
                }
            }
        }
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            serializedObject.Update();

            // General Properties
            EditorGUILayout.LabelField("General Properties", EditorStyles.boldLabel);
            EditorGUILayout.PropertyField(surfaceNormalOffsetProperty);
            EditorGUILayout.PropertyField(surfaceRayOffsetProperty);

            EditorGUILayout.PropertyField(orientationModeProperty);

            if (surfaceMagnetism.CurrentOrientationMode != SurfaceMagnetism.OrientationMode.None)
            {
                EditorGUILayout.PropertyField(orientationVerticalProperty);
            }

            if (surfaceMagnetism.CurrentOrientationMode == SurfaceMagnetism.OrientationMode.Blended)
            {
                EditorGUILayout.PropertyField(orientationBlendProperty);
            }

            // Raycast properties
            EditorGUILayout.LabelField("Raycast Properties", EditorStyles.boldLabel);
            EditorGUILayout.PropertyField(magneticSurfacesProperty, true);

            // When raycast from the center of the GameObject, Raycast may hit one of the collider on the GameObject (or children)
            // This results in the GameObject "magnetizes" against itself. Warn user if this possibility exists
            var colliders = surfaceMagnetism.GetComponentsInChildren <Collider>();

            foreach (var collider in colliders)
            {
                if (surfaceMagnetism.MagneticSurfaces.Any(s => collider.gameObject.IsInLayerMask(s)))
                {
                    InspectorUIUtility.DrawWarning("This GameObject, or a child of the GameObject, has a collider on a layer listed in the Magnetic Surfaces property. Raycasts calculated for the SurfaceMagnetism component may result in hits against itself causing odd behavior. Consider moving this GameObject and all children to the \"Ignore Raycast\" layer");
                    break;
                }
            }

            EditorGUILayout.PropertyField(closestDistanceProperty);
            EditorGUILayout.PropertyField(maxDistanceProperty);
            EditorGUILayout.PropertyField(currentRaycastDirectionModeProperty);
            EditorGUILayout.PropertyField(raycastModeProperty);

            // Draw properties dependent on type of raycast direction mode selected
            switch (raycastModeProperty.intValue)
            {
            case (int)SceneQueryType.BoxRaycast:
                EditorGUILayout.PropertyField(boxRaysPerEdgeProperty);
                EditorGUILayout.PropertyField(orthographicBoxCastProperty);
                EditorGUILayout.PropertyField(maximumNormalVarianceProperty);
                break;

            case (int)SceneQueryType.SphereCast:
                EditorGUILayout.PropertyField(sphereSizeProperty);
                break;

            case (int)SceneQueryType.SphereOverlap:
                InspectorUIUtility.DrawWarning("SurfaceMagnetism does not support SphereOverlap raycast mode");
                break;
            }

            if (raycastModeProperty.intValue != (int)SceneQueryType.SimpleRaycast &&
                raycastModeProperty.intValue != (int)SceneQueryType.SphereOverlap)
            {
                EditorGUILayout.PropertyField(volumeCastSizeOverrideProperty);
            }

            // Other properties
            EditorGUILayout.LabelField("Other Properties", EditorStyles.boldLabel);
            EditorGUILayout.PropertyField(useLinkedAltScaleOverrideProperty);
            EditorGUILayout.PropertyField(debugEnabledProperty);

            serializedObject.ApplyModifiedProperties();
        }
        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;
        }
        public virtual void RenderCustomInspector()
        {
            serializedObject.Update();

            Rect position;
            bool isPlayMode = EditorApplication.isPlaying || EditorApplication.isPaused;

            #region General Settings
            EditorGUILayout.BeginHorizontal();
            InspectorUIUtility.DrawTitle("General");
            InspectorUIUtility.RenderDocLinkButton(Interactable_URL);
            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginVertical(EditorStyles.helpBox);

            // States
            SerializedProperty states = serializedObject.FindProperty("States");

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

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

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

            //standard Interactable Object UI
            SerializedProperty enabled = serializedObject.FindProperty("Enabled");
            EditorGUILayout.PropertyField(enabled, new GUIContent("Enabled", "Is this Interactable Enabled?"));

            SerializedProperty actionId = serializedObject.FindProperty("InputActionId");

            if (actionOptions == null)
            {
                GUI.enabled = false;
                EditorGUILayout.Popup("Input Actions", 0, new string[] { "Missing Mixed Reality Toolkit" });
                GUI.enabled = true;
            }
            else
            {
                position = EditorGUILayout.GetControlRect();
                DrawDropDownProperty(position, actionId, actionOptions, new GUIContent("Input Actions", "The input action filter"));
            }

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

            SerializedProperty voiceCommands = serializedObject.FindProperty("VoiceCommand");

            // check speech commands profile for a list of commands
            if (speechKeywords == null)
            {
                GUI.enabled = false;
                EditorGUILayout.Popup("Speech Command", 0, new string[] { "Missing Speech Commands" });
                InspectorUIUtility.DrawNotice("Create speech commands in the MRTK/Input/Speech Commands Profile");
                GUI.enabled = true;
            }
            else
            {
                //look for items in the sppech commands list that match the voiceCommands string
                // this string should be empty if we are not listening to speech commands
                // will return zero if empty, to match the inserted off value.
                int currentIndex = SpeechKeywordLookup(voiceCommands.stringValue, speechKeywords);
                GUI.enabled = !isPlayMode;
                position    = EditorGUILayout.GetControlRect();
                GUIContent label = new GUIContent("Speech Command", "Speech Commands to use with Interactable, pulled from MRTK/Input/Speech Commands Profile");
                EditorGUI.BeginProperty(position, label, voiceCommands);
                {
                    currentIndex = EditorGUI.Popup(position, label, currentIndex, speechKeywords);

                    if (currentIndex > 0)
                    {
                        voiceCommands.stringValue = speechKeywords[currentIndex].text;
                    }
                    else
                    {
                        voiceCommands.stringValue = "";
                    }
                }
                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?"));
                }
            }

            SerializedProperty dimensions = serializedObject.FindProperty("Dimensions");
            // 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())
                {
                    SerializedProperty canSelect           = serializedObject.FindProperty("CanSelect");
                    SerializedProperty canDeselect         = serializedObject.FindProperty("CanDeselect");
                    SerializedProperty startDimensionIndex = serializedObject.FindProperty("StartDimensionIndex");

                    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
                            {
                                SerializedProperty dimensionIndex = serializedObject.FindProperty("dimensionIndex");
                                EditorGUI.IntField(position, CurrentDimensionLabel, dimensionIndex.intValue);
                            }
                        }
                        else if (dimensions.intValue == (int)SelectionModes.Toggle + 1)
                        {
                            // toggle
                            if (!isPlayMode)
                            {
                                bool isToggled = EditorGUI.Toggle(position, isToggledLabel, startDimensionIndex.intValue > 0);
                                startDimensionIndex.intValue = isToggled ? 1 : 0;
                            }
                            else
                            {
                                SerializedProperty dimensionIndex = serializedObject.FindProperty("dimensionIndex");
                                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();

            #endregion

            EditorGUILayout.Space();

            if (!ProfilesSetup && !showProfiles)
            {
                InspectorUIUtility.DrawWarning("Profiles (Optional) have not been set up or has errors.");
            }

            #region Profiles

            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)
            {
                for (int i = 0; i < profileList.arraySize; i++)
                {
                    EditorGUILayout.BeginVertical(EditorStyles.helpBox);

                    // get profiles
                    SerializedProperty sItem      = profileList.GetArrayElementAtIndex(i);
                    SerializedProperty gameObject = sItem.FindPropertyRelative("Target");
                    string             targetName = "Profile " + (i + 1);
                    if (gameObject.objectReferenceValue != null)
                    {
                        targetName = gameObject.objectReferenceValue.name;
                        validProfileCnt++;
                    }

                    EditorGUILayout.BeginHorizontal();

                    EditorGUILayout.PropertyField(gameObject, new GUIContent("Target", "Target gameObject for this theme properties to manipulate"));
                    bool triggered = InspectorUIUtility.SmallButton(new GUIContent(InspectorUIUtility.Minus, "Remove Profile"), i, RemoveProfile);

                    if (triggered)
                    {
                        continue;
                    }

                    EditorGUILayout.EndHorizontal();

                    // get themes
                    SerializedProperty themes = sItem.FindPropertyRelative("Themes");

                    // make sure there are enough themes as dimensions
                    if (themes.arraySize > dimensions.intValue)
                    {
                        // make sure there are not more themes than dimensions
                        int cnt = themes.arraySize - 1;
                        for (int j = cnt; j > dimensions.intValue - 1; j--)
                        {
                            themes.DeleteArrayElementAtIndex(j);
                        }
                    }

                    // add themes when increasing dimensions
                    if (themes.arraySize < dimensions.intValue)
                    {
                        int cnt = themes.arraySize;
                        for (int j = cnt; j < dimensions.intValue; j++)
                        {
                            themes.InsertArrayElementAtIndex(themes.arraySize);
                            SerializedProperty theme = themes.GetArrayElementAtIndex(themes.arraySize - 1);

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

                    // Render all themes for current target
                    for (int t = 0; t < themes.arraySize; t++)
                    {
                        SerializedProperty themeItem  = themes.GetArrayElementAtIndex(t);
                        string             themeLabel = BuildThemeTitle(selectionMode, t);

                        EditorGUILayout.PropertyField(themeItem, new GUIContent(themeLabel, "Theme properties for interaction feedback"));

                        if (themeItem.objectReferenceValue != null && gameObject.objectReferenceValue)
                        {
                            if (themeItem.objectReferenceValue.name == "DefaultTheme")
                            {
                                EditorGUILayout.BeginHorizontal();
                                InspectorUIUtility.DrawWarning("DefaultTheme should not be edited.  ");
                                bool newTheme = InspectorUIUtility.FlexButton(new GUIContent("Create Theme", "Create a new theme"), new int[] { i, t, 0 }, CreateTheme);
                                if (newTheme)
                                {
                                    continue;
                                }
                                EditorGUILayout.EndHorizontal();
                            }

                            SerializedProperty hadDefault = sItem.FindPropertyRelative("HadDefaultTheme");
                            hadDefault.boolValue = true;
                            string prefKey      = themeItem.objectReferenceValue.name + "Profiles" + i + "_Theme" + t + "_Edit";
                            bool   hasPref      = EditorPrefs.HasKey(prefKey);
                            bool   showSettings = EditorPrefs.GetBool(prefKey);
                            if (!hasPref)
                            {
                                showSettings = true;
                            }

                            InspectorUIUtility.ListSettings settings = listSettings[i];
                            bool show = InspectorUIUtility.DrawSectionFoldout(themeItem.objectReferenceValue.name + " (Click to edit)", showSettings, FontStyle.Normal);

                            if (show != showSettings)
                            {
                                EditorPrefs.SetBool(prefKey, show);
                                settings.Show = show;
                            }

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

                                GUILayout.Space(5);

                                if (themeObjSettings.arraySize < 1)
                                {
                                    AddThemeProperty(new int[] { i, t, 0 });
                                }

                                int[]   location = new int[] { i, t, 0 };
                                State[] iStates  = GetStates();

                                ThemeInspector.RenderThemeSettings(themeObjSettings, themeObj, themeOptions, gameObject, location, iStates, ThemePropertiesBoxMargin);
                                InspectorUIUtility.FlexButton(new GUIContent("+", "Add Theme Property"), location, AddThemeProperty);
                                ThemeInspector.RenderThemeStates(themeObjSettings, iStates, ThemePropertiesBoxMargin);

                                themeObj.ApplyModifiedProperties();
                            }
                            listSettings[i] = settings;

                            validProfileCnt++;
                        }
                        else
                        {
                            // show message about profile setup
                            string themeMsg = "Assign a ";
                            if (gameObject.objectReferenceValue == null)
                            {
                                themeMsg += "Target ";
                            }

                            if (themeItem.objectReferenceValue == null)
                            {
                                if (gameObject.objectReferenceValue == null)
                                {
                                    themeMsg += "and ";
                                }
                                themeMsg += "Theme ";
                            }

                            themeMsg += "above to add visual effects";
                            SerializedProperty hadDefault = sItem.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;
                    }

                    EditorGUILayout.EndVertical();
                }// 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;

            #endregion

            EditorGUILayout.Space();

            #region Events settings

            bool isEventsOpen = InspectorUIUtility.DrawSectionFoldout("Events", showEvents, FontStyle.Bold, InspectorUIUtility.TitleFontSize);
            if (showEvents != isEventsOpen)
            {
                showEvents = isEventsOpen;
                EditorPrefs.SetBool(ShowEventsPrefKey, showEvents);
            }
            EditorGUILayout.Space();

            if (showEvents)
            {
                SerializedProperty onClick = serializedObject.FindProperty("OnClick");
                EditorGUILayout.PropertyField(onClick, new GUIContent("OnClick"));

                SerializedProperty events = serializedObject.FindProperty("Events");
                GUI.enabled = !isPlayMode;
                for (int i = 0; i < events.arraySize; i++)
                {
                    SerializedProperty eventItem = events.GetArrayElementAtIndex(i);
                    InteractableReceiverListInspector.RenderEventSettings(eventItem, i, eventOptions, ChangeEvent, RemoveEvent);
                }
                GUI.enabled = true;

                if (eventOptions.ClassNames.Length > 1)
                {
                    if (GUILayout.Button(new GUIContent("Add Event")))
                    {
                        AddEvent(events.arraySize);
                    }
                }
            }

            #endregion

            serializedObject.ApplyModifiedProperties();
        }
        private void RenderProfileSettings()
        {
            if (profileList.arraySize < 1)
            {
                AddProfile(0);
            }

            if (InspectorUIUtility.DrawSectionFoldoutWithKey("Profiles", ShowProfilesPrefKey, MixedRealityStylesUtility.BoldTitleFoldoutStyle))
            {
                // Render all profile items. Profiles are per GameObject/ThemeContainer
                for (int i = 0; i < profileList.arraySize; i++)
                {
                    using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
                    {
                        SerializedProperty profileItem    = profileList.GetArrayElementAtIndex(i);
                        SerializedProperty hostGameObject = profileItem.FindPropertyRelative("Target");

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

                        if (hostGameObject.objectReferenceValue == null)
                        {
                            InspectorUIUtility.DrawError("Assign a GameObject to apply visual effects");
                            if (GUILayout.Button("Assign Self"))
                            {
                                hostGameObject.objectReferenceValue = instance.gameObject;
                            }
                        }

                        SerializedProperty themes = profileItem.FindPropertyRelative("Themes");
                        ValidateThemesForDimensions(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);

                            if (themeItem.objectReferenceValue != null)
                            {
                                bool showThemeSettings = false;
                                using (new EditorGUILayout.HorizontalScope())
                                {
                                    string prefKey = themeItem.objectReferenceValue.name + "Profiles" + i + "_Theme" + t + "_Edit";
                                    showThemeSettings = InspectorUIUtility.DrawSectionFoldoutWithKey(themeLabel, prefKey, null, false);
                                    EditorGUILayout.PropertyField(themeItem, new GUIContent(string.Empty, "Theme properties for interaction feedback"));
                                }

                                if (themeItem.objectReferenceValue != null)
                                {
                                    // TODO: Odd bug where themeStates below is null when it shouldn't be. Use instance object as workaround atm
                                    //SerializedProperty themeStates = themeItem.FindPropertyRelative("States");
                                    var themeInstance = themeItem.objectReferenceValue as Theme;
                                    if (statesProperty.objectReferenceValue != themeInstance.States)
                                    {
                                        InspectorUIUtility.DrawWarning($"{themeInstance.name}'s States property does not match Interactable's States property");
                                    }

                                    if (showThemeSettings)
                                    {
                                        using (new EditorGUI.IndentLevelScope())
                                        {
                                            UnityEditor.Editor themeEditor = UnityEditor.Editor.CreateEditor(themeItem.objectReferenceValue);
                                            themeEditor.OnInspectorGUI();
                                        }
                                    }
                                }
                            }
                            else
                            {
                                EditorGUILayout.PropertyField(themeItem, new GUIContent(themeLabel, "Theme properties for interaction feedback"));

                                InspectorUIUtility.DrawError("Assign a Theme to add visual effects");
                                if (GUILayout.Button(CreateThemeLabel))
                                {
                                    themeItem.objectReferenceValue = CreateThemeAsset(hostGameObject.objectReferenceValue.name);
                                    return;
                                }
                            }

                            EditorGUILayout.Space();
                        }
                    }
                }

                if (GUILayout.Button(new GUIContent("Add Profile")))
                {
                    AddProfile(profileList.arraySize);
                }
            }
        }
        public virtual void RenderCustomInspector()
        {
            // TODO: extend the preference array to handle multiple themes open and scroll values!!!
            // TODO: add  messaging!!!
            // TODO: handle dimensions
            // TODO: add profiles
            // TODO: add themes
            // TODO: handle/display properties from themes

            // TODO: !!!!! need to make sure we refresh the shader list when the target changes

            // TODO: !!!!! finish incorporating States
            // TODO: add the default states by default
            // TODO: let flow into rest of themes and events.
            // TODO: events should target the state logic they support.

            // FIX: when deleting a theme property, the value resets or the item that's deleted is wrong

            //base.DrawDefaultInspector();

            serializedObject.Update();

            EditorGUILayout.Space();
            InspectorUIUtility.DrawTitle("Interactable");
            //EditorGUILayout.LabelField(new GUIContent("Interactable Settings"));

            EditorGUILayout.BeginVertical("Box");

            // States
            bool showStates           = false;
            SerializedProperty states = serializedObject.FindProperty("States");
            bool drawerStarted        = false;

            if (states.objectReferenceValue != null)
            {
                string statesPrefKey   = "Settings_States";
                bool   prefsShowStates = EditorPrefs.GetBool(statesPrefKey);
                EditorGUI.indentLevel = indentOnSectionStart + 1;
                showStates            = InspectorUIUtility.DrawSectionStart(states.objectReferenceValue.name + " (Click to edit)", indentOnSectionStart + 2, prefsShowStates, FontStyle.Normal, false);
                drawerStarted         = true;
                if (showStates != prefsShowStates)
                {
                    EditorPrefs.SetBool(statesPrefKey, showStates);
                }
            }
            else
            {
                AssetDatabase.Refresh();
                string[] stateLocations = AssetDatabase.FindAssets("DefaultInteractableStates");
                if (stateLocations.Length > 0)
                {
                    for (int i = 0; i < stateLocations.Length; i++)
                    {
                        string path          = AssetDatabase.GUIDToAssetPath(stateLocations[i]);
                        States defaultStates = (States)AssetDatabase.LoadAssetAtPath(path, typeof(States));
                        if (defaultStates != null)
                        {
                            states.objectReferenceValue = defaultStates;
                            break;
                        }
                    }
                }
                else
                {
                    showStates = true;
                }
            }

            if (showStates)
            {
                EditorGUILayout.PropertyField(states, new GUIContent("States", "The States this Interactable is based on"));
            }

            if (drawerStarted)
            {
                InspectorUIUtility.DrawSectionEnd(indentOnSectionStart);
            }

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

            //standard Interactable Object UI
            SerializedProperty enabled = serializedObject.FindProperty("Enabled");

            enabled.boolValue = EditorGUILayout.Toggle(new GUIContent("Enabled", "Is this Interactable Enabled?"), enabled.boolValue);

            SerializedProperty actionId = serializedObject.FindProperty("InputActionId");

            if (actionOptions == null)
            {
                GUI.enabled = false;
                EditorGUILayout.Popup("Input Actions", 0, new string[] { "Missing Mixed Reality Toolkit" });
                GUI.enabled = true;
            }
            else
            {
                int newActionId = EditorGUILayout.Popup("Input Actions", actionId.intValue, actionOptions);
                if (newActionId != actionId.intValue)
                {
                    actionId.intValue = newActionId;
                }
            }

            //selected.enumValueIndex = (int)(MixedRealityInputAction)EditorGUILayout.EnumPopup(new GUIContent("Input Action", "Input source for this Interactable, Default: Select"), (MixedRealityInputAction)selected.enumValueIndex);

            // TODO: should IsGlobal only show up on specific press types and indent?
            // TODO: should we show handedness on certain press types?
            SerializedProperty isGlobal = serializedObject.FindProperty("IsGlobal");

            isGlobal.boolValue = EditorGUILayout.Toggle(new GUIContent("Is Global", "Like a modal, does not require focus"), isGlobal.boolValue);

            SerializedProperty voiceCommands = serializedObject.FindProperty("VoiceCommand");

            voiceCommands.stringValue = EditorGUILayout.TextField(new GUIContent("Voice Command", "A voice command to trigger the click event"), voiceCommands.stringValue);

            // show requires gaze because voice command has a value
            if (!string.IsNullOrEmpty(voiceCommands.stringValue))
            {
                EditorGUI.indentLevel = indentOnSectionStart + 1;

                SerializedProperty requireGaze = serializedObject.FindProperty("RequiresFocus");
                requireGaze.boolValue = EditorGUILayout.Toggle(new GUIContent("Requires Focus", "Does the voice command require gazing at this interactable?"), requireGaze.boolValue);

                EditorGUI.indentLevel = indentOnSectionStart;
            }

            SerializedProperty dimensions = serializedObject.FindProperty("Dimensions");

            dimensions.intValue = EditorGUILayout.IntField(new GUIContent("Dimensions", "Toggle or sequence button levels"), dimensions.intValue);

            if (dimensions.intValue > 1)
            {
                EditorGUI.indentLevel = indentOnSectionStart + 1;

                SerializedProperty canSelect   = serializedObject.FindProperty("CanSelect");
                SerializedProperty canDeselect = serializedObject.FindProperty("CanDeselect");

                canSelect.boolValue   = EditorGUILayout.Toggle(new GUIContent("Can Select", "The user can toggle this button"), canSelect.boolValue);
                canDeselect.boolValue = EditorGUILayout.Toggle(new GUIContent("Can Deselect", "The user can untoggle this button, set false for a radial interaction."), canDeselect.boolValue);

                EditorGUI.indentLevel = indentOnSectionStart;
            }

            EditorGUILayout.EndVertical();
            EditorGUILayout.Space();
            InspectorUIUtility.DrawDivider();

            if (!ProfilesSetup && !showProfiles)
            {
                InspectorUIUtility.DrawWarning("Profiles (Optional) have not been set up or has errors.");
            }

            // profiles section
            string profileTitle = "Profiles";
            bool   isOPen       = InspectorUIUtility.DrawSectionStart(profileTitle, indentOnSectionStart + 1, showProfiles, InspectorUIUtility.LableStyle(InspectorUIUtility.TitleFontSize, InspectorUIUtility.ColorTint50).fontStyle, false, InspectorUIUtility.TitleFontSize);

            if (showProfiles != isOPen)
            {
                showProfiles = isOPen;
                EditorPrefs.SetBool(prefKey, showProfiles);
            }

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

            int validProfileCnt = 0;
            int themeCnt        = 0;

            if (showProfiles)
            {
                for (int i = 0; i < profileList.arraySize; i++)
                {
                    EditorGUILayout.BeginVertical("Box");
                    // get profiles
                    SerializedProperty sItem = profileList.GetArrayElementAtIndex(i);
                    EditorGUI.indentLevel = indentOnSectionStart;

                    SerializedProperty gameObject = sItem.FindPropertyRelative("Target");
                    string             targetName = "Profile " + (i + 1);
                    if (gameObject.objectReferenceValue != null)
                    {
                        targetName = gameObject.objectReferenceValue.name;
                        validProfileCnt++;
                    }

                    EditorGUILayout.BeginHorizontal();
                    InspectorUIUtility.DrawLabel(targetName, 12, InspectorUIUtility.ColorTint100);

                    bool triggered = InspectorUIUtility.SmallButton(new GUIContent(InspectorUIUtility.Minus, "Remove Profile"), i, RemoveProfile);

                    if (triggered)
                    {
                        continue;
                    }

                    EditorGUILayout.EndHorizontal();

                    EditorGUI.indentLevel = indentOnSectionStart + 1;
                    EditorGUILayout.PropertyField(gameObject, new GUIContent("Target", "Target gameObject for this theme properties to manipulate"));

                    // get themes
                    SerializedProperty themes = sItem.FindPropertyRelative("Themes");

                    // make sure there are enough themes as dimensions
                    if (themes.arraySize > dimensions.intValue)
                    {
                        // make sure there are not more themes than dimensions
                        int cnt = themes.arraySize - 1;
                        for (int j = cnt; j > dimensions.intValue - 1; j--)
                        {
                            themes.DeleteArrayElementAtIndex(j);
                        }
                    }

                    // add themes when increasing dimensions
                    if (themes.arraySize < dimensions.intValue)
                    {
                        int cnt = themes.arraySize;
                        for (int j = cnt; j < dimensions.intValue; j++)
                        {
                            themes.InsertArrayElementAtIndex(themes.arraySize);
                            SerializedProperty theme = themes.GetArrayElementAtIndex(themes.arraySize - 1);

                            // TODO: make sure there is only one or make unique
                            string[] themeLocations = AssetDatabase.FindAssets("DefaultTheme");
                            if (themeLocations.Length > 0)
                            {
                                for (int k = 0; k < themeLocations.Length; k++)
                                {
                                    string path         = AssetDatabase.GUIDToAssetPath(themeLocations[k]);
                                    Theme  defaultTheme = (Theme)AssetDatabase.LoadAssetAtPath(path, typeof(Theme));
                                    if (defaultTheme != null)
                                    {
                                        theme.objectReferenceValue = defaultTheme;
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    for (int t = 0; t < themes.arraySize; t++)
                    {
                        SerializedProperty themeItem = themes.GetArrayElementAtIndex(t);
                        EditorGUILayout.PropertyField(themeItem, new GUIContent("Theme", "Theme properties for interaction feedback"));

                        // TODO: we need the theme and target in order to figure out what properties to expose in the list
                        // TODO: or do we show them all and show alerts when a theme property is not compatible
                        if (themeItem.objectReferenceValue != null && gameObject.objectReferenceValue)
                        {
                            if (themeItem.objectReferenceValue.name == "DefaultTheme")
                            {
                                EditorGUILayout.BeginHorizontal();
                                InspectorUIUtility.DrawWarning("DefaultTheme should not be edited.  ");
                                bool newTheme = InspectorUIUtility.FlexButton(new GUIContent("Create Theme", "Create a new theme"), new int[] { i, t, 0 }, CreateTheme);
                                if (newTheme)
                                {
                                    continue;
                                }
                                EditorGUILayout.EndHorizontal();
                            }

                            SerializedProperty hadDefault = sItem.FindPropertyRelative("HadDefaultTheme");
                            hadDefault.boolValue  = true;
                            EditorGUI.indentLevel = indentOnSectionStart + 2;

                            string prefKey      = themeItem.objectReferenceValue.name + "Profiles" + i + "_Theme" + t + "_Edit";
                            bool   showSettings = EditorPrefs.GetBool(prefKey);

                            InspectorUIUtility.ListSettings settings = listSettings[i];
                            bool show = InspectorUIUtility.DrawSectionStart(themeItem.objectReferenceValue.name + " (Click to edit)", indentOnSectionStart + 3, showSettings, FontStyle.Normal, false);

                            if (show != showSettings)
                            {
                                EditorPrefs.SetBool(prefKey, show);
                                settings.Show = show;
                            }

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

                                GUILayout.Space(5);

                                if (themeObjSettings.arraySize < 1)
                                {
                                    AddThemeProperty(new int[] { i, t, 0 });
                                }

                                int[] location = new int[] { i, t, 0 };

                                State[] iStates = GetStates();

                                ThemeInspector.RenderThemeSettings(themeObjSettings, themeObj, themeOptions, gameObject, location, iStates);

                                InspectorUIUtility.FlexButton(new GUIContent("+", "Add Theme Property"), location, AddThemeProperty);

                                ThemeInspector.RenderThemeStates(themeObjSettings, iStates, 30);

                                themeObj.ApplyModifiedProperties();
                            }

                            InspectorUIUtility.DrawSectionEnd(indentOnSectionStart + 2);
                            listSettings[i] = settings;

                            validProfileCnt++;
                        }
                        else
                        {
                            // show message about profile setup
                            string themeMsg = "Assign a ";
                            if (gameObject.objectReferenceValue == null)
                            {
                                themeMsg += "Target ";
                            }

                            if (themeItem.objectReferenceValue == null)
                            {
                                if (gameObject.objectReferenceValue == null)
                                {
                                    themeMsg += "and ";
                                }
                                themeMsg += "Theme ";
                            }

                            themeMsg += "above to add visual effects";

                            SerializedProperty hadDefault = sItem.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);
                        }
                    }

                    EditorGUI.indentLevel = indentOnSectionStart;

                    EditorGUILayout.EndVertical();

                    themeCnt += themes.arraySize;
                }

                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;

            InspectorUIUtility.DrawSectionEnd(indentOnSectionStart);
            EditorGUILayout.Space();
            InspectorUIUtility.DrawDivider();

            // Events section
            InspectorUIUtility.DrawTitle("Events");
            //EditorGUILayout.LabelField(new GUIContent("Events"));

            SerializedProperty onClick = serializedObject.FindProperty("OnClick");

            EditorGUILayout.PropertyField(onClick, new GUIContent("OnClick"));

            SerializedProperty events = serializedObject.FindProperty("Events");

            for (int i = 0; i < events.arraySize; i++)
            {
                SerializedProperty eventItem = events.GetArrayElementAtIndex(i);
                InteractableReceiverListInspector.RenderEventSettings(eventItem, i, eventOptions, ChangeEvent, RemoveEvent);
            }

            if (eventOptions.ClassNames.Length > 1)
            {
                if (GUILayout.Button(new GUIContent("Add Event")))
                {
                    AddEvent(events.arraySize);
                }
            }

            serializedObject.ApplyModifiedProperties();
        }
Пример #8
0
        public virtual void RenderCustomInspector()
        {
            serializedObject.Update();

            Rect position;

            EditorGUILayout.Space();
            InspectorUIUtility.DrawTitle("Interactable");

            EditorGUILayout.BeginVertical("Box");
            bool isPlayMode = EditorApplication.isPlaying || EditorApplication.isPaused;

            // States
            bool showStates           = false;
            SerializedProperty states = serializedObject.FindProperty("States");
            bool   drawerStarted      = false;
            string statesPrefKey      = "Settings_States";
            bool   prefsShowStates    = EditorPrefs.GetBool(statesPrefKey);

            if (states.objectReferenceValue != null)
            {
                EditorGUI.indentLevel = indentOnSectionStart + 1;
                showStates            = InspectorUIUtility.DrawSectionStart(states.objectReferenceValue.name + " (Click to edit)", indentOnSectionStart + 2, prefsShowStates, FontStyle.Normal, false);
                drawerStarted         = true;
                if (showStates != prefsShowStates)
                {
                    EditorPrefs.SetBool(statesPrefKey, showStates);
                }
            }
            else
            {
                AssetDatabase.Refresh();
                string[] stateLocations = AssetDatabase.FindAssets("DefaultInteractableStates");
                if (stateLocations.Length > 0)
                {
                    for (int i = 0; i < stateLocations.Length; i++)
                    {
                        string path          = AssetDatabase.GUIDToAssetPath(stateLocations[i]);
                        States defaultStates = (States)AssetDatabase.LoadAssetAtPath(path, typeof(States));
                        if (defaultStates != null)
                        {
                            states.objectReferenceValue = defaultStates;
                            break;
                        }
                    }

                    EditorGUI.indentLevel = indentOnSectionStart + 1;
                    showStates            = InspectorUIUtility.DrawSectionStart(states.objectReferenceValue.name + " (Click to edit)", indentOnSectionStart + 2, prefsShowStates, FontStyle.Normal, false);
                    drawerStarted         = true;
                }
                else
                {
                    showStates = true;
                }
            }

            if (showStates)
            {
                GUI.enabled = !isPlayMode;
                EditorGUILayout.PropertyField(states, new GUIContent("States", "The States this Interactable is based on"));
                GUI.enabled = true;
            }

            if (drawerStarted)
            {
                InspectorUIUtility.DrawSectionEnd(indentOnSectionStart);
            }
            if (states.objectReferenceValue == null)
            {
                InspectorUIUtility.DrawError("Please assign a States object!");
                EditorGUILayout.EndVertical();
                serializedObject.ApplyModifiedProperties();
                return;
            }

            //standard Interactable Object UI
            SerializedProperty enabled = serializedObject.FindProperty("Enabled");

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

            SerializedProperty actionId = serializedObject.FindProperty("InputActionId");

            if (actionOptions == null)
            {
                GUI.enabled = false;
                EditorGUILayout.Popup("Input Actions", 0, new string[] { "Missing Mixed Reality Toolkit" });
                GUI.enabled = true;
            }
            else
            {
                position = EditorGUILayout.GetControlRect();
                DrawDropDownProperty(position, actionId, actionOptions, new GUIContent("Input Actions", "The input action filter"));
            }

            EditorGUI.indentLevel = indentOnSectionStart + 1;
            SerializedProperty isGlobal = serializedObject.FindProperty("IsGlobal");

            EditorGUILayout.PropertyField(isGlobal, new GUIContent("Is Global", "Like a modal, does not require focus"));
            EditorGUI.indentLevel = indentOnSectionStart;
            SerializedProperty voiceCommands = serializedObject.FindProperty("VoiceCommand");

            // check speech commands profile for a list of commands
            if (speechKeywords == null)
            {
                GUI.enabled = false;
                EditorGUILayout.Popup("Speech Command", 0, new string[] { "Missing Speech Commands" });
                InspectorUIUtility.DrawNotice("Create speech commands in the MRTK/Input/Speech Commands Profile");
                GUI.enabled = true;
            }
            else
            {
                //look for items in the sppech commands list that match the voiceCommands string
                // this string should be empty if we are not listening to speech commands
                // will return zero if empty, to match the inserted off value.
                int currentIndex = SpeechKeywordLookup(voiceCommands.stringValue, speechKeywords);
                GUI.enabled = !isPlayMode;
                position    = EditorGUILayout.GetControlRect();
                GUIContent label = new GUIContent("Speech Command", "Speech Commands to use with Interactable, pulled from MRTK/Input/Speech Commands Profile");
                EditorGUI.BeginProperty(position, label, voiceCommands);
                {
                    currentIndex = EditorGUI.Popup(position, label, currentIndex, speechKeywords);

                    if (currentIndex > 0)
                    {
                        voiceCommands.stringValue = speechKeywords[currentIndex].text;
                    }
                    else
                    {
                        voiceCommands.stringValue = "";
                    }
                }
                EditorGUI.EndProperty();
                GUI.enabled = true;
            }

            // show requires gaze because voice command has a value
            if (!string.IsNullOrEmpty(voiceCommands.stringValue))
            {
                EditorGUI.indentLevel = indentOnSectionStart + 1;

                SerializedProperty requireGaze = serializedObject.FindProperty("RequiresFocus");
                EditorGUILayout.PropertyField(requireGaze, new GUIContent("Requires Focus", "Does the voice command require gazing at this interactable?"));

                EditorGUI.indentLevel = indentOnSectionStart;
            }

            SerializedProperty dimensions = serializedObject.FindProperty("Dimensions");

            GUI.enabled = !isPlayMode;
            EditorGUILayout.PropertyField(dimensions, new GUIContent("Dimensions", "Toggle or sequence button levels"));
            GUI.enabled = true;

            if (dimensions.intValue > 1)
            {
                EditorGUI.indentLevel = indentOnSectionStart + 1;

                SerializedProperty canSelect   = serializedObject.FindProperty("CanSelect");
                SerializedProperty canDeselect = serializedObject.FindProperty("CanDeselect");

                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."));

                EditorGUI.indentLevel = indentOnSectionStart;
            }

            EditorGUILayout.EndVertical();
            EditorGUILayout.Space();
            InspectorUIUtility.DrawDivider();

            if (!ProfilesSetup && !showProfiles)
            {
                InspectorUIUtility.DrawWarning("Profiles (Optional) have not been set up or has errors.");
            }

            // profiles section
            string profileTitle = "Profiles";
            bool   isOPen       = InspectorUIUtility.DrawSectionStart(profileTitle, indentOnSectionStart + 1, showProfiles, InspectorUIUtility.LableStyle(InspectorUIUtility.TitleFontSize, InspectorUIUtility.ColorTint50).fontStyle, false, InspectorUIUtility.TitleFontSize);

            if (showProfiles != isOPen)
            {
                showProfiles = isOPen;
                EditorPrefs.SetBool(prefKey, showProfiles);
            }

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

            int validProfileCnt = 0;
            int themeCnt        = 0;

            if (showProfiles)
            {
                for (int i = 0; i < profileList.arraySize; i++)
                {
                    EditorGUILayout.BeginVertical("Box");
                    // get profiles
                    SerializedProperty sItem = profileList.GetArrayElementAtIndex(i);
                    EditorGUI.indentLevel = indentOnSectionStart;

                    SerializedProperty gameObject = sItem.FindPropertyRelative("Target");
                    string             targetName = "Profile " + (i + 1);
                    if (gameObject.objectReferenceValue != null)
                    {
                        targetName = gameObject.objectReferenceValue.name;
                        validProfileCnt++;
                    }

                    EditorGUILayout.BeginHorizontal();
                    InspectorUIUtility.DrawLabel(targetName, 12, InspectorUIUtility.ColorTint100);

                    bool triggered = InspectorUIUtility.SmallButton(new GUIContent(InspectorUIUtility.Minus, "Remove Profile"), i, RemoveProfile);

                    if (triggered)
                    {
                        continue;
                    }

                    EditorGUILayout.EndHorizontal();

                    EditorGUI.indentLevel = indentOnSectionStart + 1;
                    EditorGUILayout.PropertyField(gameObject, new GUIContent("Target", "Target gameObject for this theme properties to manipulate"));

                    // get themes
                    SerializedProperty themes = sItem.FindPropertyRelative("Themes");

                    // make sure there are enough themes as dimensions
                    if (themes.arraySize > dimensions.intValue)
                    {
                        // make sure there are not more themes than dimensions
                        int cnt = themes.arraySize - 1;
                        for (int j = cnt; j > dimensions.intValue - 1; j--)
                        {
                            themes.DeleteArrayElementAtIndex(j);
                        }
                    }

                    // add themes when increasing dimensions
                    if (themes.arraySize < dimensions.intValue)
                    {
                        int cnt = themes.arraySize;
                        for (int j = cnt; j < dimensions.intValue; j++)
                        {
                            themes.InsertArrayElementAtIndex(themes.arraySize);
                            SerializedProperty theme = themes.GetArrayElementAtIndex(themes.arraySize - 1);

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

                    for (int t = 0; t < themes.arraySize; t++)
                    {
                        SerializedProperty themeItem = themes.GetArrayElementAtIndex(t);
                        EditorGUI.indentLevel = indentOnSectionStart + 2;
                        EditorGUILayout.PropertyField(themeItem, new GUIContent("Theme", "Theme properties for interaction feedback"));

                        if (themeItem.objectReferenceValue != null && gameObject.objectReferenceValue)
                        {
                            if (themeItem.objectReferenceValue.name == "DefaultTheme")
                            {
                                EditorGUILayout.BeginHorizontal();
                                InspectorUIUtility.DrawWarning("DefaultTheme should not be edited.  ");
                                bool newTheme = InspectorUIUtility.FlexButton(new GUIContent("Create Theme", "Create a new theme"), new int[] { i, t, 0 }, CreateTheme);
                                if (newTheme)
                                {
                                    continue;
                                }
                                EditorGUILayout.EndHorizontal();
                            }

                            SerializedProperty hadDefault = sItem.FindPropertyRelative("HadDefaultTheme");
                            hadDefault.boolValue  = true;
                            EditorGUI.indentLevel = indentOnSectionStart + 3;

                            string prefKey      = themeItem.objectReferenceValue.name + "Profiles" + i + "_Theme" + t + "_Edit";
                            bool   hasPref      = EditorPrefs.HasKey(prefKey);
                            bool   showSettings = EditorPrefs.GetBool(prefKey);
                            if (!hasPref)
                            {
                                showSettings = true;
                            }

                            InspectorUIUtility.ListSettings settings = listSettings[i];
                            bool show = InspectorUIUtility.DrawSectionStart(themeItem.objectReferenceValue.name + " (Click to edit)", indentOnSectionStart + 3, showSettings, FontStyle.Normal, false);

                            if (show != showSettings)
                            {
                                EditorPrefs.SetBool(prefKey, show);
                                settings.Show = show;
                            }

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

                                GUILayout.Space(5);

                                if (themeObjSettings.arraySize < 1)
                                {
                                    AddThemeProperty(new int[] { i, t, 0 });
                                }

                                int[]   location = new int[] { i, t, 0 };
                                State[] iStates  = GetStates();

                                ThemeInspector.RenderThemeSettings(themeObjSettings, themeObj, themeOptions, gameObject, location, iStates);
                                InspectorUIUtility.FlexButton(new GUIContent("+", "Add Theme Property"), location, AddThemeProperty);
                                ThemeInspector.RenderThemeStates(themeObjSettings, iStates, 30);

                                themeObj.ApplyModifiedProperties();
                            }

                            InspectorUIUtility.DrawSectionEnd(indentOnSectionStart + 2);
                            listSettings[i] = settings;

                            validProfileCnt++;
                        }
                        else
                        {
                            // show message about profile setup
                            string themeMsg = "Assign a ";
                            if (gameObject.objectReferenceValue == null)
                            {
                                themeMsg += "Target ";
                            }

                            if (themeItem.objectReferenceValue == null)
                            {
                                if (gameObject.objectReferenceValue == null)
                                {
                                    themeMsg += "and ";
                                }
                                themeMsg += "Theme ";
                            }

                            themeMsg += "above to add visual effects";
                            SerializedProperty hadDefault = sItem.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);
                        }
                    }

                    EditorGUI.indentLevel = indentOnSectionStart;
                    EditorGUILayout.EndVertical();
                    themeCnt += themes.arraySize;
                }

                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;

            InspectorUIUtility.DrawSectionEnd(indentOnSectionStart);
            EditorGUILayout.Space();
            InspectorUIUtility.DrawDivider();

            // Events section
            InspectorUIUtility.DrawTitle("Events");

            SerializedProperty onClick = serializedObject.FindProperty("OnClick");

            EditorGUILayout.PropertyField(onClick, new GUIContent("OnClick"));

            SerializedProperty events = serializedObject.FindProperty("Events");

            GUI.enabled = !isPlayMode;
            for (int i = 0; i < events.arraySize; i++)
            {
                SerializedProperty eventItem = events.GetArrayElementAtIndex(i);
                InteractableReceiverListInspector.RenderEventSettings(eventItem, i, eventOptions, ChangeEvent, RemoveEvent);
            }
            GUI.enabled = true;

            if (eventOptions.ClassNames.Length > 1)
            {
                if (GUILayout.Button(new GUIContent("Add Event")))
                {
                    AddEvent(events.arraySize);
                }
            }

            serializedObject.ApplyModifiedProperties();
        }