private void RenderAnimatablePropertyList(SerializedProperty animatablePropertyList, SerializedProperty stateContainerName, int animationTargetIndex)
        {
            using (new EditorGUI.IndentLevelScope())
            {
                for (int k = 0; k < animatablePropertyList.arraySize; k++)
                {
                    SerializedProperty animatableProperty     = animatablePropertyList.GetArrayElementAtIndex(k);
                    SerializedProperty animatablePropertyName = animatableProperty.FindPropertyRelative("animatablePropertyName");

                    using (new EditorGUILayout.VerticalScope())
                    {
                        using (new EditorGUILayout.HorizontalScope())
                        {
                            RenderAnimatableProperty(animatableProperty, animationTargetIndex);
                            serializedObject.ApplyModifiedProperties();

                            if (InspectorUIUtility.SmallButton(RemoveButtonLabel))
                            {
                                RemoveKeyFrames(stateContainerName.stringValue, animatablePropertyName.stringValue, animationTargetIndex);
                                animatablePropertyList.DeleteArrayElementAtIndex(k);
                                break;
                            }
                        }
                    }
                }

                InspectorUIUtility.DrawDivider();
            }

            RenderAddAnimatablePropertyMenuButton(stateContainerName.stringValue, animationTargetIndex);
        }
        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();

            InspectorUIUtility.DrawTitle("Profiles");

            if (profilesProperty.arraySize == 0)
            {
                AddProfile();
            }

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

                    using (new EditorGUILayout.HorizontalScope())
                    {
                        SerializedProperty targetGameObject = profile.FindPropertyRelative("Target");
                        EditorGUILayout.PropertyField(targetGameObject, new GUIContent("Target", "Target gameObject for this theme properties to manipulate"));

                        if (InspectorUIUtility.SmallButton(RemoveProfileContent))
                        {
                            profilesProperty.DeleteArrayElementAtIndex(i);
                            serializedObject.ApplyModifiedProperties();
                            continue;
                        }
                    }

                    SerializedProperty theme = profile.FindPropertyRelative("Theme");
                    EditorGUILayout.PropertyField(theme, new GUIContent("Theme", "Theme properties for interaction feedback"));

                    // Render Theme Settings
                    if (theme.objectReferenceValue != null)
                    {
                        InspectorUIUtility.ListSettings settings = listSettings[i];
                        settings.Show = InspectorUIUtility.DrawSectionFoldout("Theme Settings (Click to edit)", listSettings[i].Show);
                        if (settings.Show)
                        {
                            UnityEditor.Editor themeEditor = UnityEditor.Editor.CreateEditor(theme.objectReferenceValue);
                            themeEditor.OnInspectorGUI();
                        }

                        listSettings[i] = settings;
                    }
                }
            }// profile for loop

            if (InspectorUIUtility.RenderIndentedButton(AddProfileContent, EditorStyles.miniButton))
            {
                AddProfile();
            }

            serializedObject.ApplyModifiedProperties();
        }
Example #3
0
        protected bool RenderDeleteButton(int index)
        {
            // Create Delete button if we have an array of themes
            if (themeDefinitions.arraySize > 1 && InspectorUIUtility.SmallButton(RemoveThemePropertyContent))
            {
                ClearHistoryCache(index);
                DeleteThemeDefinition((uint)index);

                serializedObject.Update();
                EditorUtility.SetDirty(theme);
                return(true);
            }

            return(false);
        }
        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();
        }
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

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

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

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

            Rect stateModelPos = EditorGUILayout.GetControlRect();

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

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

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

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

                    activeIndex.intValue = i;

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

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

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

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

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

            serializedObject.ApplyModifiedProperties();
        }
        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);
                }
            }
        }
Example #8
0
        public override void OnInspectorGUI()
        {
            //base.OnInspectorGUI();
            serializedObject.Update();

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

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

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

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

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

            int bitCount = 0;

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

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

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

                index.intValue = i;

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

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

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

                EditorGUILayout.EndHorizontal();

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

                EditorGUILayout.EndVertical();
            }

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

            serializedObject.ApplyModifiedProperties();
        }
        public 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();
        }
Example #10
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();
        }
        // Draw custom inspector for the SpeechKeyword state
        private void RenderSpeechKeywordInspector(SerializedProperty speechKeywordEventConfiguration)
        {
            SerializedProperty global             = speechKeywordEventConfiguration.FindPropertyRelative("global");
            SerializedProperty keywords           = speechKeywordEventConfiguration.FindPropertyRelative("keywords");
            SerializedProperty speechKeywordEvent = speechKeywordEventConfiguration.FindPropertyRelative("onAnySpeechKeywordRecognized");

            EditorGUILayout.PropertyField(global);
            EditorGUILayout.PropertyField(speechKeywordEvent);

            InspectorUIUtility.DrawTitle("Keyword Events");

            for (int j = 0; j < keywords.arraySize; j++)
            {
                SerializedProperty keywordContainter    = keywords.GetArrayElementAtIndex(j);
                SerializedProperty keyword              = keywordContainter.FindPropertyRelative("keyword");
                SerializedProperty keywordResponseEvent = keywordContainter.FindPropertyRelative("OnKeywordRecognized");

                using (new EditorGUILayout.HorizontalScope())
                {
                    using (new EditorGUILayout.VerticalScope())
                    {
                        EditorGUILayout.Space();

                        EditorGUILayout.PropertyField(keyword);

                        var speechCommands = GetProfileSpeechCommands();

                        if (speechCommands != null)
                        {
                            bool doesKeywordExistInProfile = Array.Exists(speechCommands, word => word.Keyword == keyword.stringValue);

                            if (!doesKeywordExistInProfile)
                            {
                                EditorGUILayout.HelpBox(
                                    "The Keyword above is not registered in the speech command profile. \n " +
                                    "To register a keyword:\n " +
                                    "1. Select the MixedRealityToolkit game object\n " +
                                    "2. Select Copy and Customize at the top of the profile\n " +
                                    "3. Navigate to the Input section and select Clone to enable modification of the Input profile\n " +
                                    "4. Scroll down to the Speech section in the Input profile and clone the Speech Profile\n " +
                                    "5. Select Add a New Speech Command\n ", MessageType.Error);
                            }
                            else
                            {
                                EditorGUILayout.PropertyField(keywordResponseEvent);
                            }
                        }

                        EditorGUILayout.Space();
                    }

                    if (InspectorUIUtility.SmallButton(RemoveStateButtonLabel))
                    {
                        keywords.DeleteArrayElementAtIndex(j);
                        break;
                    }
                }
            }

            if (GUILayout.Button("Add Keyword"))
            {
                keywords.InsertArrayElementAtIndex(keywords.arraySize);
            }
        }
        private void RenderStates()
        {
            // Draw a States title for the State list section
            InspectorUIUtility.DrawTitle(statesLabel);

            for (int i = 0; i < states.arraySize; i++)
            {
                SerializedProperty state      = states.GetArrayElementAtIndex(i);
                SerializedProperty stateName  = state.FindPropertyRelative("stateName");
                SerializedProperty stateValue = state.FindPropertyRelative("stateValue");
                SerializedProperty stateEventConfiguration = state.FindPropertyRelative("eventConfiguration");

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

                    Color previousGUIColor = GUI.color;

                    if (inPlayMode)
                    {
                        // If the state is active, then highlight the state container with a new color
                        if (stateValue.intValue == 1)
                        {
                            GUI.color = Color.cyan;
                        }
                    }

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

                        // Draw a foldout for the state configuration
                        if (InspectorUIUtility.DrawSectionFoldoutWithKey(stateName.stringValue, stateFoldoutID, MixedRealityStylesUtility.TitleFoldoutStyle, true))
                        {
                            EditorGUILayout.Space();

                            using (new EditorGUILayout.VerticalScope())
                            {
                                using (new EditorGUI.IndentLevelScope())
                                {
                                    // Show the event configuration for the state
                                    RenderStateEventConfiguration(stateName, stateEventConfiguration);

                                    CheckSpecialCaseStates(stateName.stringValue, stateEventConfiguration);

                                    // When a new core state is added via inspector, the name is initialized to "New Core State" and then changed
                                    // to the name the user selects from the list of CoreInteractionStates
                                    if (stateName.stringValue == newCoreStateName)
                                    {
                                        SetCoreStateType(state, stateName);
                                    }

                                    // When a new state is added via inspector, the name is initialized to "Create Custom State" and then changed
                                    // to the name the user enters a name in the text field and then selects the "Set State Name" button
                                    if (stateName.stringValue == createCustomStateName)
                                    {
                                        SetUserDefinedState(stateName);
                                    }
                                }
                            }
                        }

                        EditorGUILayout.Space();
                    }

                    // Do not draw a remove button during play mode
                    if (!inPlayMode)
                    {
                        if (CanDrawRemoveButton(stateName.stringValue))
                        {
                            // Draw a button with a '-' for state removal
                            if (InspectorUIUtility.SmallButton(RemoveStateButtonLabel))
                            {
                                states.DeleteArrayElementAtIndex(i);
                                break;
                            }
                        }
                    }

                    // Draw the state value in the inspector next to the state during play mode for debugging
                    if (inPlayMode)
                    {
                        using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox, GUILayout.Width(stateValueDisplayWidth)))
                        {
                            EditorGUILayout.Space();
                            EditorGUILayout.LabelField(stateValue.intValue.ToString(), GUILayout.Width(stateValueDisplayContainerWidth));
                            EditorGUILayout.Space();
                        }
                    }

                    GUI.color = previousGUIColor;
                }
            }
        }
        private void RenderStateContainers()
        {
            InspectorUIUtility.DrawTitle("State Animations");

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

                Color previousGUIColor = GUI.color;

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

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

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

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

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

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

                                RenderAnimationTargetList(animationTargetsList, stateContainerName);
                            }
                        }

                        EditorGUILayout.Space();
                    }

                    GUI.color = previousGUIColor;

                    if (!inPlayMode)
                    {
                        if (InspectorUIUtility.SmallButton(RemoveButtonLabel))
                        {
                            instance.RemoveAnimatorState(instance.RootStateMachine, stateContainerName.stringValue);
                            stateContainers.DeleteArrayElementAtIndex(i);
                            break;
                        }
                    }
                }
            }
        }
        private void RenderAnimationTargetList(SerializedProperty animationTargetList, SerializedProperty stateContainerName)
        {
            using (new EditorGUI.IndentLevelScope())
            {
                for (int j = 0; j < animationTargetList.arraySize; j++)
                {
                    SerializedProperty animationTarget        = animationTargetList.GetArrayElementAtIndex(j);
                    SerializedProperty targetObj              = animationTarget.FindPropertyRelative("target");
                    SerializedProperty animatablePropertyList = animationTarget.FindPropertyRelative("stateAnimatableProperties");

                    EditorGUILayout.Space();

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

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

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

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

                                animationTargetList.DeleteArrayElementAtIndex(j);
                                break;
                            }
                        }

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

                                GameObject targetGameObject = targetObj.objectReferenceValue as GameObject;

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

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

                            EditorGUILayout.Space();
                        }
                    }
                }

                EditorGUILayout.Space();

                RenderAddTargetButton(animationTargetList);
            }
        }