上传YomovSDK

This commit is contained in:
Sora丶kong
2026-03-03 03:15:46 +08:00
parent 9096da7e6c
commit eb97f31065
6477 changed files with 1932208 additions and 3 deletions

View File

@@ -0,0 +1,193 @@
using System.Collections.Generic;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
namespace UnityEditor.XR.OpenXR.Features
{
/// <summary>
/// The Boot Config builder exposes a centralized call-site for populating BootConfig options.
/// </summary>
public class BootConfigBuilder
{
private struct SettingEntry
{
public bool IsDirty;
public string Setting;
}
private readonly Dictionary<string, SettingEntry> _bootConfigSettings;
/// <summary>
/// Internal constructor. Should only ever get called inside this assembly. More to the point, it should only ever
/// get called inside <see cref="OpenXRFeatureBuildHooks"/>
/// </summary>
internal BootConfigBuilder()
{
_bootConfigSettings = new Dictionary<string, SettingEntry>();
}
/// <summary>
/// Populate the boot config settings from the current EditorUserBuildSettings based on the BuildReport.
/// If <see cref="SetBootConfigValue"/> or <see cref="SetBootConfigBoolean"/> have been called before this call, we do not overwrite
/// the value set, as we assume that these were meant to be the new, updated values.
/// </summary>
/// <param name="report">The BuildReport load the bootconfig from.</param>
internal void ReadBootConfig(BuildReport report)
{
var bootConfig = new BootConfig(report);
bootConfig.ReadBootConfig();
foreach (var setting in bootConfig.Settings)
{
// only update the boot config if the key doesn't currently live in _bootConfigSettings
// We may have updated _bootConfigSettings before we've called `ReadBootConfig`. If that is the case,
// this value overrides what's in the boot config.
if (!_bootConfigSettings.TryGetValue(setting.Key, out var entry))
_bootConfigSettings[setting.Key] = new SettingEntry { IsDirty = false, Setting = setting.Value };
}
}
/// <summary>
/// To ensure we don't have any lingering values carried over into the next build, we clear out the current
/// boot config as part of the post build step.
/// Any setting that we have added via a <see cref="SetBootConfigValue"/> or <see cref="SetBootConfigBoolean"/> will be cleaned up.
/// Any setting that was already in the boot config will not be removed.
/// </summary>
/// <param name="report"></param>
internal void ClearAndWriteBootConfig(BuildReport report)
{
var bootConfig = new BootConfig(report);
bootConfig.ReadBootConfig();
foreach (var entry in _bootConfigSettings)
{
if (entry.Value.IsDirty)
bootConfig.ClearEntryForKeyAndValue(entry.Key, entry.Value.Setting);
}
bootConfig.WriteBootConfig();
_bootConfigSettings.Clear();
}
/// <summary>
/// Write the current boot config.
/// Since we can override the <see cref="IPostprocessBuildWithReport.OnPostprocessBuild"/>, <see cref="IPreprocessBuildWithReport.OnPreprocessBuild"/> methods,
/// we cannot guarantee this method will get called, nor the order in which this method can be called. If you override these methods,
/// unless you call the base methods last, you'll want to invoke this method manually.
/// </summary>
/// <param name="report">Build report that we want to write</param>
internal void WriteBootConfig(BuildReport report)
{
// don't bother writing out if there's no boot config settings, or there isn't an OpenXR loader active.
if (_bootConfigSettings.Count <= 0)
return;
var bootConfig = new BootConfig(report);
bootConfig.ReadBootConfig();
foreach (var entry in _bootConfigSettings)
{
// We only want to clean up the entries that we've added in this build processor.
// Any other entries, we want to leave as is.
if (entry.Value.IsDirty)
bootConfig.SetValueForKey(entry.Key, entry.Value.Setting);
}
bootConfig.WriteBootConfig();
}
/// <summary>
/// Method for setting a specific boot config option, given the key and the string value to store.
/// </summary>
/// <param name="key">Key of the value to be stored</param>
/// <param name="value">String value to write to the key</param>
/// <returns>True if we are able to set the config value, otherwise returns false</returns>
public bool SetBootConfigValue(string key, string value)
{
if (string.IsNullOrEmpty(key))
{
Debug.LogError("Cannot write a boot config value with an empty key.");
return false;
}
_bootConfigSettings[key] = new SettingEntry { IsDirty = true, Setting = value };
return true;
}
/// <summary>
/// Method for setting a specific BOOLEAN config option. This method ensures a consistent method for writing a boolean value
/// </summary>
/// <param name="key">Key of the value to be stored</param>
/// <param name="value">Boolean value to set</param>
/// <returns>If the `key` existing in the boot config and it's "1", return true. Otherwise return false.</returns>
public bool SetBootConfigBoolean(string key, bool value)
{
if (string.IsNullOrEmpty(key))
{
Debug.LogError("Cannot write a boot config with an empty key");
return false;
}
_bootConfigSettings[key] = new SettingEntry { IsDirty = true, Setting = value ? "1" : "0" };
return true;
}
/// <summary>
/// Get a config value from the boot config, given a specific key.
/// </summary>
/// <param name="key">Key we want to locate in the boot config</param>
/// <param name="value">Where we store the result.</param>
/// <returns>true if we find the key in the bootconfig, otherwise we return false</returns>
public bool TryGetBootConfigValue(string key, out string value)
{
if (string.IsNullOrEmpty(key))
{
Debug.LogError("Cannot write a boot config with an empty key");
value = null;
return false;
}
bool result = _bootConfigSettings.TryGetValue(key, out var entry);
value = result ? entry.Setting : null;
return result;
}
/// <summary>
/// Return a boolean based on the value stored at `key`
/// </summary>
/// <param name="key">key to look for in the boot config</param>
/// <param name="value">Where we store the result.</param>
/// <returns>true if we find the key in the bootconfig, otherwise we return false</returns>
public bool TryGetBootConfigBoolean(string key, out bool value)
{
if (string.IsNullOrEmpty(key))
{
Debug.LogError("Cannot perform a look up with a null or empty string key");
value = false;
return false;
}
bool result = _bootConfigSettings.TryGetValue(key, out var entry);
value = result && entry.Setting.Equals("1");
return result;
}
/// <summary>
/// Try and remove an entry from the boot config.
/// </summary>
/// <param name="key">The key to attempt to remove</param>
/// <returns>true if we were able to remove the boot config entry, otherwise false</returns>
public bool TryRemoveBootConfigEntry(string key)
{
if (string.IsNullOrEmpty(key) || !_bootConfigSettings.ContainsKey(key))
{
return false;
}
return _bootConfigSettings.Remove(key);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 538109467cb24d89bc714edf64bef64b
timeCreated: 1680043148

View File

@@ -0,0 +1,71 @@
using UnityEditor;
using UnityEngine;
using UnityEngine.XR.OpenXR.Features.Interactions;
namespace UnityEngine.XR.OpenXR.Features.Interactions
{
[CustomEditor(typeof(DPadInteraction))]
internal class DPadInteractionCustomEditor : Editor
{
private SerializedProperty forceThresholdLeft;
private SerializedProperty forceThresholdReleaseLeft;
private SerializedProperty centerRegionLeft;
private SerializedProperty wedgeAngleLeft;
private SerializedProperty isStickyLeft;
private SerializedProperty forceThresholdRight;
private SerializedProperty forceThresholdReleaseRight;
private SerializedProperty centerRegionRight;
private SerializedProperty wedgeAngleRight;
private SerializedProperty isStickyRight;
static GUIContent s_ForceThresholdLabelLeft = EditorGUIUtility.TrTextContent("ForceThreshold");
static GUIContent s_ForceThresholdReleaseLabelLeft = EditorGUIUtility.TrTextContent("ForceThresholdRelease");
static GUIContent s_CenterRegionLeft = EditorGUIUtility.TrTextContent("centerRegion");
static GUIContent s_WedgeAngleLeft = EditorGUIUtility.TrTextContent("wedgeAngle");
static GUIContent s_IsStickyLeft = EditorGUIUtility.TrTextContent("isSticky");
static GUIContent s_ForceThresholdLabelRight = EditorGUIUtility.TrTextContent("ForceThreshold");
static GUIContent s_ForceThresholdReleaseLabelRight = EditorGUIUtility.TrTextContent("ForceThresholdRelease");
static GUIContent s_CenterRegionRight = EditorGUIUtility.TrTextContent("centerRegion");
static GUIContent s_WedgeAngleRight = EditorGUIUtility.TrTextContent("wedgeAngle");
static GUIContent s_IsStickyRight = EditorGUIUtility.TrTextContent("isSticky");
void OnEnable()
{
forceThresholdLeft = serializedObject.FindProperty("forceThresholdLeft");
forceThresholdReleaseLeft = serializedObject.FindProperty("forceThresholdReleaseLeft");
centerRegionLeft = serializedObject.FindProperty("centerRegionLeft");
wedgeAngleLeft = serializedObject.FindProperty("wedgeAngleLeft");
isStickyLeft = serializedObject.FindProperty("isStickyLeft");
forceThresholdRight = serializedObject.FindProperty("forceThresholdRight");
forceThresholdReleaseRight = serializedObject.FindProperty("forceThresholdReleaseRight");
centerRegionRight = serializedObject.FindProperty("centerRegionRight");
wedgeAngleRight = serializedObject.FindProperty("wedgeAngleRight");
isStickyRight = serializedObject.FindProperty("isStickyRight");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUILayout.LabelField("Dpad Bindings Custom Values For Left Controller:", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(forceThresholdLeft, s_ForceThresholdLabelLeft);
EditorGUILayout.PropertyField(forceThresholdReleaseLeft, s_ForceThresholdReleaseLabelLeft);
EditorGUILayout.PropertyField(centerRegionLeft, s_CenterRegionLeft);
EditorGUILayout.PropertyField(wedgeAngleLeft, s_WedgeAngleLeft);
EditorGUILayout.PropertyField(isStickyLeft, s_IsStickyLeft);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Dpad Bindings Custom Values For Right Controller:", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(forceThresholdRight, s_ForceThresholdLabelRight);
EditorGUILayout.PropertyField(forceThresholdReleaseRight, s_ForceThresholdReleaseLabelRight);
EditorGUILayout.PropertyField(centerRegionRight, s_CenterRegionRight);
EditorGUILayout.PropertyField(wedgeAngleRight, s_WedgeAngleRight);
EditorGUILayout.PropertyField(isStickyRight, s_IsStickyRight);
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 363c557b3b6241808ae890327f748be8
timeCreated: 1658880764

View File

@@ -0,0 +1,285 @@
using System;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using UnityEngine.XR.OpenXR.Features;
using UnityEngine;
using UnityEngine.XR.OpenXR;
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Tests.Editor")]
[assembly: InternalsVisibleTo("Unity.XR.OpenXR.Tests")]
namespace UnityEditor.XR.OpenXR.Features
{
/// <summary>
/// Editor OpenXR Feature helpers.
/// </summary>
public static class FeatureHelpers
{
/// <summary>
/// Discovers all features in project and ensures that OpenXRSettings.Instance.features is up to date
/// for selected build target group.
/// </summary>
/// <param name="group">build target group to refresh</param>
public static void RefreshFeatures(BuildTargetGroup group)
{
FeatureHelpersInternal.GetAllFeatureInfo(group);
}
/// <summary>
/// Given a feature id, returns the first instance of <see cref="OpenXRFeature" /> associated with that id.
/// </summary>
/// <param name="featureId">The unique id identifying the feature</param>
/// <returns>The instance of the feature matching thd id, or null.</returns>
public static OpenXRFeature GetFeatureWithIdForActiveBuildTarget(string featureId)
{
return GetFeatureWithIdForBuildTarget(BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget), featureId);
}
/// <summary>
/// Given an array of feature ids, returns an array of matching <see cref="OpenXRFeature" /> instances.
/// </summary>
/// <param name="featureIds">Array of feature ids to match against.</param>
/// <returns>An array of all matching features.</returns>
public static OpenXRFeature[] GetFeaturesWithIdsForActiveBuildTarget(string[] featureIds)
{
return GetFeaturesWithIdsForBuildTarget(BuildPipeline.GetBuildTargetGroup(UnityEditor.EditorUserBuildSettings.activeBuildTarget), featureIds);
}
/// <summary>
/// Given a feature id, returns the first <see cref="OpenXRFeature" /> associated with that id.
/// </summary>
/// <param name="buildTargetGroup">The build target group to get the feature from.</param>
/// <param name="featureId">The unique id identifying the feature</param>
/// <returns>The instance of the feature matching thd id, or null.</returns>
public static OpenXRFeature GetFeatureWithIdForBuildTarget(BuildTargetGroup buildTargetGroup, string featureId)
{
if (String.IsNullOrEmpty(featureId))
return null;
var settings = OpenXRSettings.GetSettingsForBuildTargetGroup(buildTargetGroup);
if (settings == null)
return null;
foreach (var feature in settings.features)
{
if (String.Compare(featureId, feature.featureIdInternal, true) == 0)
return feature;
}
return null;
}
/// <summary>
/// Given an array of feature ids, returns an array of matching <see cref="OpenXRFeature" /> instances that match.
/// </summary>
/// <param name="buildTargetGroup">The build target group to get the feature from.</param>
/// <param name="featureIds">Array of feature ids to match against.</param>
/// <returns>An array of all matching features.</returns>
public static OpenXRFeature[] GetFeaturesWithIdsForBuildTarget(BuildTargetGroup buildTargetGroup, string[] featureIds)
{
List<OpenXRFeature> ret = new List<OpenXRFeature>();
if (featureIds == null || featureIds.Length == 0)
return ret.ToArray();
foreach (var featureId in featureIds)
{
var feature = GetFeatureWithIdForBuildTarget(buildTargetGroup, featureId);
if (feature != null)
ret.Add(feature);
}
return ret.ToArray();
}
}
internal static class FeatureHelpersInternal
{
public class AllFeatureInfo
{
public List<FeatureInfo> Features;
public BuildTarget[] CustomLoaderBuildTargets;
}
public enum FeatureInfoCategory
{
Feature,
Interaction
}
public struct FeatureInfo
{
public string PluginPath;
public OpenXRFeatureAttribute Attribute;
public OpenXRFeature Feature;
public FeatureInfoCategory Category;
}
private static FeatureInfoCategory DetermineExtensionCategory(string extensionCategoryString)
{
if (String.Compare(extensionCategoryString, FeatureCategory.Interaction) == 0)
{
return FeatureInfoCategory.Interaction;
}
return FeatureInfoCategory.Feature;
}
/// <summary>
/// Gets all features for group. If serialized feature instances do not exist, creates them.
/// </summary>
/// <param name="group">BuildTargetGroup to get feature information for.</param>
/// <returns>feature info</returns>
public static AllFeatureInfo GetAllFeatureInfo(BuildTargetGroup group)
{
AllFeatureInfo ret = new AllFeatureInfo { Features = new List<FeatureInfo>() };
var openXrPackageSettings = OpenXRPackageSettings.GetOrCreateInstance();
var openXrSettings = openXrPackageSettings.GetSettingsForBuildTargetGroup(group);
if (openXrSettings == null)
{
return ret;
}
var assetPath = Path.Combine(OpenXRPackageSettings.GetAssetPathForComponents(OpenXRPackageSettings.s_PackageSettingsDefaultSettingsPath), openXrPackageSettings.name + ".asset");
var openXrExtensionAssets = AssetDatabase.LoadAllAssetsAtPath(assetPath);
// Find any current extensions that are already serialized
var currentExts = new Dictionary<OpenXRFeatureAttribute, OpenXRFeature>();
var buildGroupName = group.ToString();
foreach (var ext in openXrExtensionAssets)
{
if (ext == null || !ext.name.Contains(buildGroupName))
continue;
foreach (Attribute attr in Attribute.GetCustomAttributes(ext.GetType()))
{
if (attr is OpenXRFeatureAttribute)
{
var extAttr = (OpenXRFeatureAttribute)attr;
currentExts[extAttr] = (OpenXRFeature)ext;
break;
}
}
}
// only one custom loader is allowed per platform.
string customLoaderExtName = "";
// Find any extensions that haven't yet been added to the feature list and create instances of them
List<OpenXRFeature> all = new List<OpenXRFeature>();
foreach (var extType in TypeCache.GetTypesWithAttribute<OpenXRFeatureAttribute>())
{
foreach (Attribute attr in Attribute.GetCustomAttributes(extType))
{
if (attr is OpenXRFeatureAttribute)
{
var extAttr = (OpenXRFeatureAttribute)attr;
if (extAttr.BuildTargetGroups != null && !((IList)extAttr.BuildTargetGroups).Contains(group))
continue;
if (!currentExts.TryGetValue(extAttr, out var extObj))
{
// Create a new one
extObj = (OpenXRFeature)ScriptableObject.CreateInstance(extType);
extObj.name = extType.Name + " " + group;
AssetDatabase.AddObjectToAsset(extObj, openXrSettings);
AssetDatabase.SaveAssets();
}
else
{
extObj.name = extType.Name + " " + group;
}
if (extObj == null)
continue;
bool enabled = (extObj.enabled);
var ms = MonoScript.FromScriptableObject(extObj);
var path = AssetDatabase.GetAssetPath(ms);
var dir = "";
if (!String.IsNullOrEmpty(path))
dir = Path.GetDirectoryName(path);
ret.Features.Add(new FeatureInfo()
{
PluginPath = dir,
Attribute = extAttr,
Feature = extObj,
Category = DetermineExtensionCategory(extAttr.Category)
});
if (enabled && extAttr.CustomRuntimeLoaderBuildTargets?.Length > 0)
{
if (ret.CustomLoaderBuildTargets != null && (bool)extAttr.CustomRuntimeLoaderBuildTargets?.Intersect(ret.CustomLoaderBuildTargets).Any())
{
Debug.LogError($"Only one OpenXR feature may have a custom runtime loader per platform. Disable {customLoaderExtName} or {extAttr.UiName}.");
}
ret.CustomLoaderBuildTargets = extAttr.CustomRuntimeLoaderBuildTargets?.Union(ret?.CustomLoaderBuildTargets ?? new BuildTarget[] { }).ToArray();
customLoaderExtName = extAttr.UiName;
}
all.Add(extObj);
break;
}
}
}
// Update the feature list
var originalFeatures = openXrSettings.features;
var newFeatures = all
.Where(f => f != null)
.OrderByDescending(f => f.priority)
.ThenBy(f => f.nameUi)
.ToArray();
// Populate the internal feature variables for all features
bool fieldChanged = false;
foreach (var feature in newFeatures)
{
if (feature.internalFieldsUpdated)
continue;
feature.internalFieldsUpdated = true;
foreach (var attr in feature.GetType().GetCustomAttributes<OpenXRFeatureAttribute>())
{
foreach (var sourceField in attr.GetType().GetFields())
{
var copyField = sourceField.GetCustomAttribute<OpenXRFeatureAttribute.CopyFieldAttribute>();
if (copyField == null)
continue;
var targetField = feature.GetType().GetField(copyField.FieldName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.FlattenHierarchy);
if (targetField == null)
continue;
// Only set value if value is different
if ((targetField.GetValue(feature) == null && sourceField.GetValue(attr) != null) ||
targetField.GetValue(feature) == null || targetField.GetValue(feature).Equals(sourceField.GetValue(attr)) == false)
{
targetField.SetValue(feature, sourceField.GetValue(attr));
fieldChanged = true;
}
}
}
}
// Ensure the settings are saved after the features are populated
if (fieldChanged || originalFeatures == null || originalFeatures.SequenceEqual(newFeatures) == false)
{
openXrSettings.features = newFeatures;
#if UNITY_EDITOR
EditorUtility.SetDirty(openXrSettings);
#endif
}
return ret;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 492341990ff1a774f9aec136ed15b8d6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.XR.OpenXR;
using UnityEngine;
using UnityEngine.XR.OpenXR;
namespace UnityEditor.XR.OpenXR.Features
{
internal static class KnownFeatureSetsContent
{
internal static readonly string s_MicrosoftHoloLensFeatureSetId = "com.microsoft.openxr.featureset.hololens";
internal static readonly string s_MicrosoftHoloLensTitle = "Microsoft HoloLens";
internal static readonly string s_MicrosoftHoloLensInformationText = "Enable the full suite of features for Microsoft HoloLens 2.";
internal static readonly string s_MicrosoftDownloadText = "This package must be installed. Click this icon to visit the download page for this package.";
internal static readonly string s_MicrosoftDownloadLink = "http://aka.ms/openxr-unity-install";
}
internal static class KnownFeatureSets
{
internal static Dictionary<BuildTargetGroup, OpenXRFeatureSetManager.FeatureSet[]> k_KnownFeatureSets =
new Dictionary<BuildTargetGroup, OpenXRFeatureSetManager.FeatureSet[]>()
{
{
BuildTargetGroup.WSA,
new OpenXRFeatureSetManager.FeatureSet[]
{
new OpenXRFeatureSetManager.FeatureSet() {
isEnabled = false,
name = KnownFeatureSetsContent.s_MicrosoftHoloLensTitle,
featureSetId = KnownFeatureSetsContent.s_MicrosoftHoloLensFeatureSetId,
description = KnownFeatureSetsContent.s_MicrosoftHoloLensInformationText,
downloadText = KnownFeatureSetsContent.s_MicrosoftDownloadText,
downloadLink = KnownFeatureSetsContent.s_MicrosoftDownloadLink,
},
}
},
};
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f8eb84d75a4f7f64a86d587416e11ef1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,139 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine.XR.OpenXR;
namespace UnityEditor.XR.OpenXR.Features
{
internal class OpenXRChooseRuntimeLibraries : IPreprocessBuildWithReport
{
public int callbackOrder => 0;
public static string GetLoaderLibraryPath()
{
var extensions = FeatureHelpersInternal.GetAllFeatureInfo(BuildTargetGroup.Standalone);
// Loop over all the native plugin importers and find the custom loader
var importers = PluginImporter.GetAllImporters();
foreach (var importer in importers)
{
if (!importer.GetCompatibleWithEditor() || !importer.assetPath.Contains("openxr_loader"))
continue;
#if UNITY_EDITOR_WIN
if (!importer.GetCompatibleWithPlatform(BuildTarget.StandaloneWindows64) || !importer.assetPath.EndsWith(".dll"))
continue;
#elif UNITY_EDITOR_OSX
if (!importer.GetCompatibleWithPlatform(BuildTarget.StandaloneOSX) || !importer.assetPath.EndsWith(".dylib"))
continue;
#endif
bool importerPartOfExtension = false;
var root = Path.GetDirectoryName(importer.assetPath);
foreach (var extInfo in extensions.Features)
{
bool extensionContainsLoader = (root != null && root.Contains(extInfo.PluginPath));
importerPartOfExtension |= extensionContainsLoader;
bool customRuntimeLoaderOnEditorTarget = extInfo.Attribute.CustomRuntimeLoaderBuildTargets?.Intersect(
new[] { BuildTarget.StandaloneWindows64, BuildTarget.StandaloneOSX, BuildTarget.StandaloneLinux64 }).Any() ?? false;
if (extensionContainsLoader &&
customRuntimeLoaderOnEditorTarget &&
extInfo.Feature.enabled)
{
return AssetPathToAbsolutePath(importer.assetPath);
}
}
// return default loader
bool hasCustomLoader = extensions.CustomLoaderBuildTargets?.Length > 0;
if (!importerPartOfExtension && !hasCustomLoader)
return AssetPathToAbsolutePath(importer.assetPath);
}
return "";
}
private static string AssetPathToAbsolutePath(string assetPath)
{
var path = assetPath.Replace('/', Path.DirectorySeparatorChar);
if (assetPath.StartsWith("Packages"))
{
path = String.Join("" + Path.DirectorySeparatorChar, path.Split(Path.DirectorySeparatorChar).Skip(2));
return Path.Combine(PackageManager.PackageInfo.FindForAssetPath(assetPath).resolvedPath, path);
}
return path;
}
public void OnPreprocessBuild(BuildReport report)
{
var enabled = BuildHelperUtils.HasLoader(report.summary.platformGroup, typeof(OpenXRLoaderBase));
var extensions = FeatureHelpersInternal.GetAllFeatureInfo(report.summary.platformGroup);
// Keep set of seen plugins, only disable plugins that haven't been seen.
HashSet<string> seenPlugins = new HashSet<string>();
// Loop over all the native plugin importers and only include the enabled ones in the build
var importers = PluginImporter.GetAllImporters();
foreach (var importer in importers)
{
if (!importer.GetCompatibleWithPlatform(report.summary.platform))
continue;
bool loader = false;
if (importer.assetPath.Contains("openxr_loader"))
{
loader = true;
if (extensions.CustomLoaderBuildTargets?.Contains(report.summary.platform) ?? false)
importer.SetIncludeInBuildDelegate(path => false);
else
importer.SetIncludeInBuildDelegate(path => enabled);
}
if (importer.assetPath.Contains("UnityOpenXR"))
{
importer.SetIncludeInBuildDelegate(path => enabled);
}
var root = Path.GetDirectoryName(importer.assetPath);
foreach (var extInfo in extensions.Features)
{
if (root != null && root.Contains(extInfo.PluginPath))
{
if (extInfo.Feature.enabled &&
(!loader || (extInfo.Attribute.CustomRuntimeLoaderBuildTargets?.Contains(report.summary.platform) ?? false)))
{
importer.SetIncludeInBuildDelegate(path => enabled);
}
else if (!seenPlugins.Contains(importer.assetPath))
{
importer.SetIncludeInBuildDelegate(path => false);
}
seenPlugins.Add(importer.assetPath);
}
}
}
}
[InitializeOnLoadMethod]
static void InitializeOnLoad()
{
var importers = PluginImporter.GetAllImporters();
// fixes asset bundle building since IPreProcessBuildWithReport isn't called
foreach (var importer in importers)
{
if (importer.assetPath.Contains("openxr_loader"))
{
importer.SetIncludeInBuildDelegate(path => false);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 95e24fd5228ad9f4da756618cc40a329
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,157 @@
using System;
using System.Collections.Generic;
using UnityEditor.Android;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
#if XR_MGMT_4_4_0_OR_NEWER
using Unity.XR.Management.AndroidManifest.Editor;
#endif
namespace UnityEditor.XR.OpenXR.Features
{
/// <summary>
/// Inherit from this class to get callbacks to hook into the build process when your OpenXR Extension is enabled.
/// </summary>
#pragma warning disable 0618
public abstract class OpenXRFeatureBuildHooks : IPostGenerateGradleAndroidProject, IPostprocessBuildWithReport, IPreprocessBuildWithReport
#pragma warning restore 0618
#if XR_MGMT_4_4_0_OR_NEWER
, IAndroidManifestRequirementProvider
#endif
{
private OpenXRFeature _ext;
private readonly BootConfigBuilder _bootConfigBuilder = new BootConfigBuilder();
private bool IsExtensionEnabled(BuildTarget target, BuildTargetGroup group)
{
if (!BuildHelperUtils.HasLoader(group, typeof(OpenXRLoaderBase)))
return false;
OpenXRSettings buildTargetOpenXRSettings = OpenXRSettings.GetSettingsForBuildTargetGroup(BuildPipeline.GetBuildTargetGroup(target));
if (buildTargetOpenXRSettings == null || buildTargetOpenXRSettings.features == null)
{
UnityEngine.Debug.LogWarning($"Could not find valid OpenXR settings for build target {target}.");
return false;
}
if (_ext == null || _ext.GetType() != featureType)
{
foreach (var ext in buildTargetOpenXRSettings.features)
{
if (featureType == ext.GetType())
{
_ext = ext;
}
}
}
return _ext != null && _ext.enabled;
}
/// <summary>
/// Returns the current callback order for build processing.
/// </summary>
/// <value>Int value denoting the callback order.</value>
public abstract int callbackOrder { get; }
/// <summary>
/// Post process build step for checking if a feature is enabled. If so will call to the feature to run their build pre processing.
/// </summary>
/// <param name="report">Build report.</param>
public virtual void OnPreprocessBuild(BuildReport report)
{
if (!IsExtensionEnabled(report.summary.platform, report.summary.platformGroup))
return;
_bootConfigBuilder.ReadBootConfig(report);
OnProcessBootConfigExt(report, _bootConfigBuilder);
OnPreprocessBuildExt(report);
_bootConfigBuilder.WriteBootConfig(report);
}
/// <summary>
/// Post process build step for checking if a feature is enabled for android builds. If so will call to the feature to run their build post processing for android builds.
/// </summary>
/// <param name="path">Path to gradle project.</param>
public virtual void OnPostGenerateGradleAndroidProject(string path)
{
if (!IsExtensionEnabled(BuildTarget.Android, BuildTargetGroup.Android))
return;
OnPostGenerateGradleAndroidProjectExt(path);
}
/// <summary>
/// Post-process build step for any necessary clean-up. This will also call to the feature to run their build post processing.
/// </summary>
/// <param name="report">Build report.</param>
public virtual void OnPostprocessBuild(BuildReport report)
{
if (!IsExtensionEnabled(report.summary.platform, report.summary.platformGroup))
return;
OnPostprocessBuildExt(report);
_bootConfigBuilder.ClearAndWriteBootConfig(report);
}
/// <summary>
/// System.Type of the class that implements OpenXRFeature.
/// </summary>
public abstract Type featureType { get; }
/// <summary>
/// Called during the build process when the feature is enabled. Implement this function to receive a callback before the build starts.
/// </summary>
/// <param name="report">Report that contains information about the build, such as its target platform and output path.</param>
protected abstract void OnPreprocessBuildExt(BuildReport report);
/// <summary>
/// Called during build process when extension is enabled. Implement this function to receive a callback after the Android Gradle project is generated and before building begins. Function is not called for Internal builds.
/// </summary>
/// <param name="path">The path to the root of the Gradle project. Note: When exporting the project, this parameter holds the path to the folder specified for export.</param>
protected abstract void OnPostGenerateGradleAndroidProjectExt(string path);
/// <summary>
/// Called during the build process when extension is enabled. Implement this function to receive a callback after the build is complete.
/// </summary>
/// <param name="report">BuildReport that contains information about the build, such as the target platform and output path.</param>
protected abstract void OnPostprocessBuildExt(BuildReport report);
/// <summary>
/// Called during the build process when extension is enabled. Implement this function to add Boot Config Settings.
/// </summary>
/// <param name="report">BuildReport that contains information about the build, such as the target platform and output path.</param>
/// <param name="builder">This is the Boot Config interface tha can be used to write boot configs</param>
protected virtual void OnProcessBootConfigExt(BuildReport report, BootConfigBuilder builder)
{
}
#if XR_MGMT_4_4_0_OR_NEWER
/// <summary>
/// Post process build step for checking if the hooks' related feature is enabled for Android builds If so, the hook can safely provide its Android manifest requirements.
/// </summary>
public virtual ManifestRequirement ProvideManifestRequirement()
{
if (!IsExtensionEnabled(BuildTarget.Android, BuildTargetGroup.Android))
return null;
return ProvideManifestRequirementExt();
}
/// <summary>
/// Called during build process when collecting requirements for Android Manifest. Implement this function to add, override or remove Android manifest entries.
/// </summary>
protected virtual ManifestRequirement ProvideManifestRequirementExt()
{
return null;
}
#endif
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d5e18cf6f984a146a3b372338d4571c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,663 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEditorInternal;
namespace UnityEditor.XR.OpenXR.Features
{
enum IssueType
{
None,
Warning,
Error
}
internal class ChildListItem
{
public GUIContent uiName;
public GUIContent documentationIcon;
public GUIContent categoryName;
public GUIContent version;
public GUIContent partner;
public string partnerName;
public string documentationLink;
public bool settingsExpanded;
public OpenXRFeature feature;
public bool shouldDisplaySettings;
public UnityEditor.Editor settingsEditor;
public string featureId;
public IssueType issueType;
}
internal class OpenXRFeatureEditor
{
/// <summary>
/// Path of the OpenXR settings in the Settings window. Uses "/" as separator. The last token becomes the settings label if none is provided.
/// </summary>
public const string k_FeatureSettingsPathUI =
#if XR_MGMT_3_2_0_OR_NEWER
"Project/XR Plug-in Management/OpenXR/Features";
#else
"Project/XR Plugin Management/OpenXR/Features";
#endif
static class Styles
{
public static float k_IconWidth = 16f;
public static float k_DefaultSelectionWidth = 200f;
public static float k_DefualtLineMultiplier = 2f;
public static GUIStyle s_SelectionStyle = "TV Selection";
public static GUIStyle s_SelectionBackground = "ScrollViewAlt";
public static GUIStyle s_FeatureSetTitleLable;
public static GUIStyle s_ListLabel;
public static GUIStyle s_ListSelectedLabel;
public static GUIStyle s_ListLabelToggle;
public static GUIStyle s_Feature;
public static GUIStyle s_FeatureSettings;
}
static class Content
{
public static readonly GUIContent k_HelpIcon = EditorGUIUtility.IconContent("_Help");
public static readonly GUIContent k_SettingsIcon = EditorGUIUtility.IconContent("Settings");
public static readonly GUIContent k_Settings = new GUIContent("", k_SettingsIcon.image, "Open settings editor for this feature.");
public static readonly GUIContent k_InteractionProfilesTitle = new GUIContent("Enabled Interaction Profiles");
}
List<OpenXRFeatureSetManager.FeatureSetInfo> selectionListItems = new List<OpenXRFeatureSetManager.FeatureSetInfo>();
OpenXRFeatureSetManager.FeatureSetInfo selectedItem = null;
private List<IssueType> issuesPerFeatureSet = new List<IssueType>();
List<ChildListItem> filteredListItems = new List<ChildListItem>();
List<ChildListItem> allListItems = new List<ChildListItem>();
Dictionary<string, ChildListItem> interactionItems = new Dictionary<string, ChildListItem>();
List<string> selectedFeatureIds = new List<string>();
ReorderableList interactionFeaturesList = null;
HashSet<string> requiredFeatures = new HashSet<string>();
FeatureHelpersInternal.AllFeatureInfo allFeatureInfos = null;
BuildTargetGroup activeBuildTarget = BuildTargetGroup.Unknown;
List<OpenXRFeature.ValidationRule> _issues = new List<OpenXRFeature.ValidationRule>();
Dictionary<BuildTargetGroup, int> lastSelectedItemIndex = new Dictionary<BuildTargetGroup, int>();
OpenXRFeatureSettingsEditor featureSetSettingsEditor = null;
bool mustInitializeFeatures = false;
static readonly string s_AllFeatures = "All Features";
public OpenXRFeatureEditor()
{
SetupInteractionListUI();
}
(Rect, Rect) TakeFromFrontOfRect(Rect rect, float width)
{
var newRect = new Rect(rect);
newRect.x = rect.x + 5;
newRect.width = width;
rect.x = (newRect.x + newRect.width) + 1;
rect.width -= width + 1;
return (newRect, rect);
}
void SetupInteractionListUI()
{
if (interactionFeaturesList != null)
return;
interactionFeaturesList = new ReorderableList(selectedFeatureIds, typeof(ChildListItem), false, true, true, true);
interactionFeaturesList.drawHeaderCallback = (rect) =>
{
var labelSize = EditorStyles.label.CalcSize(Content.k_InteractionProfilesTitle);
var labelRect = new Rect(rect);
labelRect.width = labelSize.x;
EditorGUI.LabelField(labelRect, Content.k_InteractionProfilesTitle, EditorStyles.label);
};
interactionFeaturesList.drawElementCallback = (rect, index, isActive, isFocused) =>
{
Rect fieldRect;
string featureId = selectedFeatureIds[index];
var item = interactionItems[featureId];
var labelSize = EditorStyles.label.CalcSize(item.uiName);
(fieldRect, rect) = TakeFromFrontOfRect(rect, labelSize.x);
EditorGUI.BeginDisabledGroup(requiredFeatures.Contains(item.featureId));
EditorGUI.LabelField(fieldRect, item.uiName, EditorStyles.label);
EditorGUI.EndDisabledGroup();
if (!String.IsNullOrEmpty(item.documentationLink))
{
var size = EditorStyles.label.CalcSize(item.documentationIcon);
(fieldRect, rect) = TakeFromFrontOfRect(rect, size.x);
if (GUI.Button(fieldRect, item.documentationIcon, EditorStyles.label))
{
UnityEngine.Application.OpenURL(item.documentationLink);
}
}
if (item.issueType != IssueType.None)
{
GUIContent icon = (item.issueType == IssueType.Error) ? CommonContent.k_ValidationErrorIcon : CommonContent.k_ValidationWarningIcon;
var size = EditorStyles.label.CalcSize(icon);
(fieldRect, rect) = TakeFromFrontOfRect(rect, size.x);
if (GUI.Button(fieldRect, icon, EditorStyles.label))
{
OpenXRProjectValidationRulesSetup.ShowWindow(activeBuildTarget);
}
}
};
interactionFeaturesList.onAddDropdownCallback = (rect, list) =>
{
GenericMenu menu = new GenericMenu();
foreach (var kvp in interactionItems)
{
if (selectedFeatureIds.IndexOf(kvp.Key) == -1)
{
menu.AddItem(kvp.Value.uiName, false, (object obj) =>
{
string featureId = obj as string;
if (!String.IsNullOrEmpty(featureId))
{
selectedFeatureIds.Add(featureId);
var interactionItem = interactionItems[featureId];
interactionItem.feature.enabled = true;
}
}, kvp.Key);
}
}
menu.DropDown(rect);
};
interactionFeaturesList.onCanRemoveCallback = (list) =>
{
var featureId = selectedFeatureIds[list.index];
return !requiredFeatures.Contains(featureId);
};
interactionFeaturesList.onRemoveCallback = (list) =>
{
var featureId = selectedFeatureIds[list.index];
if (requiredFeatures.Contains(featureId))
return;
var interactionItem = interactionItems[featureId];
interactionItem.feature.enabled = false;
selectedFeatureIds.RemoveAt(list.index);
};
}
void UpdateValidationIssues(BuildTargetGroup buildTargetGroup)
{
_issues.Clear();
OpenXRProjectValidation.GetCurrentValidationIssues(_issues, buildTargetGroup);
foreach (var item in allListItems)
{
item.issueType = GetValidationIssueType(item.feature);
}
foreach (var item in interactionItems.Values)
{
item.issueType = GetValidationIssueType(item.feature);
}
issuesPerFeatureSet.Clear();
foreach (var featureSet in selectionListItems)
{
var featureSetIssue = IssueType.None;
foreach (var item in allListItems)
{
if (featureSet.featureIds == null)
break;
if (Array.IndexOf(featureSet.featureIds, item.featureId) == -1)
continue;
if (item.issueType == IssueType.Error)
{
featureSetIssue = IssueType.Error;
break;
}
if (item.issueType == IssueType.Warning)
{
featureSetIssue = IssueType.Warning;
}
}
issuesPerFeatureSet.Add(featureSetIssue);
}
}
IssueType GetValidationIssueType(OpenXRFeature feature)
{
IssueType ret = IssueType.None;
foreach (var issue in _issues)
{
if (feature == issue.feature)
{
if (issue.error)
{
ret = IssueType.Error;
break;
}
ret = IssueType.Warning;
}
}
return ret;
}
void DrawInteractionList()
{
EditorGUILayout.Space();
if (interactionItems.Count == 0)
return;
var iconSize = EditorGUIUtility.GetIconSize();
EditorGUILayout.BeginVertical();
EditorGUILayout.Space();
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
EditorGUIUtility.SetIconSize(new Vector2(30, 30));
GUILayout.Label(EditorGUIUtility.IconContent("console.infoicon"), new GUIStyle(EditorStyles.label));
EditorGUIUtility.SetIconSize(iconSize);
GUILayout.Label("Only enable interaction profiles that you actually test, to ensure their input bindings are complete. Otherwise, disable that interaction profile, to allow the OpenXR runtime to remap user input from a profile you do test.", EditorStyles.wordWrappedLabel);
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
interactionFeaturesList.DoLayoutList();
EditorGUILayout.EndVertical();
}
void OnSelectItem(OpenXRFeatureSetManager.FeatureSetInfo selectedItem)
{
this.selectedItem = selectedItem;
int selectedItemIndex = selectionListItems.IndexOf(selectedItem);
if (lastSelectedItemIndex.ContainsKey(activeBuildTarget))
lastSelectedItemIndex[activeBuildTarget] = selectedItemIndex;
else
lastSelectedItemIndex.Add(activeBuildTarget, selectedItemIndex);
if (this.selectedItem != null)
{
if (String.IsNullOrEmpty(selectedItem.featureSetId))
{
filteredListItems = allListItems.
OrderBy((item) => item.uiName.text).
ToList();
}
else
{
filteredListItems = allListItems.
Where((item) => Array.IndexOf(selectedItem.featureIds, item.featureId) > -1).
OrderBy((item) => item.uiName.text).
ToList();
}
}
}
void DrawSelectionList()
{
var skin = EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector);
var lineHeight = EditorGUIUtility.singleLineHeight * Styles.k_DefualtLineMultiplier;
EditorGUILayout.BeginVertical(GUILayout.Width(Styles.k_DefaultSelectionWidth), GUILayout.ExpandWidth(true));
{
EditorGUILayout.LabelField("OpenXR Feature Groups", Styles.s_FeatureSetTitleLable);
EditorGUILayout.BeginVertical(Styles.s_SelectionBackground, GUILayout.ExpandHeight(true));
{
int index = 0;
foreach (var item in selectionListItems)
{
var typeOfIssues = issuesPerFeatureSet[index++];
var selected = (item == this.selectedItem);
var style = selected ? Styles.s_ListSelectedLabel : Styles.s_ListLabel;
bool disabled = item.uiName.text != "All" && item.featureIds == null;
EditorGUILayout.BeginHorizontal(style, GUILayout.ExpandWidth(true));
{
EditorGUI.BeginDisabledGroup(disabled);
if (String.Compare(item.uiName.text, s_AllFeatures, true) != 0)
{
var currentToggleState = item.isEnabled;
var newToggleState = EditorGUILayout.ToggleLeft("", currentToggleState, GUILayout.ExpandWidth(false), GUILayout.Width(Styles.k_IconWidth), GUILayout.Height(lineHeight));
if (newToggleState != currentToggleState)
{
item.isEnabled = newToggleState;
OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets(activeBuildTarget);
}
}
if (GUILayout.Button(item.uiName, Styles.s_ListLabel, GUILayout.ExpandWidth(true), GUILayout.Height(lineHeight)))
{
OnSelectItem(item);
}
EditorGUI.EndDisabledGroup();
if (disabled && item.helpIcon != null)
{
if (GUILayout.Button(item.helpIcon, EditorStyles.label, GUILayout.Width(Styles.k_IconWidth), GUILayout.Height(lineHeight)))
{
UnityEngine.Application.OpenURL(item.downloadLink);
}
}
if (typeOfIssues != IssueType.None)
{
GUIContent icon = (typeOfIssues == IssueType.Error) ? CommonContent.k_ValidationErrorIcon : CommonContent.k_ValidationWarningIcon;
if (GUILayout.Button(icon, EditorStyles.label, GUILayout.Width(Styles.k_IconWidth), GUILayout.Height(lineHeight)))
{
OpenXRProjectValidationRulesSetup.ShowWindow(activeBuildTarget);
}
}
EditorGUILayout.EndHorizontal();
}
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndVertical();
}
}
void DrawFeatureList()
{
EditorGUILayout.BeginVertical();
{
EditorGUILayout.LabelField("", Styles.s_FeatureSetTitleLable);
foreach (var filteredListItem in filteredListItems)
{
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(false));
{
EditorGUILayout.BeginVertical(Styles.s_Feature, GUILayout.ExpandWidth(false));
{
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(false));
{
var typeOfIssue = filteredListItem.issueType;
var featureNameSize = EditorStyles.toggle.CalcSize(filteredListItem.uiName);
var oldEnabledState = filteredListItem.feature.enabled;
EditorGUI.BeginDisabledGroup(requiredFeatures.Contains(filteredListItem.featureId));
filteredListItem.feature.enabled = EditorGUILayout.ToggleLeft(filteredListItem.uiName, filteredListItem.feature.enabled, GUILayout.ExpandWidth(false), GUILayout.Width(featureNameSize.x));
EditorGUI.EndDisabledGroup();
if (!String.IsNullOrEmpty(filteredListItem.documentationLink))
{
if (GUILayout.Button(filteredListItem.documentationIcon, EditorStyles.label, GUILayout.Width(Styles.k_IconWidth)))
{
UnityEngine.Application.OpenURL(filteredListItem.documentationLink);
}
}
if (typeOfIssue != IssueType.None)
{
GUIContent icon = (typeOfIssue == IssueType.Error) ? CommonContent.k_ValidationErrorIcon : CommonContent.k_ValidationWarningIcon;
if (GUILayout.Button(icon, EditorStyles.label, GUILayout.Width(Styles.k_IconWidth)))
{
OpenXRProjectValidationRulesSetup.ShowWindow(activeBuildTarget);
}
}
if (filteredListItem.shouldDisplaySettings)
{
if (GUILayout.Button(Content.k_Settings, Styles.s_FeatureSettings, GUILayout.ExpandWidth(true)))
{
if (featureSetSettingsEditor == null)
{
if (EditorWindow.HasOpenInstances<OpenXRFeatureSettingsEditor>())
{
featureSetSettingsEditor = EditorWindow.GetWindow<OpenXRFeatureSettingsEditor>();
}
else
{
featureSetSettingsEditor = ScriptableObject.CreateInstance<OpenXRFeatureSettingsEditor>() as OpenXRFeatureSettingsEditor;
}
}
if (featureSetSettingsEditor != null)
{
featureSetSettingsEditor.ActiveItem = filteredListItem.featureId;
featureSetSettingsEditor.ActiveBuildTarget = activeBuildTarget;
featureSetSettingsEditor.ShowUtility();
featureSetSettingsEditor.Focus();
}
}
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.Space();
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndHorizontal();
}
}
EditorGUILayout.EndVertical();
}
}
void DrawFeatureSetUI()
{
EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
DrawSelectionList();
DrawFeatureList();
EditorGUILayout.EndHorizontal();
}
public void OnGUI(BuildTargetGroup buildTargetGroup)
{
InitStyles();
Vector2 iconSize = EditorGUIUtility.GetIconSize();
EditorGUIUtility.SetIconSize(new Vector2(Styles.k_IconWidth, Styles.k_IconWidth));
if (buildTargetGroup != activeBuildTarget || mustInitializeFeatures)
{
var allFeatureInfo = InitializeFeatures(buildTargetGroup);
OpenXRFeatureSetManager.activeBuildTarget = buildTargetGroup;
OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets(buildTargetGroup, allFeatureInfo);
// This must be done after SetFeaturesFromEnabledFeatureSets to ensure we dont get an infinite update loop
mustInitializeFeatures = false;
if (EditorWindow.HasOpenInstances<OpenXRFeatureSettingsEditor>())
{
featureSetSettingsEditor = EditorWindow.GetWindow<OpenXRFeatureSettingsEditor>();
}
if (featureSetSettingsEditor != null)
{
featureSetSettingsEditor.ActiveBuildTarget = activeBuildTarget;
}
}
if (allFeatureInfos != null)
{
UpdateValidationIssues(buildTargetGroup);
DrawInteractionList();
EditorGUILayout.Space();
DrawFeatureSetUI();
}
EditorGUIUtility.SetIconSize(iconSize);
}
bool HasSettingsToDisplay(OpenXRFeature feature)
{
FieldInfo[] fieldInfo = feature.GetType().GetFields(BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance);
foreach (var field in fieldInfo)
{
var nonSerializedAttrs = field.GetCustomAttributes(typeof(NonSerializedAttribute));
if (nonSerializedAttrs.Count() == 0)
return true;
}
fieldInfo = feature.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.DeclaredOnly | BindingFlags.Instance);
foreach (var field in fieldInfo)
{
var serializedAttrs = field.GetCustomAttributes(typeof(SerializeField));
if (serializedAttrs.Count() > 0)
return true;
}
return false;
}
FeatureHelpersInternal.AllFeatureInfo InitializeFeatures(BuildTargetGroup group)
{
selectionListItems.Clear();
filteredListItems.Clear();
allListItems.Clear();
interactionItems.Clear();
selectedFeatureIds.Clear();
requiredFeatures.Clear();
allFeatureInfos = FeatureHelpersInternal.GetAllFeatureInfo(group);
activeBuildTarget = group;
var featureSets = OpenXRFeatureSetManager.FeatureSetInfosForBuildTarget(group).
OrderBy((fs) => fs.uiName.text);
foreach (var featureSet in featureSets)
{
bool isKnownUninstalledFeatureSet = featureSet.featureIds == null && OpenXRFeatureSetManager.IsKnownFeatureSet(activeBuildTarget, featureSet.featureSetId);
var featureSetFeatures = allFeatureInfos.Features.
Where((f) =>
featureSet.featureIds != null && Array.IndexOf(featureSet.featureIds, f.Attribute.FeatureId) > -1);
if (isKnownUninstalledFeatureSet || featureSetFeatures.Any())
{
selectionListItems.Add(featureSet);
if (featureSet.isEnabled && featureSet.requiredFeatureIds != null)
requiredFeatures.UnionWith(featureSet.requiredFeatureIds);
}
}
foreach (var _ext in allFeatureInfos.Features)
{
if (_ext.Attribute.Hidden)
continue;
var listItem = new ChildListItem()
{
uiName = new GUIContent(_ext.Attribute.UiName),
documentationIcon = new GUIContent("", Content.k_HelpIcon.image, "Click for documentation"),
categoryName = new GUIContent($"Category: {_ext.Category.ToString()}"),
partner = new GUIContent($"Author: {_ext.Attribute.Company}"),
version = new GUIContent($"Version: {_ext.Attribute.Version}"),
partnerName = _ext.Attribute.Company,
documentationLink = _ext.Attribute.InternalDocumentationLink,
shouldDisplaySettings = HasSettingsToDisplay(_ext.Feature),
feature = _ext.Feature,
featureId = _ext.Attribute.FeatureId
};
if (_ext.Attribute.Category == UnityEditor.XR.OpenXR.Features.FeatureCategory.Interaction)
{
interactionItems.Add(listItem.featureId, listItem);
if (listItem.feature.enabled)
{
selectedFeatureIds.Add(listItem.featureId);
}
}
else
{
allListItems.Add(listItem);
}
}
selectionListItems.Add(new OpenXRFeatureSetManager.FeatureSetInfo()
{
uiName = new GUIContent(s_AllFeatures),
featureSetId = string.Empty,
featureIds = allFeatureInfos.Features.Select((e) => e.Attribute.FeatureId).ToArray(),
});
var initialSelectedItem = selectionListItems[selectionListItems.Count - 1];
if (lastSelectedItemIndex.ContainsKey(activeBuildTarget))
{
initialSelectedItem = selectionListItems[lastSelectedItemIndex[activeBuildTarget]];
}
OnSelectItem(initialSelectedItem);
return allFeatureInfos;
}
void InitStyles()
{
if (Styles.s_ListLabel == null)
{
Styles.s_FeatureSetTitleLable = new GUIStyle(EditorStyles.label);
Styles.s_FeatureSetTitleLable.fontSize = 14;
Styles.s_FeatureSetTitleLable.fontStyle = FontStyle.Bold;
Styles.s_ListLabel = new GUIStyle(EditorStyles.label);
Styles.s_ListLabel.border = new RectOffset(0, 0, 0, 0);
Styles.s_ListLabel.padding = new RectOffset(5, 0, 0, 0);
Styles.s_ListLabel.margin = new RectOffset(2, 2, 2, 2);
Styles.s_ListSelectedLabel = new GUIStyle(Styles.s_SelectionStyle);
Styles.s_ListSelectedLabel.border = Styles.s_ListLabel.border;
Styles.s_ListSelectedLabel.padding = Styles.s_ListLabel.padding;
Styles.s_ListSelectedLabel.margin = Styles.s_ListLabel.margin;
Styles.s_ListLabelToggle = new GUIStyle(EditorStyles.toggle);
Styles.s_ListLabelToggle.border = Styles.s_ListLabel.border;
Styles.s_ListLabelToggle.padding = Styles.s_ListLabel.padding;
Styles.s_ListLabelToggle.margin = Styles.s_ListLabel.margin;
Styles.s_FeatureSettings = new GUIStyle(Styles.s_SelectionStyle);
Styles.s_FeatureSettings.alignment = TextAnchor.MiddleRight;
Styles.s_FeatureSettings.border = new RectOffset(2, 2, 0, 0);
Styles.s_FeatureSettings.padding = new RectOffset(0, 2, 5, 0);
Styles.s_Feature = new GUIStyle(Styles.s_SelectionStyle);
Styles.s_Feature.border = new RectOffset(0, 0, 0, 0);
Styles.s_Feature.padding = new RectOffset(5, 0, 0, 0);
Styles.s_Feature.margin = new RectOffset(2, 2, 2, 2);
}
}
public static OpenXRFeatureEditor CreateFeatureEditor()
{
if (OpenXRSettings.Instance == null)
return null;
if (TypeCache.GetTypesWithAttribute<OpenXRFeatureAttribute>().Count > 0)
return new OpenXRFeatureEditor();
return null;
}
internal void OnFeatureSetStateChanged(BuildTargetGroup buildTargetGroup)
{
if (activeBuildTarget != buildTargetGroup)
return;
mustInitializeFeatures = true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1996830aac95a1546a907ea607836dd8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,50 @@
using System;
namespace UnityEditor.XR.OpenXR.Features
{
/// <summary>
/// Attribute used to describe a feature set to the OpenXR Editor components.
/// </summary>
[System.AttributeUsage(System.AttributeTargets.Class, Inherited = false, AllowMultiple = true)]
sealed public class OpenXRFeatureSetAttribute : System.Attribute
{
/// <summary>
/// The list of feature ids that this feature set can enable or disable.
/// </summary>
public string[] FeatureIds;
/// <summary>
/// The string used to represent the feature set in the UI.
/// </summary>
public string UiName;
/// <summary>
/// Description of the feature set.
/// </summary>
public string Description;
/// <summary>
/// The id used to uniquely define this feature set. It is recommended to use reverse DNS naming for this id.
/// </summary>
public string FeatureSetId;
/// <summary>
/// The list of build targets that this feature set supports.
/// </summary>
public BuildTargetGroup[] SupportedBuildTargets;
/// <summary>
/// The list of feature ids that this feature set requires. The features in this list will be enabled (and the UI will not allow them to be disabled) whenever the feature set itself is enabled.
///
/// Feature Ids are a subset of <see cref="FeatureIds"/>. Any feature id in this list and not also in <see cref="FeatureIds"/> will be ignored.
/// </summary>
public string[] RequiredFeatureIds;
/// <summary>
/// The list of feature ids that this feature set desires (but does not require). The features in this list will be enabled (but the UI will allow them to be disabled) whenever the feature set itself is enabled.
///
/// Feature Ids are a subset of <see cref="FeatureIds"/>. Any feature id in this list and not also in <see cref="FeatureIds"/> will be ignored.
/// </summary>
public string[] DefaultFeatureIds;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 697e81cea9b72024098b4297d2c31678
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,462 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.XR.OpenXR;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
namespace UnityEditor.XR.OpenXR.Features
{
/// <summary>
/// API for finding and managing feature sets for OpenXR.
/// </summary>
public static class OpenXRFeatureSetManager
{
[InitializeOnLoadMethod]
static void InitializeOnLoad()
{
void OnFirstUpdate()
{
EditorApplication.update -= OnFirstUpdate;
InitializeFeatureSets();
}
OpenXRFeature.canSetFeatureDisabled = CanFeatureBeDisabled;
EditorApplication.update += OnFirstUpdate;
}
/// <summary>
/// Description of a known (either built-in or found) feature set.
/// </summary>
public class FeatureSet
{
/// <summary>
/// Toggles the enabled state for this feature. Impacts the effect of <see cref="OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets"/>.
/// If you change this value, you must call <see cref="OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets"/> to reflect that change on the actual feature sets.
/// </summary>
public bool isEnabled;
/// <summary>
/// The name that displays in the UI.
/// </summary>
public string name;
/// <summary>
/// Description of this feature set.
/// </summary>
public string description;
/// <summary>
/// The feature set id as defined in <see cref="OpenXRFeatureSetAttribute.FeatureSetId"/>.
/// </summary>
public string featureSetId;
/// <summary>
/// The text to be shown with the <see cref="downloadLink" />.
/// </summary>
public string downloadText;
/// <summary>
/// The URI string used to link to external documentation.
/// </summary>
public string downloadLink;
/// <summary>
/// The set of features that this feature set menages.
/// </summary>
public string[] featureIds;
/// <summary>
/// State that tracks whether this feature set is built in or was detected after the user installed it.
/// </summary>
public bool isInstalled;
/// <summary>
/// The set of required features that this feature set manages.
/// </summary>
public string[] requiredFeatureIds;
/// <summary>
/// The set of default features that this feature set manages.
/// </summary>
public string[] defaultFeatureIds;
}
internal class FeatureSetInfo : FeatureSet
{
public GUIContent uiName;
public GUIContent uiLongName;
public GUIContent uiDescription;
public GUIContent helpIcon;
/// <summary>
/// Stores the previous known value of isEnabled as of the last call to `SetFeaturesFromEnabledFeatureSets`
/// </summary>
public bool wasEnabled;
}
static Dictionary<BuildTargetGroup, List<FeatureSetInfo>> s_AllFeatureSets = null;
struct FeatureSetState
{
public HashSet<string> featureSetFeatureIds;
public HashSet<string> requiredToEnabledFeatureIds;
public HashSet<string> requiredToDisabledFeatureIds;
public HashSet<string> defaultToEnabledFeatureIds;
}
static Dictionary<BuildTargetGroup, FeatureSetState> s_FeatureSetState = new Dictionary<BuildTargetGroup, FeatureSetState>();
/// <summary>
/// Event called when the feature set state has been changed.
/// </summary>
internal static event Action<BuildTargetGroup> onFeatureSetStateChanged;
/// <summary>
/// The current active build target. Used to handle callbacks from <see cref="OpenXRFeature.enabled" /> into
/// <see cref="CanFeatureBeDisabled" /> to determine if a feature can currently be disabled.
/// </summary>
public static BuildTargetGroup activeBuildTarget = BuildTargetGroup.Unknown;
static void FillKnownFeatureSets(bool addTestFeatureSet = false)
{
BuildTargetGroup[] buildTargetGroups = new BuildTargetGroup[] { BuildTargetGroup.Standalone, BuildTargetGroup.WSA, BuildTargetGroup.Android };
if (addTestFeatureSet)
{
foreach (var buildTargetGroup in buildTargetGroups)
{
List<FeatureSetInfo> knownFeatureSets = new List<FeatureSetInfo>();
if (addTestFeatureSet)
{
knownFeatureSets.Add(new FeatureSetInfo()
{
isEnabled = false,
name = "Known Test",
featureSetId = "com.unity.xr.test.featureset",
description = "Known Test feature group.",
downloadText = "Click here to go to the Unity main website.",
downloadLink = Constants.k_DocumentationURL,
uiName = new GUIContent("Known Test"),
uiDescription = new GUIContent("Known Test feature group."),
helpIcon = new GUIContent("", CommonContent.k_HelpIcon.image, "Click here to go to the Unity main website."),
});
}
s_AllFeatureSets.Add(buildTargetGroup, knownFeatureSets);
}
}
foreach (var kvp in KnownFeatureSets.k_KnownFeatureSets)
{
List<FeatureSetInfo> knownFeatureSets;
if (!s_AllFeatureSets.TryGetValue(kvp.Key, out knownFeatureSets))
{
knownFeatureSets = new List<FeatureSetInfo>();
foreach (var featureSet in kvp.Value)
{
knownFeatureSets.Add(new FeatureSetInfo()
{
isEnabled = false,
name = featureSet.name,
featureSetId = featureSet.featureSetId,
description = featureSet.description,
downloadText = featureSet.downloadText,
downloadLink = featureSet.downloadLink,
uiName = new GUIContent(featureSet.name),
uiLongName = new GUIContent($"{featureSet.name} feature group"),
uiDescription = new GUIContent(featureSet.description),
helpIcon = new GUIContent("", CommonContent.k_HelpIcon.image, featureSet.downloadText),
});
}
s_AllFeatureSets.Add(kvp.Key, knownFeatureSets);
}
}
}
/// <summary>
/// Initializes all currently known feature sets. This will do two initialization passes:
///
/// 1) Starts with all built in/known feature sets.
/// 2) Queries the system for anything with an <see cref="OpenXRFeatureSetAttribute"/>
/// defined on it and uses that to add/update the store of known feature sets.
/// </summary>
public static void InitializeFeatureSets()
{
InitializeFeatureSets(false);
}
internal static void InitializeFeatureSets(bool addTestFeatureSet)
{
if (s_AllFeatureSets == null)
s_AllFeatureSets = new Dictionary<BuildTargetGroup, List<FeatureSetInfo>>();
s_AllFeatureSets.Clear();
FillKnownFeatureSets(addTestFeatureSet);
var types = TypeCache.GetTypesWithAttribute<OpenXRFeatureSetAttribute>();
foreach (var t in types)
{
var attrs = Attribute.GetCustomAttributes(t);
foreach (var attr in attrs)
{
var featureSetAttr = attr as OpenXRFeatureSetAttribute;
if (featureSetAttr == null)
continue;
if (!addTestFeatureSet && featureSetAttr.FeatureSetId.Contains("com.unity.xr.test.featureset"))
continue;
foreach (var buildTargetGroup in featureSetAttr.SupportedBuildTargets)
{
var key = buildTargetGroup;
if (!s_AllFeatureSets.ContainsKey(key))
{
s_AllFeatureSets.Add(key, new List<FeatureSetInfo>());
}
var isEnabled = OpenXREditorSettings.Instance.IsFeatureSetSelected(buildTargetGroup, featureSetAttr.FeatureSetId);
var newFeatureSet = new FeatureSetInfo()
{
isEnabled = isEnabled,
wasEnabled = isEnabled,
name = featureSetAttr.UiName,
description = featureSetAttr.Description,
featureSetId = featureSetAttr.FeatureSetId,
downloadText = "",
downloadLink = "",
featureIds = featureSetAttr.FeatureIds,
requiredFeatureIds = featureSetAttr.RequiredFeatureIds,
defaultFeatureIds = featureSetAttr.DefaultFeatureIds,
isInstalled = true,
uiName = new GUIContent(featureSetAttr.UiName),
uiLongName = new GUIContent($"{featureSetAttr.UiName} feature group"),
uiDescription = new GUIContent(featureSetAttr.Description),
helpIcon = String.IsNullOrEmpty(featureSetAttr.Description) ? null : new GUIContent("", CommonContent.k_HelpIcon.image, featureSetAttr.Description),
};
bool foundFeatureSet = false;
var featureSets = s_AllFeatureSets[key];
for (int i = 0; i < featureSets.Count; i++)
{
if (String.Compare(featureSets[i].featureSetId, newFeatureSet.featureSetId, true) == 0)
{
foundFeatureSet = true;
featureSets[i] = newFeatureSet;
break;
}
}
if (!foundFeatureSet)
featureSets.Add(newFeatureSet);
}
}
}
var buildTargetGroups = Enum.GetValues(typeof(BuildTargetGroup));
foreach (BuildTargetGroup buildTargetGroup in buildTargetGroups)
{
FeatureSetState fsi;
if (!s_FeatureSetState.TryGetValue(buildTargetGroup, out fsi))
{
fsi = new FeatureSetState();
fsi.featureSetFeatureIds = new HashSet<string>();
fsi.requiredToEnabledFeatureIds = new HashSet<string>();
fsi.requiredToDisabledFeatureIds = new HashSet<string>();
fsi.defaultToEnabledFeatureIds = new HashSet<string>();
s_FeatureSetState.Add(buildTargetGroup, fsi);
}
SetFeaturesFromEnabledFeatureSets(buildTargetGroup);
}
}
/// <summary>
/// Returns the list of all <see cref="FeatureSet"/> for the given build target group.
/// </summary>
/// <param name="buildTargetGroup">The build target group to find the feature sets for.</param>
/// <returns>List of <see cref="FeatureSet"/> or null if there is nothing that matches the given input.</returns>
public static List<FeatureSet> FeatureSetsForBuildTarget(BuildTargetGroup buildTargetGroup)
{
return OpenXRFeatureSetManager.FeatureSetInfosForBuildTarget(buildTargetGroup).Select((fi) => fi as FeatureSet).ToList();
}
internal static List<FeatureSetInfo> FeatureSetInfosForBuildTarget(BuildTargetGroup buildTargetGroup)
{
List<FeatureSetInfo> ret = new List<FeatureSetInfo>();
HashSet<FeatureSetInfo> featureSetsForBuildTargetGroup = new HashSet<FeatureSetInfo>();
if (s_AllFeatureSets == null)
InitializeFeatureSets();
if (s_AllFeatureSets == null)
return ret;
foreach (var key in s_AllFeatureSets.Keys)
{
if (key == buildTargetGroup)
{
featureSetsForBuildTargetGroup.UnionWith(s_AllFeatureSets[key]);
}
}
ret.AddRange(featureSetsForBuildTargetGroup);
return ret;
}
/// <summary>
/// Returns a specific <see cref="FeatureSet"/> instance that matches the input.
/// </summary>
/// <param name="buildTargetGroup">The build target group this feature set supports.</param>
/// <param name="featureSetId">The feature set id for the specific feature set being requested.</param>
/// <returns>The matching <see cref="FeatureSet"/> or null.</returns>
public static FeatureSet GetFeatureSetWithId(BuildTargetGroup buildTargetGroup, string featureSetId)
{
return GetFeatureSetInfoWithId(buildTargetGroup, featureSetId) as FeatureSet;
}
internal static FeatureSetInfo GetFeatureSetInfoWithId(BuildTargetGroup buildTargetGroup, string featureSetId)
{
var featureSets = FeatureSetInfosForBuildTarget(buildTargetGroup);
if (featureSets != null)
{
foreach (var featureSet in featureSets)
{
if (String.Compare(featureSet.featureSetId, featureSetId, true) == 0)
return featureSet;
}
}
return null;
}
/// <summary>
/// Given the current enabled state of the feature sets that match for a build target group, enable and disable the features associated with
/// each feature set. Features that overlap sets of varying enabled states will maintain their enabled setting.
/// </summary>
/// <param name="buildTargetGroup">The build target group to process features sets for.</param>
public static void SetFeaturesFromEnabledFeatureSets(BuildTargetGroup buildTargetGroup)
{
var extInfo = FeatureHelpersInternal.GetAllFeatureInfo(buildTargetGroup);
SetFeaturesFromEnabledFeatureSets(buildTargetGroup, extInfo);
}
internal static void SetFeaturesFromEnabledFeatureSets(BuildTargetGroup buildTargetGroup, FeatureHelpersInternal.AllFeatureInfo extInfo)
{
var featureSets = FeatureSetInfosForBuildTarget(buildTargetGroup);
var fsi = s_FeatureSetState[buildTargetGroup];
fsi.featureSetFeatureIds.Clear();
foreach (var featureSet in featureSets)
{
if (featureSet.featureIds != null)
fsi.featureSetFeatureIds.UnionWith(featureSet.featureIds);
}
fsi.featureSetFeatureIds.Clear();
fsi.requiredToEnabledFeatureIds.Clear();
fsi.requiredToDisabledFeatureIds.Clear();
fsi.defaultToEnabledFeatureIds.Clear();
// Update the selected feature set states first
foreach (var featureSet in featureSets)
{
if (featureSet.featureIds == null)
continue;
OpenXREditorSettings.Instance.SetFeatureSetSelected(buildTargetGroup, featureSet.featureSetId, featureSet.isEnabled);
}
foreach (var featureSet in featureSets)
{
if (featureSet.featureIds == null)
continue;
if (featureSet.isEnabled && featureSet.requiredFeatureIds != null)
{
fsi.requiredToEnabledFeatureIds.UnionWith(featureSet.requiredFeatureIds);
}
if (featureSet.isEnabled != featureSet.wasEnabled)
{
if (featureSet.isEnabled && featureSet.defaultFeatureIds != null)
{
fsi.defaultToEnabledFeatureIds.UnionWith(featureSet.defaultFeatureIds);
}
else if (!featureSet.isEnabled && featureSet.requiredFeatureIds != null)
{
fsi.requiredToDisabledFeatureIds.UnionWith(featureSet.requiredFeatureIds);
}
featureSet.wasEnabled = featureSet.isEnabled;
}
}
foreach (var ext in extInfo.Features)
{
if (ext.Feature.enabled && fsi.requiredToDisabledFeatureIds.Contains(ext.Attribute.FeatureId))
ext.Feature.enabled = false;
if (!ext.Feature.enabled && fsi.requiredToEnabledFeatureIds.Contains(ext.Attribute.FeatureId))
ext.Feature.enabled = true;
if (!ext.Feature.enabled && fsi.defaultToEnabledFeatureIds.Contains(ext.Attribute.FeatureId))
ext.Feature.enabled = true;
}
s_FeatureSetState[buildTargetGroup] = fsi;
onFeatureSetStateChanged?.Invoke(buildTargetGroup);
}
/// <summary>
/// Tell the user if the feature with passed in feature id can be disabled.
///
/// Uses the currently set <see cref="activeBuildTarget" /> to determine
/// the BuildTargetGroup to use for checking against. If this value is not set, or
/// set for a different build target than expected then return value may be incorrect.
/// </summary>
/// <param name="featureId">The feature id of the feature to check.</param>
/// <returns>True if currently required by some feature set, false otherwise.</returns>
internal static bool CanFeatureBeDisabled(string featureId)
{
return CanFeatureBeDisabled(featureId, activeBuildTarget);
}
/// <summary>
/// Tell the user if the feature with passed in feature id can be disabled based on the build target group.
/// </summary>
/// <param name="featureId">The feature id of the feature to check.</param>
/// <param name="buildTargetGroup">The build target group whose feature sets you are checking against.</param>
/// <returns>True if currently required by some feature set, false otherwise.</returns>
public static bool CanFeatureBeDisabled(string featureId, BuildTargetGroup buildTargetGroup)
{
if (!s_FeatureSetState.ContainsKey(buildTargetGroup))
return true;
var fsi = s_FeatureSetState[buildTargetGroup];
return !fsi.requiredToEnabledFeatureIds.Contains(featureId);
}
internal static bool IsKnownFeatureSet(BuildTargetGroup buildTargetGroup, string featureSetId)
{
if (!KnownFeatureSets.k_KnownFeatureSets.ContainsKey(buildTargetGroup))
return false;
var featureSets = KnownFeatureSets.k_KnownFeatureSets[buildTargetGroup].
Where((fs) => fs.featureSetId == featureSetId);
return featureSets.Any();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 011454f4f10d7894fa63c7fcbdf46552
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,170 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
using UnityEditor;
namespace UnityEditor.XR.OpenXR.Features
{
internal class OpenXRFeatureSettingsEditor : EditorWindow
{
struct Styles
{
public static GUIStyle s_WrapTextLabel;
}
struct Content
{
public static GUIContent k_NoSelectionTitleContent = new GUIContent("OpenXR Feature Settings");
public static GUIContent k_NoSelectionHelpMsg = new GUIContent("There is no current feature selected for the this build target. Go to Player Settings->XR Plug-in Management->OpenXR to select a settings for a feature to edit.");
}
static void InitStyles()
{
if (Styles.s_WrapTextLabel == null)
{
Styles.s_WrapTextLabel = new GUIStyle(EditorStyles.label);
Styles.s_WrapTextLabel.wordWrap = true;
}
}
BuildTargetGroup activeBuildTarget;
string activeFeatureId = String.Empty;
Editor activeItemEditor = null;
OpenXRFeature featureToEdit = null;
OpenXRFeatureAttribute featureAttr = null;
GUIContent uiName;
GUIContent categoryName;
GUIContent partner;
GUIContent version;
string documentationLink;
bool itemDidChange = false;
internal BuildTargetGroup ActiveBuildTarget
{
get => activeBuildTarget;
set
{
if (value != activeBuildTarget)
{
activeBuildTarget = value;
itemDidChange = true;
Repaint();
}
}
}
internal string ActiveItem
{
get => activeFeatureId;
set
{
if (String.Compare(value, activeFeatureId, true) != 0)
{
activeFeatureId = value;
itemDidChange = true;
Repaint();
}
}
}
public void InitActiveItemInfo()
{
if (!itemDidChange)
return;
itemDidChange = false;
featureToEdit = null;
titleContent = Content.k_NoSelectionTitleContent;
activeItemEditor = null;
categoryName = null;
partner = null;
version = null;
documentationLink = null;
featureToEdit = FeatureHelpers.GetFeatureWithIdForBuildTarget(activeBuildTarget, activeFeatureId);
if (featureToEdit != null)
{
foreach (Attribute attr in Attribute.GetCustomAttributes(featureToEdit.GetType()))
{
if (attr is OpenXRFeatureAttribute)
{
featureAttr = (OpenXRFeatureAttribute)attr;
break;
}
}
if (featureAttr != null)
{
activeItemEditor = UnityEditor.Editor.CreateEditor(featureToEdit);
uiName = new GUIContent(featureAttr.UiName);
categoryName = new GUIContent($"Category: {featureAttr.Category.ToString()}");
partner = new GUIContent($"Author: {featureAttr.Company}");
version = new GUIContent($"Version: {featureAttr.Version}");
documentationLink = featureAttr.InternalDocumentationLink;
titleContent = uiName;
}
}
}
public void OnGUI()
{
InitStyles();
InitActiveItemInfo();
if (featureToEdit == null)
{
titleContent = Content.k_NoSelectionTitleContent;
EditorGUILayout.Space();
EditorGUILayout.BeginVertical();
EditorGUILayout.LabelField(Content.k_NoSelectionHelpMsg, Styles.s_WrapTextLabel);
EditorGUILayout.EndVertical();
return;
}
EditorGUILayout.Space();
EditorGUILayout.BeginVertical();
EditorGUILayout.LabelField("Feature Information", EditorStyles.boldLabel);
EditorGUILayout.Space();
EditorGUI.indentLevel += 1;
EditorGUILayout.LabelField(categoryName);
EditorGUILayout.LabelField(partner);
EditorGUILayout.LabelField(version);
EditorGUI.indentLevel -= 1;
EditorGUILayout.EndVertical();
if (activeItemEditor != null)
{
EditorGUILayout.BeginVertical();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Feature Settings", EditorStyles.boldLabel);
EditorGUILayout.Space();
EditorGUI.indentLevel += 1;
activeItemEditor.OnInspectorGUI();
EditorGUI.indentLevel -= 1;
EditorGUILayout.EndVertical();
}
EditorGUILayout.Space();
if (!String.IsNullOrEmpty(documentationLink))
{
if (GUILayout.Button("Documentation", EditorStyles.linkLabel))
{
UnityEngine.Application.OpenURL(documentationLink);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 15f4013cee477754ab28c0421e6c5dc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,20 @@
using System;
using UnityEditor;
using UnityEngine;
namespace UnityEditor.XR.OpenXR.Features
{
static class CommonContent
{
public static readonly GUIContent k_Download = new GUIContent("Download");
public static readonly GUIContent k_WarningIcon = EditorGUIUtility.IconContent("Warning@2x");
public static readonly GUIContent k_ErrorIcon = EditorGUIUtility.IconContent("Error@2x");
public static readonly GUIContent k_HelpIcon = EditorGUIUtility.IconContent("_Help@2x");
public static readonly GUIContent k_Validation = new GUIContent("Your project has some settings that are incompatible with OpenXR. Click to open the project validator.");
public static readonly GUIContent k_ValidationErrorIcon = new GUIContent("", CommonContent.k_ErrorIcon.image, k_Validation.text);
public static readonly GUIContent k_ValidationWarningIcon = new GUIContent("", CommonContent.k_WarningIcon.image, k_Validation.text);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58a54d90b4a70764b8711fcc8acc4a9e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,192 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.XR.Management;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.Features;
namespace UnityEditor.XR.OpenXR.Features
{
static class Content
{
public const float k_IconSize = 16.0f;
public static readonly GUIContent k_LoaderName = new GUIContent("OpenXR");
public static readonly GUIContent k_OpenXRHelp = new GUIContent("You may need to configure additional settings for OpenXR to enable features and interactions for different runtimes.");
public static readonly GUIContent k_OpenXRHelpIcon = new GUIContent("", CommonContent.k_HelpIcon.image, k_OpenXRHelp.text);
}
[XRCustomLoaderUI("UnityEngine.XR.OpenXR.OpenXRLoader", BuildTargetGroup.Standalone)]
[XRCustomLoaderUI("UnityEngine.XR.OpenXR.OpenXRLoader", BuildTargetGroup.Android)]
[XRCustomLoaderUI("UnityEngine.XR.OpenXR.OpenXRLoader", BuildTargetGroup.WSA)]
internal class OpenXRLoaderUI : IXRCustomLoaderUI
{
protected bool shouldApplyFeatureSetChanges = false;
protected List<OpenXRFeatureSetManager.FeatureSetInfo> featureSets { get; set; }
protected float renderLineHeight = 0;
private List<OpenXRFeature.ValidationRule> _validationRules = new List<OpenXRFeature.ValidationRule>();
/// <inheritdoc/>
public bool IsLoaderEnabled { get; set; }
public string[] IncompatibleLoaders => new string[]
{
"UnityEngine.XR.WindowsMR.WindowsMRLoader",
"Unity.XR.Oculus.OculusLoader",
};
/// <inheritdoc/>
public float RequiredRenderHeight { get; protected set; }
/// <inheritdoc/>
public virtual void SetRenderedLineHeight(float height)
{
renderLineHeight = height;
RequiredRenderHeight = height;
if (IsLoaderEnabled && featureSets != null)
{
RequiredRenderHeight += featureSets.Count * height;
}
}
BuildTargetGroup activeBuildTargetGroup;
/// <inheritdoc/>
public BuildTargetGroup ActiveBuildTargetGroup
{
get => activeBuildTargetGroup;
set
{
if (value != activeBuildTargetGroup)
{
activeBuildTargetGroup = value;
this.featureSets = OpenXRFeatureSetManager.FeatureSetInfosForBuildTarget(activeBuildTargetGroup);
foreach (var featureSet in this.featureSets)
{
featureSet.isEnabled = OpenXREditorSettings.Instance.IsFeatureSetSelected(activeBuildTargetGroup, featureSet.featureSetId);
}
}
}
}
protected Rect CalculateRectForContent(float xMin, float yMin, GUIStyle style, GUIContent content)
{
var size = style.CalcSize(content);
var rect = new Rect();
rect.xMin = xMin;
rect.yMin = yMin;
rect.width = size.x;
rect.height = renderLineHeight;
return rect;
}
void RenderFeatureSet(ref OpenXRFeatureSetManager.FeatureSetInfo featureSet, Rect rect)
{
float xMin = rect.xMin;
float yMin = rect.yMin;
var labelRect = CalculateRectForContent(xMin, yMin, EditorStyles.toggle, featureSet.uiLongName);
labelRect.width += renderLineHeight;
EditorGUI.BeginDisabledGroup(!featureSet.isInstalled);
var newToggled = EditorGUI.ToggleLeft(labelRect, featureSet.uiLongName, featureSet.isEnabled);
if (newToggled != featureSet.isEnabled)
{
featureSet.isEnabled = newToggled;
shouldApplyFeatureSetChanges = true;
}
EditorGUI.EndDisabledGroup();
xMin = labelRect.xMax + 1;
if (featureSet.helpIcon != null)
{
var iconRect = CalculateRectForContent(xMin, yMin, EditorStyles.label, CommonContent.k_HelpIcon);
if (GUI.Button(iconRect, featureSet.helpIcon, EditorStyles.label))
{
if (!String.IsNullOrEmpty(featureSet.downloadLink)) UnityEngine.Application.OpenURL(featureSet.downloadLink);
}
xMin = iconRect.xMax + 1;
}
}
/// <inheritdoc/>
public virtual void OnGUI(Rect rect)
{
// If this editor is rendering then the make sure the project validator is showing
// issues for the same build target.
OpenXRProjectValidationRulesSetup.SetSelectedBuildTargetGroup(activeBuildTargetGroup);
Vector2 oldIconSize = EditorGUIUtility.GetIconSize();
EditorGUIUtility.SetIconSize(new Vector2(Content.k_IconSize, Content.k_IconSize));
shouldApplyFeatureSetChanges = false;
float xMin = rect.xMin;
float yMin = rect.yMin;
var labelRect = CalculateRectForContent(xMin, yMin, EditorStyles.toggle, Content.k_LoaderName);
var newToggled = EditorGUI.ToggleLeft(labelRect, Content.k_LoaderName, IsLoaderEnabled);
if (newToggled != IsLoaderEnabled)
{
IsLoaderEnabled = newToggled;
}
xMin = labelRect.xMax + 1.0f;
if (IsLoaderEnabled)
{
var iconRect = CalculateRectForContent(xMin, yMin, EditorStyles.label, Content.k_OpenXRHelpIcon);
EditorGUI.LabelField(iconRect, Content.k_OpenXRHelpIcon);
xMin += Content.k_IconSize + 1.0f;
OpenXRProjectValidation.GetCurrentValidationIssues(_validationRules, activeBuildTargetGroup);
if (_validationRules.Count > 0)
{
bool anyErrors = _validationRules.Any(rule => rule.error);
GUIContent icon = anyErrors ? CommonContent.k_ValidationErrorIcon : CommonContent.k_ValidationWarningIcon;
iconRect = CalculateRectForContent(xMin, yMin, EditorStyles.label, icon);
if (GUI.Button(iconRect, icon, EditorStyles.label))
{
OpenXRProjectValidationRulesSetup.ShowWindow(activeBuildTargetGroup);
}
}
}
xMin = rect.xMin;
yMin += renderLineHeight;
Rect featureSetRect = new Rect(xMin, yMin, rect.width, renderLineHeight);
if (featureSets != null && featureSets.Count > 0 && IsLoaderEnabled)
{
EditorGUI.indentLevel++;
for (int i = 0; i < featureSets.Count; i++)
{
var featureSet = featureSets[i];
RenderFeatureSet(ref featureSet, featureSetRect);
featureSets[i] = featureSet;
yMin += renderLineHeight;
featureSetRect.yMin = yMin;
featureSetRect.height = renderLineHeight;
}
EditorGUI.indentLevel--;
}
if (shouldApplyFeatureSetChanges)
{
OpenXRFeatureSetManager.SetFeaturesFromEnabledFeatureSets(ActiveBuildTargetGroup);
shouldApplyFeatureSetChanges = false;
}
EditorGUIUtility.SetIconSize(oldIconSize);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5a8992ed9d345dc4b9e452a96b131ebd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: