コード例 #1
0
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            VRTK_SDKManager sdkManager = (VRTK_SDKManager)target;

            EditorGUILayout.PropertyField(serializedObject.FindProperty("persistOnLoad"));

            const string manageNowButtonText = "Manage Now";

            using (new EditorGUILayout.VerticalScope("Box"))
            {
                VRTK_EditorUtilities.AddHeader("Scripting Define Symbols", false);

                using (new EditorGUILayout.HorizontalScope())
                {
                    EditorGUI.BeginChangeCheck();
                    bool autoManage = EditorGUILayout.Toggle(
                        VRTK_EditorUtilities.BuildGUIContent <VRTK_SDKManager>("autoManageScriptDefines", "Auto Manage"),
                        sdkManager.autoManageScriptDefines,
                        GUILayout.ExpandWidth(false)
                        );
                    if (EditorGUI.EndChangeCheck())
                    {
                        serializedObject.FindProperty("autoManageScriptDefines").boolValue = autoManage;
                        serializedObject.ApplyModifiedProperties();
                        sdkManager.ManageScriptingDefineSymbols(false, true);
                    }

                    using (new EditorGUI.DisabledGroupScope(sdkManager.autoManageScriptDefines))
                    {
                        GUIContent manageNowGUIContent = new GUIContent(
                            manageNowButtonText,
                            "Manage the scripting define symbols defined by the installed SDKs."
                            + (sdkManager.autoManageScriptDefines
                                   ? "\n\nThis button is disabled because the SDK Manager is set up to manage the scripting define symbols automatically."
                               + " Disable the checkbox on the left to allow managing them manually instead."
                                   : "")
                            );
                        if (GUILayout.Button(manageNowGUIContent, GUILayout.MaxHeight(GUI.skin.label.CalcSize(manageNowGUIContent).y)))
                        {
                            sdkManager.ManageScriptingDefineSymbols(true, true);
                        }
                    }
                }

                using (new EditorGUILayout.VerticalScope("Box"))
                {
                    VRTK_EditorUtilities.AddHeader("Active Symbols Without SDK Classes", false);

                    VRTK_SDKInfo[] availableSDKInfos = VRTK_SDKManager
                                                       .AvailableSystemSDKInfos
                                                       .Concat(VRTK_SDKManager.AvailableBoundariesSDKInfos)
                                                       .Concat(VRTK_SDKManager.AvailableHeadsetSDKInfos)
                                                       .Concat(VRTK_SDKManager.AvailableControllerSDKInfos)
                                                       .ToArray();
                    HashSet <string> sdkSymbols = new HashSet <string>(availableSDKInfos.Select(info => info.description.symbol));
                    IGrouping <BuildTargetGroup, VRTK_SDKManager.ScriptingDefineSymbolPredicateInfo>[] availableGroupedPredicateInfos = VRTK_SDKManager
                                                                                                                                        .AvailableScriptingDefineSymbolPredicateInfos
                                                                                                                                        .GroupBy(info => info.attribute.buildTargetGroup)
                                                                                                                                        .ToArray();
                    foreach (IGrouping <BuildTargetGroup, VRTK_SDKManager.ScriptingDefineSymbolPredicateInfo> grouping in availableGroupedPredicateInfos)
                    {
                        VRTK_SDKManager.ScriptingDefineSymbolPredicateInfo[] possibleActiveInfos = grouping
                                                                                                   .Where(info => !sdkSymbols.Contains(info.attribute.symbol) &&
                                                                                                          grouping.Except(new[] { info })
                                                                                                          .All(predicateInfo => !(predicateInfo.methodInfo == info.methodInfo &&
                                                                                                                                  sdkSymbols.Contains(predicateInfo.attribute.symbol))))
                                                                                                   .OrderBy(info => info.attribute.symbol)
                                                                                                   .ToArray();
                        if (possibleActiveInfos.Length == 0)
                        {
                            continue;
                        }

                        EditorGUI.indentLevel++;

                        BuildTargetGroup targetGroup = grouping.Key;
                        isBuildTargetActiveSymbolsFoldOut[targetGroup] = EditorGUI.Foldout(
                            EditorGUILayout.GetControlRect(),
                            isBuildTargetActiveSymbolsFoldOut[targetGroup],
                            targetGroup.ToString(),
                            true
                            );

                        if (isBuildTargetActiveSymbolsFoldOut[targetGroup])
                        {
                            foreach (VRTK_SDKManager.ScriptingDefineSymbolPredicateInfo predicateInfo in possibleActiveInfos)
                            {
                                int symbolIndex = sdkManager
                                                  .activeScriptingDefineSymbolsWithoutSDKClasses
                                                  .FindIndex(attribute => attribute.symbol == predicateInfo.attribute.symbol);
                                string symbolLabel = predicateInfo.attribute.symbol.Remove(
                                    0,
                                    SDK_ScriptingDefineSymbolPredicateAttribute.RemovableSymbolPrefix.Length
                                    );

                                if (!(bool)predicateInfo.methodInfo.Invoke(null, null))
                                {
                                    symbolLabel += " (not installed)";
                                }

                                EditorGUI.BeginChangeCheck();
                                bool isSymbolActive = EditorGUILayout.ToggleLeft(symbolLabel, symbolIndex != -1);
                                if (EditorGUI.EndChangeCheck())
                                {
                                    Undo.RecordObject(sdkManager, "Active Symbol Change");
                                    if (isSymbolActive)
                                    {
                                        sdkManager.activeScriptingDefineSymbolsWithoutSDKClasses.Add(predicateInfo.attribute);
                                    }
                                    else
                                    {
                                        sdkManager.activeScriptingDefineSymbolsWithoutSDKClasses.RemoveAt(symbolIndex);
                                    }
                                    sdkManager.ManageScriptingDefineSymbols(false, true);
                                }
                            }
                        }

                        EditorGUI.indentLevel--;
                    }
                }

                VRTK_EditorUtilities.DrawUsingDestructiveStyle(GUI.skin.button, style =>
                {
                    GUIContent clearSymbolsGUIContent = new GUIContent(
                        "Remove All Symbols",
                        "Remove all scripting define symbols of VRTK. This is handy if you removed the SDK files from your project but still have"
                        + " the symbols defined which results in compile errors."
                        + "\nIf you have the above checkbox enabled the symbols will be managed automatically after clearing them. Otherwise hit the"
                        + " '" + manageNowButtonText + "' button to add the symbols for the currently installed SDKs again."
                        );

                    if (GUILayout.Button(clearSymbolsGUIContent, style))
                    {
                        BuildTargetGroup[] targetGroups = VRTK_SharedMethods.GetValidBuildTargetGroups();

                        foreach (BuildTargetGroup targetGroup in targetGroups)
                        {
                            string[] currentSymbols = PlayerSettings.GetScriptingDefineSymbolsForGroup(targetGroup)
                                                      .Split(';')
                                                      .Distinct()
                                                      .OrderBy(symbol => symbol, StringComparer.Ordinal)
                                                      .ToArray();
                            string[] newSymbols = currentSymbols
                                                  .Where(symbol => !symbol.StartsWith(SDK_ScriptingDefineSymbolPredicateAttribute.RemovableSymbolPrefix, StringComparison.Ordinal))
                                                  .ToArray();

                            if (currentSymbols.SequenceEqual(newSymbols))
                            {
                                continue;
                            }

                            PlayerSettings.SetScriptingDefineSymbolsForGroup(targetGroup, string.Join(";", newSymbols));

                            string[] removedSymbols = currentSymbols.Except(newSymbols).ToArray();
                            if (removedSymbols.Length > 0)
                            {
                                VRTK_Logger.Info(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.SCRIPTING_DEFINE_SYMBOLS_REMOVED, targetGroup, string.Join(", ", removedSymbols)));
                            }
                        }
                    }
                });
            }

            using (new EditorGUILayout.VerticalScope("Box"))
            {
                VRTK_EditorUtilities.AddHeader("Script Aliases", false);
                EditorGUILayout.PropertyField(
                    serializedObject.FindProperty("scriptAliasLeftController"),
                    VRTK_EditorUtilities.BuildGUIContent <VRTK_SDKManager>("scriptAliasLeftController", "Left Controller")
                    );
                EditorGUILayout.PropertyField(
                    serializedObject.FindProperty("scriptAliasRightController"),
                    VRTK_EditorUtilities.BuildGUIContent <VRTK_SDKManager>("scriptAliasRightController", "Right Controller")
                    );
            }

            using (new EditorGUILayout.VerticalScope("Box"))
            {
                VRTK_EditorUtilities.AddHeader("Setups", false);

                using (new EditorGUILayout.HorizontalScope())
                {
                    EditorGUI.BeginChangeCheck();
                    bool autoManage = EditorGUILayout.Toggle(
                        VRTK_EditorUtilities.BuildGUIContent <VRTK_SDKManager>("autoManageVRSettings"),
                        sdkManager.autoManageVRSettings,
                        GUILayout.ExpandWidth(false)
                        );
                    if (EditorGUI.EndChangeCheck())
                    {
                        serializedObject.FindProperty("autoManageVRSettings").boolValue = autoManage;
                        serializedObject.ApplyModifiedProperties();
                        sdkManager.ManageVRSettings(false);
                    }

                    using (new EditorGUI.DisabledGroupScope(sdkManager.autoManageVRSettings))
                    {
                        GUIContent manageNowGUIContent = new GUIContent(
                            manageNowButtonText,
                            "Manage the VR settings of the Player Settings to allow for all the installed SDKs."
                            + (sdkManager.autoManageVRSettings
                                   ? "\n\nThis button is disabled because the SDK Manager is set up to manage the VR Settings automatically."
                               + " Disable the checkbox on the left to allow managing them manually instead."
                                   : "")
                            );
                        if (GUILayout.Button(manageNowGUIContent, GUILayout.MaxHeight(GUI.skin.label.CalcSize(manageNowGUIContent).y)))
                        {
                            sdkManager.ManageVRSettings(true);
                        }
                    }
                }

                EditorGUILayout.PropertyField(
                    serializedObject.FindProperty("autoLoadSetup"),
                    VRTK_EditorUtilities.BuildGUIContent <VRTK_SDKManager>("autoLoadSetup", "Auto Load")
                    );

                setupsList.DoLayoutList();

                GUIContent autoPopulateGUIContent = new GUIContent("Auto Populate", "Automatically populates the list of SDK Setups with Setups in the scene.");
                if (GUILayout.Button(autoPopulateGUIContent))
                {
                    SerializedProperty serializedProperty = setupsList.serializedProperty;
                    serializedProperty.ClearArray();
                    VRTK_SDKSetup[] setups = sdkManager.GetComponentsInChildren <VRTK_SDKSetup>(true)
                                             .Concat(VRTK_SharedMethods.FindEvenInactiveComponents <VRTK_SDKSetup>())
                                             .Distinct()
                                             .ToArray();

                    for (int index = 0; index < setups.Length; index++)
                    {
                        VRTK_SDKSetup setup = setups[index];
                        serializedProperty.InsertArrayElementAtIndex(index);
                        serializedProperty.GetArrayElementAtIndex(index).objectReferenceValue = setup;
                    }
                }

                if (sdkManager.setups.Length > 1)
                {
                    EditorGUILayout.HelpBox("Duplicated setups are removed automatically.", MessageType.Info);
                }

                if (Enumerable.Range(0, sdkManager.setups.Length).Any(IsSDKSetupNeverUsed))
                {
                    EditorGUILayout.HelpBox("Gray setups will never be loaded because either the SDK Setup isn't valid or there"
                                            + " is a valid Setup before it that uses any non-VR SDK.",
                                            MessageType.Warning);
                }
            }

            serializedObject.ApplyModifiedProperties();
        }
コード例 #2
0
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            var adaptiveQuality = (VRTK_AdaptiveQuality)target;

            EditorGUILayout.HelpBox(DontDisableHelpBoxText, adaptiveQuality.enabled ? MessageType.Warning : MessageType.Error);
            EditorGUILayout.Space();

            EditorGUILayout.PropertyField(serializedObject.FindProperty("drawDebugVisualization"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("allowKeyboardShortcuts"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("allowCommandLineArguments"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("msaaLevel"));

            EditorGUILayout.Space();
            serializedObject.FindProperty("scaleRenderViewport").boolValue =
                EditorGUILayout.BeginToggleGroup(VRTK_EditorUtilities.BuildGUIContent <VRTK_AdaptiveQuality>("scaleRenderViewport"),
                                                 adaptiveQuality.scaleRenderViewport);
            {
                float minimumRenderScale = adaptiveQuality.minimumRenderScale;
                float maximumRenderScale = adaptiveQuality.maximumRenderScale;

                EditorGUILayout.BeginHorizontal();
                {
                    var fieldInfo      = adaptiveQuality.GetType().GetField("minimumRenderScale");
                    var rangeAttribute = (RangeAttribute)Attribute.GetCustomAttribute(fieldInfo, typeof(RangeAttribute));

                    EditorGUI.BeginChangeCheck();
                    {
                        float maxFloatWidth = GUI.skin.textField.CalcSize(new GUIContent(1.23f.ToString(CultureInfo.InvariantCulture))).x;
                        minimumRenderScale = EditorGUILayout.FloatField(minimumRenderScale, GUILayout.MaxWidth(maxFloatWidth));

                        EditorGUILayout.MinMaxSlider(
                            ref minimumRenderScale,
                            ref maximumRenderScale,
                            rangeAttribute.min,
                            rangeAttribute.max);

                        maximumRenderScale = EditorGUILayout.FloatField(maximumRenderScale, GUILayout.MaxWidth(maxFloatWidth));
                    }
                    if (EditorGUI.EndChangeCheck())
                    {
                        serializedObject.FindProperty("minimumRenderScale").floatValue =
                            Mathf.Clamp((float)Math.Round(minimumRenderScale, 2), rangeAttribute.min, rangeAttribute.max);
                        serializedObject.FindProperty("maximumRenderScale").floatValue =
                            Mathf.Clamp((float)Math.Round(maximumRenderScale, 2), rangeAttribute.min, rangeAttribute.max);
                    }
                }
                EditorGUILayout.EndHorizontal();

                if (maximumRenderScale > adaptiveQuality.BiggestAllowedMaximumRenderScale())
                {
                    EditorGUILayout.HelpBox(MaximumRenderScaleTooBigHelpBoxText, MessageType.Error);
                }

                EditorGUILayout.PropertyField(serializedObject.FindProperty("maximumRenderTargetDimension"));
                EditorGUILayout.PropertyField(serializedObject.FindProperty("renderScaleFillRateStepSizeInPercent"));

                serializedObject.FindProperty("scaleRenderTargetResolution").boolValue =
                    EditorGUILayout.Toggle(VRTK_EditorUtilities.BuildGUIContent <VRTK_AdaptiveQuality>("scaleRenderTargetResolution"),
                                           adaptiveQuality.scaleRenderTargetResolution);
                if (adaptiveQuality.scaleRenderTargetResolution)
                {
                    EditorGUILayout.HelpBox(ScaleRenderTargetResolutionCostlyHelpBoxText, MessageType.Warning);
                }

                int  maxRenderScaleLevel = Mathf.Max(adaptiveQuality.renderScales.Count - 1, 0);
                bool disabled            = maxRenderScaleLevel == 0 || !Application.isPlaying;

                EditorGUI.BeginDisabledGroup(disabled);
                {
                    VRTK_EditorUtilities.AddHeader <VRTK_AdaptiveQuality>("overrideRenderViewportScale");

                    if (disabled)
                    {
                        EditorGUI.EndDisabledGroup();
                        {
                            EditorGUILayout.HelpBox(NoRenderScaleLevelsYetHelpBoxText, MessageType.Info);
                        }
                        EditorGUI.BeginDisabledGroup(true);
                    }

                    adaptiveQuality.overrideRenderViewportScale = EditorGUILayout.Toggle(
                        VRTK_EditorUtilities.BuildGUIContent <VRTK_AdaptiveQuality>("overrideRenderViewportScale"),
                        adaptiveQuality.overrideRenderViewportScale);

                    EditorGUI.BeginDisabledGroup(!adaptiveQuality.overrideRenderViewportScale);
                    {
                        adaptiveQuality.overrideRenderViewportScaleLevel =
                            EditorGUILayout.IntSlider(
                                VRTK_EditorUtilities.BuildGUIContent <VRTK_AdaptiveQuality>("overrideRenderViewportScaleLevel"),
                                adaptiveQuality.overrideRenderViewportScaleLevel,
                                0,
                                maxRenderScaleLevel);
                    }
                    EditorGUI.EndDisabledGroup();
                }
                EditorGUI.EndDisabledGroup();
            }
            EditorGUILayout.EndToggleGroup();

            if (Application.isPlaying)
            {
                string summary = adaptiveQuality.ToString();
                summary = summary.Substring(summary.IndexOf("\n", StringComparison.Ordinal) + 1);

                VRTK_EditorUtilities.AddHeader("Current State");
                EditorGUILayout.HelpBox(summary, MessageType.None);

                if (GUILayout.RepeatButton("Refresh"))
                {
                    Repaint();
                }
            }

            serializedObject.ApplyModifiedProperties();
        }
コード例 #3
0
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            VRTK_AdaptiveQuality adaptiveQuality = (VRTK_AdaptiveQuality)target;

            EditorGUILayout.HelpBox(DontDisableHelpBoxText, adaptiveQuality.enabled ? MessageType.Warning : MessageType.Error);
            EditorGUILayout.Space();

            EditorGUILayout.PropertyField(serializedObject.FindProperty("drawDebugVisualization"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("allowKeyboardShortcuts"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("allowCommandLineArguments"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("msaaLevel"));

            EditorGUILayout.Space();
            serializedObject.FindProperty("scaleRenderViewport").boolValue =
                EditorGUILayout.BeginToggleGroup(VRTK_EditorUtilities.BuildGUIContent <VRTK_AdaptiveQuality>("scaleRenderViewport"),
                                                 adaptiveQuality.scaleRenderViewport);
            {
                Limits2D renderScaleLimits = adaptiveQuality.renderScaleLimits;
                EditorGUILayout.PropertyField(serializedObject.FindProperty("renderScaleLimits"));

                if (renderScaleLimits.maximum > adaptiveQuality.BiggestAllowedMaximumRenderScale())
                {
                    EditorGUILayout.HelpBox(MaximumRenderScaleTooBigHelpBoxText, MessageType.Error);
                }

                EditorGUILayout.PropertyField(serializedObject.FindProperty("maximumRenderTargetDimension"));
                EditorGUILayout.PropertyField(serializedObject.FindProperty("renderScaleFillRateStepSizeInPercent"));

                serializedObject.FindProperty("scaleRenderTargetResolution").boolValue =
                    EditorGUILayout.Toggle(VRTK_EditorUtilities.BuildGUIContent <VRTK_AdaptiveQuality>("scaleRenderTargetResolution"),
                                           adaptiveQuality.scaleRenderTargetResolution);
                if (adaptiveQuality.scaleRenderTargetResolution)
                {
                    EditorGUILayout.HelpBox(ScaleRenderTargetResolutionCostlyHelpBoxText, MessageType.Warning);
                }

                int  maxRenderScaleLevel = Mathf.Max(adaptiveQuality.renderScales.Count - 1, 0);
                bool disabled            = maxRenderScaleLevel == 0 || !Application.isPlaying;

                EditorGUI.BeginDisabledGroup(disabled);
                {
                    VRTK_EditorUtilities.AddHeader <VRTK_AdaptiveQuality>("overrideRenderViewportScale");

                    if (disabled)
                    {
                        EditorGUI.EndDisabledGroup();
                        {
                            EditorGUILayout.HelpBox(NoRenderScaleLevelsYetHelpBoxText, MessageType.Info);
                        }
                        EditorGUI.BeginDisabledGroup(true);
                    }

                    adaptiveQuality.overrideRenderViewportScale = EditorGUILayout.Toggle(
                        VRTK_EditorUtilities.BuildGUIContent <VRTK_AdaptiveQuality>("overrideRenderViewportScale"),
                        adaptiveQuality.overrideRenderViewportScale);

                    EditorGUI.BeginDisabledGroup(!adaptiveQuality.overrideRenderViewportScale);
                    {
                        adaptiveQuality.overrideRenderViewportScaleLevel =
                            EditorGUILayout.IntSlider(
                                VRTK_EditorUtilities.BuildGUIContent <VRTK_AdaptiveQuality>("overrideRenderViewportScaleLevel"),
                                adaptiveQuality.overrideRenderViewportScaleLevel,
                                0,
                                maxRenderScaleLevel);
                    }
                    EditorGUI.EndDisabledGroup();
                }
                EditorGUI.EndDisabledGroup();
            }
            EditorGUILayout.EndToggleGroup();

            if (Application.isPlaying)
            {
                string summary = adaptiveQuality.ToString();
                summary = summary.Substring(summary.IndexOf("\n", StringComparison.Ordinal) + 1);

                VRTK_EditorUtilities.AddHeader("Current State");
                EditorGUILayout.HelpBox(summary, MessageType.None);

                if (GUILayout.RepeatButton("Refresh"))
                {
                    Repaint();
                }
            }

            serializedObject.ApplyModifiedProperties();
        }
コード例 #4
0
        public override void OnInspectorGUI()
        {
            serializedObject.Update();

            var sdkManager = (VRTK_SDKManager)target;

            EditorGUILayout.PropertyField(serializedObject.FindProperty("persistOnLoad"));

            EditorGUILayout.BeginHorizontal();

            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(serializedObject.FindProperty("autoPopulateObjectReferences"), GUILayout.ExpandWidth(false));
            if (EditorGUI.EndChangeCheck())
            {
                serializedObject.ApplyModifiedProperties();
                sdkManager.PopulateObjectReferences(false);
            }

            const string populateNowDescription = "Populate Now";
            var          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(sdkManager, populateNowDescription);
                sdkManager.PopulateObjectReferences(true);
            }

            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginHorizontal();

            EditorGUI.BeginChangeCheck();
            EditorGUILayout.PropertyField(serializedObject.FindProperty("autoManageScriptDefines"), GUILayout.ExpandWidth(false));
            if (EditorGUI.EndChangeCheck())
            {
                serializedObject.ApplyModifiedProperties();
                sdkManager.ManageScriptingDefineSymbols(false, false);
            }

            EditorGUI.BeginDisabledGroup(sdkManager.autoManageScriptDefines);
            const string manageNowDescription = "Manage Now";
            var          manageNowGUIContent  = new GUIContent(
                manageNowDescription,
                "Manage the scripting define symbols defined by the selected SDKs."
                + (sdkManager.autoManageScriptDefines
                   ? "\n\nThis button is disabled because the SDK Manager is set up to manage the scripting define symbols automatically."
                   + " Disable the checkbox on the left to allow managing them manually instead."
                   : "")
                );

            if (GUILayout.Button(manageNowGUIContent, GUILayout.MaxHeight(GUI.skin.label.CalcSize(manageNowGUIContent).y)))
            {
                Undo.RecordObject(sdkManager, manageNowDescription);
                sdkManager.ManageScriptingDefineSymbols(true, true);
            }
            EditorGUI.EndDisabledGroup();

            EditorGUILayout.EndHorizontal();

            EditorGUILayout.BeginVertical("Box");
            VRTK_EditorUtilities.AddHeader("SDK Selection", false);

            HandleSDKSelection <SDK_BaseSystem>("The SDK to use to deal with all system actions.");
            HandleSDKSelection <SDK_BaseBoundaries>("The SDK to use to utilize room scale boundaries.");
            HandleSDKSelection <SDK_BaseHeadset>("The SDK to use to utilize the VR headset.");
            HandleSDKSelection <SDK_BaseController>("The SDK to use to utilize the input devices.");

            string sdkErrorDescriptions = string.Join("\n", sdkManager.GetSimplifiedSDKErrorDescriptions());

            if (!string.IsNullOrEmpty(sdkErrorDescriptions))
            {
                EditorGUILayout.HelpBox(sdkErrorDescriptions, MessageType.Error);
            }

            EditorGUILayout.Space();

            string[] availableSystemSDKNames     = VRTK_SDKManager.AvailableSystemSDKInfos.Select(info => info.description.prettyName + (VRTK_SDKManager.InstalledSystemSDKInfos.Contains(info) ? "" : SDKNotInstalledDescription)).ToArray();
            string[] availableBoundariesSDKNames = VRTK_SDKManager.AvailableBoundariesSDKInfos.Select(info => info.description.prettyName + (VRTK_SDKManager.InstalledBoundariesSDKInfos.Contains(info) ? "" : SDKNotInstalledDescription)).ToArray();
            string[] availableHeadsetSDKNames    = VRTK_SDKManager.AvailableHeadsetSDKInfos.Select(info => info.description.prettyName + (VRTK_SDKManager.InstalledHeadsetSDKInfos.Contains(info) ? "" : SDKNotInstalledDescription)).ToArray();
            string[] availableControllerSDKNames = VRTK_SDKManager.AvailableControllerSDKInfos.Select(info => info.description.prettyName + (VRTK_SDKManager.InstalledControllerSDKInfos.Contains(info) ? "" : SDKNotInstalledDescription)).ToArray();

            Func <string, GUIContent> guiContentCreator = sdkName => new GUIContent(sdkName);

            GUIContent[] availableSDKGUIContents = availableSystemSDKNames
                                                   .Intersect(availableBoundariesSDKNames)
                                                   .Intersect(availableHeadsetSDKNames)
                                                   .Intersect(availableControllerSDKNames)
                                                   .Select(guiContentCreator)
                                                   .ToArray();

            EditorGUI.BeginChangeCheck();
            int quicklySelectedSDKIndex = EditorGUILayout.Popup(new GUIContent("Quick select SDK", "Quickly select one of the SDKs into all slots."), 0, availableSDKGUIContents);

            if (EditorGUI.EndChangeCheck())
            {
                string quicklySelectedSDKName       = availableSDKGUIContents[quicklySelectedSDKIndex].text.Replace(SDKNotInstalledDescription, "");
                Func <VRTK_SDKInfo, bool> predicate = info => info.description.prettyName == quicklySelectedSDKName;

                Undo.RecordObject(sdkManager, "SDK Change (Quick Select)");
                sdkManager.systemSDKInfo     = VRTK_SDKManager.AvailableSystemSDKInfos.First(predicate);
                sdkManager.boundariesSDKInfo = VRTK_SDKManager.AvailableBoundariesSDKInfos.First(predicate);
                sdkManager.headsetSDKInfo    = VRTK_SDKManager.AvailableHeadsetSDKInfos.First(predicate);
                sdkManager.controllerSDKInfo = VRTK_SDKManager.AvailableControllerSDKInfos.First(predicate);
            }

            GUIContent[] availableSystemSDKGUIContents     = availableSystemSDKNames.Select(guiContentCreator).ToArray();
            GUIContent[] availableBoundariesSDKGUIContents = availableBoundariesSDKNames.Select(guiContentCreator).ToArray();
            GUIContent[] availableHeadsetSDKGUIContents    = availableHeadsetSDKNames.Select(guiContentCreator).ToArray();
            GUIContent[] availableControllerSDKGUIContents = availableControllerSDKNames.Select(guiContentCreator).ToArray();
            if (availableSDKGUIContents.Length != availableSystemSDKGUIContents.Length ||
                availableSDKGUIContents.Length != availableBoundariesSDKGUIContents.Length ||
                availableSDKGUIContents.Length != availableHeadsetSDKGUIContents.Length ||
                availableSDKGUIContents.Length != availableControllerSDKGUIContents.Length)
            {
                EditorGUILayout.HelpBox("Some of the available SDK implementations are only available for a subset of SDK endpoints. Quick Select only shows SDKs that provide an implementation for *all* the different SDK endpoints in VRTK (System, Boundaries, Headset, Controller).", MessageType.Info);
            }

            EditorGUILayout.EndVertical();

            EditorGUILayout.BeginVertical("Box");
            VRTK_EditorUtilities.AddHeader("Linked Objects", false);

            EditorGUILayout.PropertyField(serializedObject.FindProperty("actualBoundaries"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("actualHeadset"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("actualLeftController"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("actualRightController"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("modelAliasLeftController"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("modelAliasRightController"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("scriptAliasLeftController"));
            EditorGUILayout.PropertyField(serializedObject.FindProperty("scriptAliasRightController"));

            EditorGUILayout.EndVertical();

            serializedObject.ApplyModifiedProperties();
        }