private void RenderThemeStates(SerializedProperty themeDefinition) { EditorGUILayout.LabelField("State Properties", EditorStyles.boldLabel); using (new EditorGUI.IndentLevelScope()) { for (int n = 0; n < themeStates.Length; n++) { InspectorUIUtility.DrawLabel(themeStates[n].Name, (int)(InspectorUIUtility.DefaultFontSize * ThemeStateFontScale), InspectorUIUtility.ColorTint50); SerializedProperty stateProperties = themeDefinition.FindPropertyRelative("stateProperties"); using (new EditorGUI.IndentLevelScope()) { for (int i = 0; i < stateProperties.arraySize; i++) { SerializedProperty propertyItem = stateProperties.GetArrayElementAtIndex(i); SerializedProperty values = propertyItem.FindPropertyRelative("values"); if (n >= values.arraySize) { // This property does not have the correct number of state values continue; } SerializedProperty name = propertyItem.FindPropertyRelative("name"); SerializedProperty type = propertyItem.FindPropertyRelative("type"); SerializedProperty statePropertyValue = values.GetArrayElementAtIndex(n); RenderValue(statePropertyValue, new GUIContent(name.stringValue, ""), (ThemePropertyTypes)type.intValue); } } } GUILayout.Space(5); } GUILayout.Space(5); }
/// <summary> /// Render the custom properties for the Leap Motion profile /// </summary> public virtual void RenderCustomInspector() { using (new EditorGUI.DisabledGroupScope(IsProfileLock((BaseMixedRealityProfile)target))) { // Add the documentation help button using (new EditorGUILayout.HorizontalScope()) { // Draw an empty title to align the documentation button to the right InspectorUIUtility.DrawLabel("", InspectorUIUtility.DefaultFontSize, InspectorUIUtility.ColorTint10); InspectorUIUtility.RenderDocumentationButton(leapDocURL); } // Show warning if the leap core assets are not in the project if (!LeapMotionUtilities.IsLeapInProject) { EditorGUILayout.HelpBox("The Leap Motion Core Assets could not be found in your project. For more information, visit the Leap Motion MRTK documentation.", MessageType.Error); } else { serializedObject.Update(); EditorGUILayout.PropertyField(leapControllerOrientation); if (instance.LeapControllerOrientation == LeapControllerOrientation.Desk) { EditorGUILayout.PropertyField(leapControllerOffset); } else if (instance.LeapControllerOrientation == LeapControllerOrientation.Headset) { // Allow selection of the LeapVRDeviceOffsetMode if the LeapControllerOrientation is Headset EditorGUILayout.PropertyField(leapVRDeviceOffsetMode); if (leapVRDeviceOffsetMode.enumValueIndex == (int)LeapVRDeviceOffsetMode.ManualHeadOffset) { // Display the properties for editing the head offset EditorGUILayout.PropertyField(leapVRDeviceOffsetY); EditorGUILayout.PropertyField(leapVRDeviceOffsetZ); EditorGUILayout.PropertyField(leapVRDeviceOffsetTiltX); } else if (leapVRDeviceOffsetMode.enumValueIndex == (int)LeapVRDeviceOffsetMode.Transform) { // Display the transform property // EditorGUILayout.PropertyField() did not allow the setting the transform property in editor leapVRDeviceOriginTransform = EditorGUILayout.ObjectField("Leap VR Device Origin", leapVRDeviceOrigin.objectReferenceValue, typeof(Transform), true) as Transform; instance.LeapVRDeviceOrigin = leapVRDeviceOriginTransform; } } // Display pinch thresholds EditorGUILayout.PropertyField(enterPinchDistance); EditorGUILayout.PropertyField(exitPinchDistance); serializedObject.ApplyModifiedProperties(); } } }
/// <summary> /// Render the custom properties for the Leap Motion profile /// </summary> public virtual void RenderCustomInspector() { using (new EditorGUI.DisabledGroupScope(IsProfileLock((BaseMixedRealityProfile)target))) { // Add the documentation help button using (new EditorGUILayout.HorizontalScope()) { // Draw an empty title to align the documentation button to the right InspectorUIUtility.DrawLabel("", InspectorUIUtility.DefaultFontSize, InspectorUIUtility.ColorTint10); InspectorUIUtility.RenderDocumentationButton(leapDocURL); } // Show warning if the leap core assets are not in the project if (!EskyLeapMotionUtilities.IsLeapInProject) { EditorGUILayout.HelpBox("The Leap Motion Core Assets could not be found in your project. For more information, visit the Leap Motion MRTK documentation.", MessageType.Error); } else { serializedObject.Update(); EditorGUILayout.PropertyField(leapControllerOrientation); if (instance.LeapControllerOrientation == EskyLeapControllerOrientation.Desk) { EditorGUILayout.PropertyField(leapControllerOffset); } EditorGUILayout.PropertyField(enterPinchDistance); EditorGUILayout.PropertyField(exitPinchDistance); serializedObject.ApplyModifiedProperties(); } } }
protected void RenderGeneralSettings() { Rect position; using (new EditorGUILayout.HorizontalScope()) { InspectorUIUtility.DrawLabel("General", InspectorUIUtility.TitleFontSize, InspectorUIUtility.ColorTint10); if (target != null) { var helpURL = target.GetType().GetCustomAttribute <HelpURLAttribute>(); if (helpURL != null) { InspectorUIUtility.RenderDocumentationButton(helpURL.URL); } } } using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { // If states value is not provided, try to use Default states type if (statesProperty.objectReferenceValue == null) { statesProperty.objectReferenceValue = GetDefaultInteractableStatesFile(); } EditorGUILayout.PropertyField(statesProperty, new GUIContent("States")); if (statesProperty.objectReferenceValue == null) { InspectorUIUtility.DrawError("Please assign a States object!"); serializedObject.ApplyModifiedProperties(); return; } EditorGUILayout.PropertyField(enabledProperty, new GUIContent("Enabled")); // Input Actions bool validActionOptions = inputActionOptions != null; using (new EditorGUI.DisabledScope(!validActionOptions)) { var actionOptions = validActionOptions ? inputActionOptions : new string[] { "Missing Mixed Reality Toolkit" }; DrawDropDownProperty(EditorGUILayout.GetControlRect(), actionId, actionOptions, InputActionsLabel); } using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.PropertyField(isGlobal, new GUIContent("Is Global")); } // Speech keywords bool validSpeechKeywords = speechKeywordOptions != null; using (new EditorGUI.DisabledScope(!validSpeechKeywords)) { string[] keywordOptions = validSpeechKeywords ? speechKeywordOptions : new string[] { "Missing Speech Commands" }; int currentIndex = validSpeechKeywords ? SpeechKeywordLookup(voiceCommands.stringValue, speechKeywordOptions) : 0; position = EditorGUILayout.GetControlRect(); // BeginProperty allows tracking of serialized properties for bolding prefab changes etc using (new EditorGUI.PropertyScope(position, SpeechComamndsLabel, voiceCommands)) { currentIndex = EditorGUI.Popup(position, SpeechComamndsLabel.text, currentIndex, keywordOptions); if (validSpeechKeywords) { voiceCommands.stringValue = currentIndex > 0 ? speechKeywordOptions[currentIndex] : string.Empty; } } } // show requires gaze because voice command has a value if (!string.IsNullOrEmpty(voiceCommands.stringValue)) { using (new EditorGUI.IndentLevelScope()) { SerializedProperty requireGaze = serializedObject.FindProperty("voiceRequiresFocus"); EditorGUILayout.PropertyField(requireGaze, VoiceRequiresFocusLabel); } } // should be 1 or more dimensions.intValue = Mathf.Clamp(dimensions.intValue, 1, 9); // user-friendly dimension settings SelectionModes selectionMode = SelectionModes.Button; position = EditorGUILayout.GetControlRect(); using (new EditorGUI.PropertyScope(position, selectionModeLabel, dimensions)) { // Show enum popup for selection mode, hide option to select SelectionModes.Invalid selectionMode = (SelectionModes)EditorGUI.EnumPopup(position, selectionModeLabel, Interactable.ConvertToSelectionMode(dimensions.intValue), (value) => { return((SelectionModes)value != SelectionModes.Invalid); }); 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; } } if (dimensions.intValue > 1) { // toggle or multi dimensional button using (new EditorGUI.IndentLevelScope()) { EditorGUILayout.PropertyField(canSelect, new GUIContent("Can Select", "The user can toggle this button")); EditorGUILayout.PropertyField(canDeselect, new GUIContent("Can Deselect", "The user can untoggle this button, set false for a radial interaction.")); position = EditorGUILayout.GetControlRect(); using (new EditorGUI.PropertyScope(position, startDimensionLabel, startDimensionIndex)) { var mode = Interactable.ConvertToSelectionMode(dimensions.intValue); if (mode == SelectionModes.Toggle) { bool isToggled = EditorGUI.Toggle(position, isToggledLabel, startDimensionIndex.intValue > 0); startDimensionIndex.intValue = isToggled ? 1 : 0; } else if (mode == SelectionModes.MultiDimension) { startDimensionIndex.intValue = EditorGUI.IntField(position, startDimensionLabel, startDimensionIndex.intValue); } startDimensionIndex.intValue = Mathf.Clamp(startDimensionIndex.intValue, 0, dimensions.intValue - 1); } } } } }
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(); }