namespace VRTK { using UnityEngine; using UnityEngine.SceneManagement; #if UNITY_2017_2_OR_NEWER using UnityEngine.XR; #else using XRSettings = UnityEngine.VR.VRSettings; #endif using UnityEditor; using UnityEditor.SceneManagement; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; [CustomEditor(typeof(VRTK_SDKSetup))] public class VRTK_SDKSetupEditor : Editor { private const string SDKNotInstalledDescription = " (not installed)"; private const string SDKNotFoundAnymoreDescription = " (not found)"; private bool? isDetailedSDKSelectionFoldOut; public override void OnInspectorGUI() { VRTK_SDKSetup setup = (VRTK_SDKSetup)target; serializedObject.Update(); using (new EditorGUILayout.VerticalScope("Box")) { VRTK_EditorUtilities.AddHeader("SDK Selection", false); Func, string> sdkNameSelector = (info, installedInfos) => info.description.prettyName + (installedInfos.Contains(info) ? "" : SDKNotInstalledDescription); string[] availableSystemSDKNames = VRTK_SDKManager.AvailableSystemSDKInfos.Select(info => sdkNameSelector(info, VRTK_SDKManager.InstalledSystemSDKInfos)).ToArray(); string[] availableBoundariesSDKNames = VRTK_SDKManager.AvailableBoundariesSDKInfos.Select(info => sdkNameSelector(info, VRTK_SDKManager.InstalledBoundariesSDKInfos)).ToArray(); string[] availableHeadsetSDKNames = VRTK_SDKManager.AvailableHeadsetSDKInfos.Select(info => sdkNameSelector(info, VRTK_SDKManager.InstalledHeadsetSDKInfos)).ToArray(); string[] availableControllerSDKNames = VRTK_SDKManager.AvailableControllerSDKInfos.Select(info => sdkNameSelector(info, VRTK_SDKManager.InstalledControllerSDKInfos)).ToArray(); string[] allAvailableSDKNames = availableSystemSDKNames .Concat(availableBoundariesSDKNames) .Concat(availableHeadsetSDKNames) .Concat(availableControllerSDKNames) .Distinct() .ToArray(); using (new EditorGUILayout.HorizontalScope()) { EditorGUI.BeginChangeCheck(); List quickSelectOptions = allAvailableSDKNames .Select(sdkName => new GUIContent(sdkName)) .ToList(); int quicklySelectedSDKIndex = 0; if (AreAllSDKsTheSame()) { quicklySelectedSDKIndex = allAvailableSDKNames .ToList() .FindIndex(availableSDKName => availableSDKName.Replace(SDKNotInstalledDescription, "") == setup.systemSDKInfo.description.prettyName); } else { quickSelectOptions.Insert(0, new GUIContent("Mixed...")); } quicklySelectedSDKIndex = EditorGUILayout.Popup( new GUIContent("Quick Select", "Quickly select one of the SDKs into all slots."), quicklySelectedSDKIndex, quickSelectOptions.ToArray()); if (!AreAllSDKsTheSame()) { quicklySelectedSDKIndex--; } if (EditorGUI.EndChangeCheck() && (AreAllSDKsTheSame() || quicklySelectedSDKIndex != -1)) { string quicklySelectedSDKName = allAvailableSDKNames[quicklySelectedSDKIndex].Replace(SDKNotInstalledDescription, ""); Func predicate = info => info.description.prettyName == quicklySelectedSDKName; VRTK_SDKInfo newSystemSDKInfo = VRTK_SDKManager.AvailableSystemSDKInfos.FirstOrDefault(predicate); VRTK_SDKInfo newBoundariesSDKInfo = VRTK_SDKManager.AvailableBoundariesSDKInfos.FirstOrDefault(predicate); VRTK_SDKInfo newHeadsetSDKInfo = VRTK_SDKManager.AvailableHeadsetSDKInfos.FirstOrDefault(predicate); VRTK_SDKInfo newControllerSDKInfo = VRTK_SDKManager.AvailableControllerSDKInfos.FirstOrDefault(predicate); Undo.RecordObject(setup, "SDK Change (Quick Select)"); if (newSystemSDKInfo != null) { setup.systemSDKInfo = newSystemSDKInfo; } if (newBoundariesSDKInfo != null) { setup.boundariesSDKInfo = newBoundariesSDKInfo; } if (newHeadsetSDKInfo != null) { setup.headsetSDKInfo = newHeadsetSDKInfo; } if (newControllerSDKInfo != null) { setup.controllerSDKInfo = newControllerSDKInfo; } UpdateDetailedSDKSelectionFoldOut(); } if (AreAllSDKsTheSame()) { VRTK_SDKInfo selectedInfo = new[] { setup.systemSDKInfo, setup.boundariesSDKInfo, setup.headsetSDKInfo, setup.controllerSDKInfo, }.First(info => info != null); DrawVRDeviceNameLabel(selectedInfo, "System, Boundaries, Headset and Controller", 0); } } EditorGUI.indentLevel++; if (!isDetailedSDKSelectionFoldOut.HasValue) { UpdateDetailedSDKSelectionFoldOut(); } isDetailedSDKSelectionFoldOut = EditorGUI.Foldout( EditorGUILayout.GetControlRect(), isDetailedSDKSelectionFoldOut.Value, "Detailed Selection", true ); if (isDetailedSDKSelectionFoldOut.Value) { EditorGUI.BeginChangeCheck(); DrawAndHandleSDKSelection("The SDK to use to deal with all system actions.", 1); DrawAndHandleSDKSelection("The SDK to use to utilize room scale boundaries.", 1); DrawAndHandleSDKSelection("The SDK to use to utilize the VR headset.", 1); DrawAndHandleSDKSelection("The SDK to use to utilize the input devices.", 1); if (EditorGUI.EndChangeCheck()) { UpdateDetailedSDKSelectionFoldOut(); } } EditorGUI.indentLevel--; string errorDescriptions = string.Join("\n", setup.GetSimplifiedErrorDescriptions()); if (!string.IsNullOrEmpty(errorDescriptions)) { EditorGUILayout.HelpBox(errorDescriptions, MessageType.Error); } if (allAvailableSDKNames.Length != availableSystemSDKNames.Length || allAvailableSDKNames.Length != availableBoundariesSDKNames.Length || allAvailableSDKNames.Length != availableHeadsetSDKNames.Length || allAvailableSDKNames.Length != availableControllerSDKNames.Length) { EditorGUILayout.HelpBox("Only endpoints that are supported by the selected SDK are changed by Quick Select, the others are left untouched." + "\n\nSome of the available SDK implementations are only available for a subset of SDK endpoints. Quick Select" + " shows SDKs that provide an implementation for *any* of the different SDK endpoints in VRTK" + " (System, Boundaries, Headset, Controller).", MessageType.Info); } } using (new EditorGUILayout.VerticalScope("Box")) { VRTK_EditorUtilities.AddHeader("Object References", false); using (new EditorGUILayout.HorizontalScope()) { EditorGUI.BeginChangeCheck(); bool autoPopulate = EditorGUILayout.Toggle(VRTK_EditorUtilities.BuildGUIContent("autoPopulateObjectReferences", "Auto Populate"), setup.autoPopulateObjectReferences, GUILayout.ExpandWidth(false)); if (EditorGUI.EndChangeCheck()) { serializedObject.FindProperty("autoPopulateObjectReferences").boolValue = autoPopulate; serializedObject.ApplyModifiedProperties(); setup.PopulateObjectReferences(false); } const string populateNowDescription = "Populate Now"; GUIContent populateNowGUIContent = new GUIContent(populateNowDescription, "Set the SDK object references to the objects of the selected SDKs."); if (GUILayout.Button(populateNowGUIContent, GUILayout.MaxHeight(GUI.skin.label.CalcSize(populateNowGUIContent).y))) { Undo.RecordObject(setup, populateNowDescription); setup.PopulateObjectReferences(true); } } if (setup.autoPopulateObjectReferences) { EditorGUILayout.HelpBox("The SDK Setup is configured to automatically populate object references so the following fields are disabled." + " Uncheck `Auto Populate` above to enable customization of the fields below.", MessageType.Info); } using (new EditorGUI.DisabledGroupScope(setup.autoPopulateObjectReferences)) { using (new EditorGUILayout.VerticalScope("Box")) { VRTK_EditorUtilities.AddHeader("Actual Objects", false); EditorGUILayout.PropertyField( serializedObject.FindProperty("actualBoundaries"), VRTK_EditorUtilities.BuildGUIContent("actualBoundaries", "Boundaries") ); EditorGUILayout.PropertyField( serializedObject.FindProperty("actualHeadset"), VRTK_EditorUtilities.BuildGUIContent("actualHeadset", "Headset") ); EditorGUILayout.PropertyField( serializedObject.FindProperty("actualLeftController"), VRTK_EditorUtilities.BuildGUIContent("actualLeftController", "Left Controller") ); EditorGUILayout.PropertyField( serializedObject.FindProperty("actualRightController"), VRTK_EditorUtilities.BuildGUIContent("actualRightController", "Right Controller") ); } using (new EditorGUILayout.VerticalScope("Box")) { VRTK_EditorUtilities.AddHeader("Model Aliases", false); EditorGUILayout.PropertyField( serializedObject.FindProperty("modelAliasLeftController"), VRTK_EditorUtilities.BuildGUIContent("modelAliasLeftController", "Left Controller") ); EditorGUILayout.PropertyField( serializedObject.FindProperty("modelAliasRightController"), VRTK_EditorUtilities.BuildGUIContent("modelAliasRightController", "Right Controller") ); } } EditorGUILayout.HelpBox( "The game object this SDK Setup is attached to will be set inactive automatically to allow for SDK loading and switching.", MessageType.Info ); IEnumerable referencedObjects = new[] { setup.actualBoundaries, setup.actualHeadset, setup.actualLeftController, setup.actualRightController, setup.modelAliasLeftController, setup.modelAliasRightController }.Where(referencedObject => referencedObject != null); if (referencedObjects.Any(referencedObject => !referencedObject.transform.IsChildOf(setup.transform))) { EditorGUILayout.HelpBox( "There is at least one referenced object that is neither a child of, deep child (child of a child) of nor attached to the game object this SDK Setup is attached to.", MessageType.Error ); } } serializedObject.ApplyModifiedProperties(); } #region Handle undo protected virtual void OnEnable() { Undo.undoRedoPerformed += UndoRedoPerformed; } protected virtual void OnDisable() { Undo.undoRedoPerformed -= UndoRedoPerformed; } private void UndoRedoPerformed() { UpdateDetailedSDKSelectionFoldOut(); } #endregion /// /// Draws a popup menu and handles the selection for an SDK info. /// /// The SDK base type. Must be a subclass of . /// The description of the SDK base. private void DrawAndHandleSDKSelection(string description, int indentLevelToRemove) where BaseType : SDK_Base { Type baseType = typeof(BaseType); Type sdkManagerType = typeof(VRTK_SDKManager); string baseName = baseType.Name.Remove(0, typeof(SDK_Base).Name.Length); ReadOnlyCollection availableSDKInfos = (ReadOnlyCollection)sdkManagerType .GetProperty(string.Format("Available{0}SDKInfos", baseName), BindingFlags.Public | BindingFlags.Static) .GetGetMethod() .Invoke(null, null); ReadOnlyCollection installedSDKInfos = (ReadOnlyCollection)sdkManagerType .GetProperty(string.Format("Installed{0}SDKInfos", baseName), BindingFlags.Public | BindingFlags.Static) .GetGetMethod() .Invoke(null, null); PropertyInfo sdkInfoPropertyInfo = typeof(VRTK_SDKSetup).GetProperty(string.Format("{0}SDKInfo", baseName.ToLowerInvariant())); VRTK_SDKSetup sdkSetup = (VRTK_SDKSetup)target; VRTK_SDKInfo selectedSDKInfo = (VRTK_SDKInfo)sdkInfoPropertyInfo.GetGetMethod().Invoke(sdkSetup, null); List availableSDKNames = availableSDKInfos.Select(info => info.description.prettyName + (installedSDKInfos.Contains(info) ? "" : SDKNotInstalledDescription)).ToList(); int selectedSDKIndex = availableSDKInfos.IndexOf(selectedSDKInfo); if (selectedSDKInfo.originalTypeNameWhenFallbackIsUsed != null) { availableSDKNames.Add(selectedSDKInfo.originalTypeNameWhenFallbackIsUsed + SDKNotFoundAnymoreDescription); selectedSDKIndex = availableSDKNames.Count - 1; } GUIContent[] availableSDKGUIContents = availableSDKNames.Select(availableSDKName => new GUIContent(availableSDKName)).ToArray(); using (new EditorGUILayout.HorizontalScope()) { EditorGUI.BeginChangeCheck(); int newSelectedSDKIndex = EditorGUILayout.Popup( new GUIContent(baseName, description), selectedSDKIndex, availableSDKGUIContents ); VRTK_SDKInfo newSelectedSDKInfo = selectedSDKInfo.originalTypeNameWhenFallbackIsUsed != null && newSelectedSDKIndex == availableSDKNames.Count - 1 ? selectedSDKInfo : availableSDKInfos[newSelectedSDKIndex]; if (EditorGUI.EndChangeCheck() && newSelectedSDKInfo != selectedSDKInfo) { Undo.RecordObject(sdkSetup, string.Format("{0} SDK Change", baseName)); sdkInfoPropertyInfo.GetSetMethod().Invoke(sdkSetup, new object[] { newSelectedSDKInfo }); } DrawVRDeviceNameLabel(selectedSDKInfo, baseName, indentLevelToRemove); } } private static void DrawVRDeviceNameLabel(VRTK_SDKInfo info, string sdkBaseName, int indentLevelToRemove) { float maxVRDeviceNameTextWidth = new[] { VRTK_SDKManager.AvailableSystemSDKInfos, VRTK_SDKManager.AvailableBoundariesSDKInfos, VRTK_SDKManager.AvailableHeadsetSDKInfos, VRTK_SDKManager.AvailableControllerSDKInfos } .SelectMany(infos => infos.Select(sdkInfo => sdkInfo.description.vrDeviceName)) .Concat(XRSettings.supportedDevices) .Concat(new[] { "None" }) .Distinct() .Select(deviceName => GUI.skin.label.CalcSize(new GUIContent(deviceName)).x) .Max(); EditorGUI.indentLevel -= indentLevelToRemove; EditorGUILayout.LabelField( new GUIContent(info.description.vrDeviceName, string.Format("The VR Device used by the {0} {1} SDK.", info.description.prettyName, sdkBaseName)), new GUIStyle(EditorStyles.centeredGreyMiniLabel) { alignment = TextAnchor.MiddleRight }, GUILayout.Width(maxVRDeviceNameTextWidth) ); EditorGUI.indentLevel += indentLevelToRemove; } private bool AreAllSDKsTheSame() { VRTK_SDKSetup setup = (VRTK_SDKSetup)target; VRTK_SDKInfo[] infos = { setup.systemSDKInfo, setup.boundariesSDKInfo, setup.headsetSDKInfo, setup.controllerSDKInfo, }; if (infos.Any(info => info.originalTypeNameWhenFallbackIsUsed != null)) { return false; } return infos.Select(info => info.description.prettyName) .Distinct() .Count() == 1; } private void UpdateDetailedSDKSelectionFoldOut() { isDetailedSDKSelectionFoldOut = !AreAllSDKsTheSame(); } private sealed class SDKSetupGameObjectsDisabler : AssetModificationProcessor { /* * Used to make sure the warning is actually seen in case the console is automatically cleared * because of 'Clear on Play' when playing the scene in the Editor. */ private const string PreferencesKey = "VRTK.SDKSetupGameObjectsDisabler.InfoMessage"; [InitializeOnLoadMethod] private static void ListenToPlayModeChanges() { #if UNITY_2017_2_OR_NEWER EditorApplication.playModeStateChanged += (PlayModeStateChange state) => #else EditorApplication.playmodeStateChanged += () => #endif { if (EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isPlaying) { FixOpenAndUnsavedScenes(); } if (EditorApplication.isPlaying #if UNITY_5_6_OR_NEWER && SessionState.GetBool(PreferencesKey, false) #else && EditorPrefs.HasKey(PreferencesKey) #endif ) { #if UNITY_5_6_OR_NEWER SessionState.EraseBool( #else EditorPrefs.DeleteKey( #endif PreferencesKey ); } }; } private static string[] OnWillSaveAssets(string[] paths) { FixOpenAndUnsavedScenes(); return paths; } private static void FixOpenAndUnsavedScenes() { List setups = Enumerable.Range(0, EditorSceneManager.loadedSceneCount) .SelectMany(sceneIndex => SceneManager.GetSceneAt(sceneIndex).GetRootGameObjects()) .SelectMany(rootObject => rootObject.GetComponentsInChildren()) .Select(manager => manager.setups.Where(setup => setup != null).ToArray()) .Where(sdkSetups => sdkSetups.Length > 1) .SelectMany(sdkSetups => sdkSetups) .Where(setup => setup.gameObject.activeSelf) .ToList(); if (setups.Count == 0) { return; } setups.ForEach(setup => setup.gameObject.SetActive(false)); string infoMessage = string.Format( "The following game objects have been set inactive to allow for SDK loading and switching using the SDK Setups on them:\n{0}", string.Join(", ", setups.Select(setup => setup.name).ToArray())); if (EditorApplication.isPlayingOrWillChangePlaymode) { #if UNITY_5_6_OR_NEWER SessionState.SetString( #else EditorPrefs.SetString( #endif PreferencesKey, infoMessage ); } else { VRTK_Logger.Info(infoMessage); } } } } }