private static EntryAction RenderManualConstraintItem(SerializedProperty constraintEntry, bool canRemove = true) { var constraint = constraintEntry.objectReferenceValue; if (constraint == null) { // clean up deleted constraints return(EntryAction.Detach); } using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField(constraint.GetType().Name, GUILayout.ExpandWidth(true)); if (canRemove) { if (InspectorUIUtility.FlexButton(new GUIContent("Go to Constraint", "Scroll inspector to and visually highlight constraint."))) { return(EntryAction.Highlight); } if (InspectorUIUtility.FlexButton(new GUIContent("Remove Entry", "Remove constraint from constraint manager but keep constraint component attached to the gameobject."))) { return(EntryAction.Detach); } } return(EntryAction.None); } }
public static void RenderEventSettings(SerializedProperty eventItem, int index, InteractableTypesContainer options, InspectorUIUtility.MultiListButtonEvent changeEvent, InspectorUIUtility.ListButtonEvent removeEvent) { EditorGUILayout.BeginVertical("Box"); SerializedProperty uEvent = eventItem.FindPropertyRelative("Event"); SerializedProperty eventName = eventItem.FindPropertyRelative("Name"); SerializedProperty className = eventItem.FindPropertyRelative("ClassName"); SerializedProperty assemblyQualifiedName = eventItem.FindPropertyRelative("AssemblyQualifiedName"); SerializedProperty hideEvents = eventItem.FindPropertyRelative("HideUnityEvents"); // show event dropdown int id = InspectorUIUtility.ReverseLookup(className.stringValue, options.ClassNames); Rect position = EditorGUILayout.GetControlRect(); GUIContent selectLable = new GUIContent("Select Event Type", "Select the event type from the list"); EditorGUI.BeginProperty(position, selectLable, className); { int newId = EditorGUI.Popup(position, selectLable.text, id, options.ClassNames); if (id != newId || String.IsNullOrEmpty(className.stringValue)) { className.stringValue = options.ClassNames[newId]; assemblyQualifiedName.stringValue = options.AssemblyQualifiedNames[newId]; changeEvent(new int[] { index, newId }, eventItem); } } EditorGUI.EndProperty(); if (!hideEvents.boolValue) { EditorGUILayout.PropertyField(uEvent, new GUIContent(eventName.stringValue)); } // show event properties EditorGUI.indentLevel = indentOnSectionStart + 1; SerializedProperty eventSettings = eventItem.FindPropertyRelative("Settings"); for (int j = 0; j < eventSettings.arraySize; j++) { SerializedProperty propertyField = eventSettings.GetArrayElementAtIndex(j); bool isEvent = InspectorFieldsUtility.IsPropertyType(propertyField, InspectorField.FieldTypes.Event); if (!hideEvents.boolValue || !isEvent) { InspectorFieldsUtility.DisplayPropertyField(eventSettings.GetArrayElementAtIndex(j)); } } EditorGUI.indentLevel = indentOnSectionStart; EditorGUILayout.Space(); if (removeEvent != null) { InspectorUIUtility.FlexButton(new GUIContent("Remove Event"), index, removeEvent); } EditorGUILayout.EndVertical(); }
private void RenderDefaultThemeWarning(SerializedProperty profileItem, SerializedProperty themeItem) { if (themeItem.objectReferenceValue.name == "DefaultTheme") { using (new EditorGUILayout.HorizontalScope()) { InspectorUIUtility.DrawWarning("DefaultTheme should not be edited. "); if (InspectorUIUtility.FlexButton(CreateThemeLabel)) { CreateTheme(profileItem, themeItem); } } } }
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(); }
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(); }
/// <summary> /// Render event properties for the given event item. If item has been removed, returns true. False otherwise /// </summary> /// <param name="eventItem">serialized property of the event item to render properties from</param> /// <returns>If item has been removed, returns true. False otherwise</returns> public static bool RenderEvent(SerializedProperty eventItem, bool canRemove = true) { using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { SerializedProperty uEvent = eventItem.FindPropertyRelative("Event"); SerializedProperty eventName = eventItem.FindPropertyRelative("Name"); SerializedProperty className = eventItem.FindPropertyRelative("ClassName"); SerializedProperty assemblyQualifiedName = eventItem.FindPropertyRelative("AssemblyQualifiedName"); Type receiverType; InspectorUIUtility.DrawHeader("Event Receiver Type"); using (new EditorGUILayout.HorizontalScope()) { Rect position = EditorGUILayout.GetControlRect(); using (new EditorGUI.PropertyScope(position, SelectEventLabel, className)) { var receiverTypes = TypeCacheUtility.GetSubClasses <ReceiverBase>(); var recevierClassNames = receiverTypes.Select(t => t?.Name).ToArray(); int id = Array.IndexOf(recevierClassNames, className.stringValue); int newId = EditorGUI.Popup(position, id, recevierClassNames); if (newId == -1) { newId = 0; } receiverType = receiverTypes[newId]; // Temporary workaround to fix bug shipped in GA where assemblyQualifiedName was never set if (string.IsNullOrEmpty(assemblyQualifiedName.stringValue)) { assemblyQualifiedName.stringValue = receiverType.AssemblyQualifiedName; } if (id != newId) { EventChanged(receiverType, eventItem); } } if (canRemove) { if (InspectorUIUtility.FlexButton(new GUIContent("Remove Event"))) { return(true); } } } EditorGUILayout.Space(); InspectorUIUtility.DrawHeader("Event Properties"); ReceiverBase receiver = (ReceiverBase)Activator.CreateInstance(receiverType, new UnityEvent()); if (!receiver.HideUnityEvents) { EditorGUILayout.PropertyField(uEvent, new GUIContent(receiver.Name)); } SerializedProperty eventSettings = eventItem.FindPropertyRelative("Settings"); // If fields for given receiver class type have been changed, update the related inspector field data var fieldList = InspectorFieldsUtility.GetInspectorFields(receiver); if (!InspectorFieldsUtility.AreFieldsSame(eventSettings, fieldList)) { InspectorFieldsUtility.UpdateSettingsList(eventSettings, fieldList); } for (int index = 0; index < eventSettings.arraySize; index++) { SerializedProperty propertyField = eventSettings.GetArrayElementAtIndex(index); bool isEvent = InspectorFieldsUtility.IsPropertyType(propertyField, InspectorField.FieldTypes.Event); if (!receiver.HideUnityEvents || !isEvent) { InspectorFieldsUtility.DisplayPropertyField(eventSettings.GetArrayElementAtIndex(index)); } } } return(false); }
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(); }
/// <summary> /// Render event properties for the given event item. If item has been removed, returns true. False otherwise /// </summary> /// <param name="eventItem">serialized property of the event item to render properties from</param> /// <param name="index">index of event item in higher order list</param> /// <param name="options">Event type options</param> /// <param name="changeEvent">Function to call if event properties have changed</param> /// <param name="removeEvent">Function to call if event requested to be removed</param> /// <returns>If item has been removed, returns true. False otherwise</returns> public static bool RenderEventSettings(SerializedProperty eventItem, int index, InteractableTypesContainer options, InspectorUIUtility.MultiListButtonEvent changeEvent, InspectorUIUtility.ListButtonEvent removeEvent) { using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { SerializedProperty uEvent = eventItem.FindPropertyRelative("Event"); SerializedProperty eventName = eventItem.FindPropertyRelative("Name"); SerializedProperty className = eventItem.FindPropertyRelative("ClassName"); SerializedProperty assemblyQualifiedName = eventItem.FindPropertyRelative("AssemblyQualifiedName"); SerializedProperty hideEvents = eventItem.FindPropertyRelative("HideUnityEvents"); // show event dropdown int id = InspectorUIUtility.ReverseLookup(className.stringValue, options.ClassNames); using (new EditorGUILayout.HorizontalScope()) { Rect position = EditorGUILayout.GetControlRect(); EditorGUI.BeginProperty(position, SelectEventLabel, className); { int newId = EditorGUI.Popup(position, id, options.ClassNames); if (id != newId || String.IsNullOrEmpty(className.stringValue)) { className.stringValue = options.ClassNames[newId]; assemblyQualifiedName.stringValue = options.AssemblyQualifiedNames[newId]; changeEvent(new int[] { index, newId }, eventItem); } } EditorGUI.EndProperty(); if (removeEvent != null) { if (InspectorUIUtility.FlexButton(new GUIContent("Remove Event"), index, removeEvent)) { return(true); } } } EditorGUILayout.Space(); if (!hideEvents.boolValue) { EditorGUILayout.PropertyField(uEvent, new GUIContent(eventName.stringValue)); } // show event properties SerializedProperty eventSettings = eventItem.FindPropertyRelative("Settings"); for (int j = 0; j < eventSettings.arraySize; j++) { SerializedProperty propertyField = eventSettings.GetArrayElementAtIndex(j); bool isEvent = InspectorFieldsUtility.IsPropertyType(propertyField, InspectorField.FieldTypes.Event); if (!hideEvents.boolValue || !isEvent) { InspectorFieldsUtility.DisplayPropertyField(eventSettings.GetArrayElementAtIndex(j)); } } } return(false); }