public override void OnInspectorGUI() { serializedObject.Update(); if (target != null) { InspectorUIUtility.RenderHelpURL(target.GetType()); } bool trackedObjectChanged = false; EditorGUI.BeginChangeCheck(); InspectorUIUtility.DrawEnumSerializedProperty(trackedTargetProperty, TrackedTypeLabel, solverHandler.TrackedTargetType); if (!SolverHandler.IsValidTrackedObjectType(solverHandler.TrackedTargetType)) { InspectorUIUtility.DrawWarning(" Current Tracked Target Type value of \"" + Enum.GetName(typeof(TrackedObjectType), solverHandler.TrackedTargetType) + "\" is obsolete. Select MotionController or HandJoint values instead"); } if (trackedTargetProperty.enumValueIndex == (int)TrackedObjectType.HandJoint || trackedTargetProperty.enumValueIndex == (int)TrackedObjectType.MotionController) { EditorGUILayout.PropertyField(trackedHandnessProperty); if (trackedHandnessProperty.enumValueIndex > (int)Handedness.Both) { InspectorUIUtility.DrawWarning("Only Handedness values of None, Left, Right, and Both are valid"); } } if (trackedTargetProperty.enumValueIndex == (int)TrackedObjectType.HandJoint) { EditorGUILayout.PropertyField(trackedHandJointProperty); } else if (trackedTargetProperty.enumValueIndex == (int)TrackedObjectType.CustomOverride) { EditorGUILayout.PropertyField(transformOverrideProperty); } EditorGUILayout.PropertyField(additionalOffsetProperty); EditorGUILayout.PropertyField(additionalRotationProperty); trackedObjectChanged = EditorGUI.EndChangeCheck(); EditorGUILayout.PropertyField(updateSolversProperty); serializedObject.ApplyModifiedProperties(); if (EditorApplication.isPlaying && trackedObjectChanged) { solverHandler.RefreshTrackedObject(); } }
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); } } } }
public override void OnInspectorGUI() { base.OnInspectorGUI(); serializedObject.Update(); // General Properties EditorGUILayout.LabelField("General Properties", EditorStyles.boldLabel); EditorGUILayout.PropertyField(surfaceNormalOffsetProperty); EditorGUILayout.PropertyField(surfaceRayOffsetProperty); EditorGUILayout.PropertyField(orientationModeProperty); if (surfaceMagnetism.CurrentOrientationMode != SurfaceMagnetism.OrientationMode.None) { EditorGUILayout.PropertyField(orientationVerticalProperty); } if (surfaceMagnetism.CurrentOrientationMode == SurfaceMagnetism.OrientationMode.Blended) { EditorGUILayout.PropertyField(orientationBlendProperty); } // Raycast properties EditorGUILayout.LabelField("Raycast Properties", EditorStyles.boldLabel); EditorGUILayout.PropertyField(magneticSurfacesProperty, true); // When raycast from the center of the GameObject, Raycast may hit one of the collider on the GameObject (or children) // This results in the GameObject "magnetizes" against itself. Warn user if this possibility exists var colliders = surfaceMagnetism.GetComponentsInChildren <Collider>(); foreach (var collider in colliders) { if (surfaceMagnetism.MagneticSurfaces.Any(s => collider.gameObject.IsInLayerMask(s))) { InspectorUIUtility.DrawWarning("This GameObject, or a child of the GameObject, has a collider on a layer listed in the Magnetic Surfaces property. Raycasts calculated for the SurfaceMagnetism component may result in hits against itself causing odd behavior. Consider moving this GameObject and all children to the \"Ignore Raycast\" layer"); break; } } EditorGUILayout.PropertyField(closestDistanceProperty); EditorGUILayout.PropertyField(maxDistanceProperty); EditorGUILayout.PropertyField(currentRaycastDirectionModeProperty); EditorGUILayout.PropertyField(raycastModeProperty); // Draw properties dependent on type of raycast direction mode selected switch (raycastModeProperty.intValue) { case (int)SceneQueryType.BoxRaycast: EditorGUILayout.PropertyField(boxRaysPerEdgeProperty); EditorGUILayout.PropertyField(orthographicBoxCastProperty); EditorGUILayout.PropertyField(maximumNormalVarianceProperty); break; case (int)SceneQueryType.SphereCast: EditorGUILayout.PropertyField(sphereSizeProperty); break; case (int)SceneQueryType.SphereOverlap: InspectorUIUtility.DrawWarning("SurfaceMagnetism does not support SphereOverlap raycast mode"); break; } if (raycastModeProperty.intValue != (int)SceneQueryType.SimpleRaycast && raycastModeProperty.intValue != (int)SceneQueryType.SphereOverlap) { EditorGUILayout.PropertyField(volumeCastSizeOverrideProperty); } // Other properties EditorGUILayout.LabelField("Other Properties", EditorStyles.boldLabel); EditorGUILayout.PropertyField(useLinkedAltScaleOverrideProperty); EditorGUILayout.PropertyField(debugEnabledProperty); serializedObject.ApplyModifiedProperties(); }
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(); }
private void RenderProfileSettings() { if (profileList.arraySize < 1) { AddProfile(0); } if (InspectorUIUtility.DrawSectionFoldoutWithKey("Profiles", ShowProfilesPrefKey, MixedRealityStylesUtility.BoldTitleFoldoutStyle)) { // Render all profile items. Profiles are per GameObject/ThemeContainer for (int i = 0; i < profileList.arraySize; i++) { using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { SerializedProperty profileItem = profileList.GetArrayElementAtIndex(i); SerializedProperty hostGameObject = profileItem.FindPropertyRelative("Target"); using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.PropertyField(hostGameObject, new GUIContent("Target", "Target gameObject for this theme properties to manipulate")); if (InspectorUIUtility.SmallButton(new GUIContent(InspectorUIUtility.Minus, "Remove Profile"), i, RemoveProfile)) { // Profile removed via RemoveProfile callback continue; } } if (hostGameObject.objectReferenceValue == null) { InspectorUIUtility.DrawError("Assign a GameObject to apply visual effects"); if (GUILayout.Button("Assign Self")) { hostGameObject.objectReferenceValue = instance.gameObject; } } SerializedProperty themes = profileItem.FindPropertyRelative("Themes"); ValidateThemesForDimensions(dimensions, themes); // Render all themes for current target for (int t = 0; t < themes.arraySize; t++) { SerializedProperty themeItem = themes.GetArrayElementAtIndex(t); string themeLabel = BuildThemeTitle(dimensions.intValue, t); if (themeItem.objectReferenceValue != null) { bool showThemeSettings = false; using (new EditorGUILayout.HorizontalScope()) { string prefKey = themeItem.objectReferenceValue.name + "Profiles" + i + "_Theme" + t + "_Edit"; showThemeSettings = InspectorUIUtility.DrawSectionFoldoutWithKey(themeLabel, prefKey, null, false); EditorGUILayout.PropertyField(themeItem, new GUIContent(string.Empty, "Theme properties for interaction feedback")); } if (themeItem.objectReferenceValue != null) { // TODO: Odd bug where themeStates below is null when it shouldn't be. Use instance object as workaround atm //SerializedProperty themeStates = themeItem.FindPropertyRelative("States"); var themeInstance = themeItem.objectReferenceValue as Theme; if (statesProperty.objectReferenceValue != themeInstance.States) { InspectorUIUtility.DrawWarning($"{themeInstance.name}'s States property does not match Interactable's States property"); } if (showThemeSettings) { using (new EditorGUI.IndentLevelScope()) { UnityEditor.Editor themeEditor = UnityEditor.Editor.CreateEditor(themeItem.objectReferenceValue); themeEditor.OnInspectorGUI(); } } } } else { EditorGUILayout.PropertyField(themeItem, new GUIContent(themeLabel, "Theme properties for interaction feedback")); InspectorUIUtility.DrawError("Assign a Theme to add visual effects"); if (GUILayout.Button(CreateThemeLabel)) { themeItem.objectReferenceValue = CreateThemeAsset(hostGameObject.objectReferenceValue.name); return; } } EditorGUILayout.Space(); } } } if (GUILayout.Button(new GUIContent("Add Profile"))) { AddProfile(profileList.arraySize); } } }
public 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(); }