protected virtual SerializedObject ChangeThemeProperty(int index, SerializedObject themeObj, SerializedProperty target, bool isNew = false)
        {
            SerializedProperty themeObjSettings = themeObj.FindProperty("Settings");

            themeObjSettings = ThemeInspector.ChangeThemeProperty(index, themeObjSettings, target, GetStates(), isNew);
            return(themeObj);
        }
        protected virtual void AddThemeProperty(SerializedProperty profileItem, SerializedProperty themeItem)
        {
            SerializedProperty serializedTarget = profileItem.FindPropertyRelative("Target");

            SerializedObject themeObj = new SerializedObject(themeItem.objectReferenceValue);

            themeObj.Update();

            SerializedProperty themeObjSettings = themeObj.FindProperty("Settings");

            themeObjSettings.InsertArrayElementAtIndex(themeObjSettings.arraySize);

            SerializedProperty settingsItem          = themeObjSettings.GetArrayElementAtIndex(themeObjSettings.arraySize - 1);
            SerializedProperty className             = settingsItem.FindPropertyRelative("Name");
            SerializedProperty assemblyQualifiedName = settingsItem.FindPropertyRelative("AssemblyQualifiedName");

            if (themeObjSettings.arraySize == 1)
            {
                className.stringValue             = "ScaleOffsetColorTheme";
                assemblyQualifiedName.stringValue = typeof(ScaleOffsetColorTheme).AssemblyQualifiedName;
            }
            else
            {
                className.stringValue             = themeOptions.ClassNames[0];
                assemblyQualifiedName.stringValue = themeOptions.AssemblyQualifiedNames[0];
            }

            SerializedProperty easing = settingsItem.FindPropertyRelative("Easing");

            SerializedProperty time  = easing.FindPropertyRelative("LerpTime");
            SerializedProperty curve = easing.FindPropertyRelative("Curve");

            time.floatValue           = 0.5f;
            curve.animationCurveValue = AnimationCurve.Linear(0, 1, 1, 1);

            themeObjSettings = ThemeInspector.ChangeThemeProperty(themeObjSettings.arraySize - 1, themeObjSettings, serializedTarget, GetStates(), true);

            themeObj.ApplyModifiedProperties();
        }
        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();
        }
        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;
        }