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<VRTK_SDKInfo, ReadOnlyCollection<VRTK_SDKInfo>, 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<GUIContent> 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<VRTK_SDKInfo, bool> 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<SDK_BaseSystem>("The SDK to use to deal with all system actions.", 1);
|
|
DrawAndHandleSDKSelection<SDK_BaseBoundaries>("The SDK to use to utilize room scale boundaries.", 1);
|
|
DrawAndHandleSDKSelection<SDK_BaseHeadset>("The SDK to use to utilize the VR headset.", 1);
|
|
DrawAndHandleSDKSelection<SDK_BaseController>("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<VRTK_SDKSetup>("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<VRTK_SDKSetup>("actualBoundaries", "Boundaries")
|
|
);
|
|
EditorGUILayout.PropertyField(
|
|
serializedObject.FindProperty("actualHeadset"),
|
|
VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("actualHeadset", "Headset")
|
|
);
|
|
EditorGUILayout.PropertyField(
|
|
serializedObject.FindProperty("actualLeftController"),
|
|
VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("actualLeftController", "Left Controller")
|
|
);
|
|
EditorGUILayout.PropertyField(
|
|
serializedObject.FindProperty("actualRightController"),
|
|
VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("actualRightController", "Right Controller")
|
|
);
|
|
}
|
|
|
|
using (new EditorGUILayout.VerticalScope("Box"))
|
|
{
|
|
VRTK_EditorUtilities.AddHeader("Model Aliases", false);
|
|
EditorGUILayout.PropertyField(
|
|
serializedObject.FindProperty("modelAliasLeftController"),
|
|
VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("modelAliasLeftController", "Left Controller")
|
|
);
|
|
EditorGUILayout.PropertyField(
|
|
serializedObject.FindProperty("modelAliasRightController"),
|
|
VRTK_EditorUtilities.BuildGUIContent<VRTK_SDKSetup>("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<GameObject> 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
|
|
|
|
/// <summary>
|
|
/// Draws a popup menu and handles the selection for an SDK info.
|
|
/// </summary>
|
|
/// <typeparam name="BaseType">The SDK base type. Must be a subclass of <see cref="SDK_Base"/>.</typeparam>
|
|
/// <param name="description">The description of the SDK base.</param>
|
|
private void DrawAndHandleSDKSelection<BaseType>(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<VRTK_SDKInfo> availableSDKInfos = (ReadOnlyCollection<VRTK_SDKInfo>)sdkManagerType
|
|
.GetProperty(string.Format("Available{0}SDKInfos", baseName), BindingFlags.Public | BindingFlags.Static)
|
|
.GetGetMethod()
|
|
.Invoke(null, null);
|
|
ReadOnlyCollection<VRTK_SDKInfo> installedSDKInfos = (ReadOnlyCollection<VRTK_SDKInfo>)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<string> 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<VRTK_SDKSetup> setups = Enumerable.Range(0, EditorSceneManager.loadedSceneCount)
|
|
.SelectMany(sceneIndex => SceneManager.GetSceneAt(sceneIndex).GetRootGameObjects())
|
|
.SelectMany(rootObject => rootObject.GetComponentsInChildren<VRTK_SDKManager>())
|
|
.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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|