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