Commit 29de0c28 authored by BlackAngle233's avatar BlackAngle233
Browse files

10.19 learned

parent 912976bb
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Editor
{
/// <summary>
/// Custom property drawer for <see cref="Microsoft.MixedReality.Toolkit.PrefabAttribute"/> decorated <see href="https://docs.unity3d.com/ScriptReference/GameObject.html">GameObject</see> values rendered in the inspector.
/// </summary>
[CustomPropertyDrawer(typeof(PrefabAttribute))]
public class PrefabPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var prefabAttribute = (PrefabAttribute)attribute;
if (prefabAttribute != null &&
property.propertyType == SerializedPropertyType.ObjectReference &&
(property.objectReferenceValue is GameObject || property.objectReferenceValue == null))
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property);
if (!EditorGUI.EndChangeCheck()) { return; }
if (property.objectReferenceValue == null) { return; }
if (PrefabUtility.GetPrefabAssetType(property.objectReferenceValue) == PrefabAssetType.NotAPrefab)
{
property.objectReferenceValue = null;
Debug.LogWarning("Assigned GameObject must be a prefab.");
}
}
else
{
EditorGUI.LabelField(position, label.text, "Use PrefabAttribute with GameObject fields only.");
}
}
}
}
\ No newline at end of file
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit
{
/// <summary>
/// Draws an object field as a scene asset reference.
/// This enables fields to store references to scene assets (which is an editor-only object) as unity objects (which work in both editor and runtime)
/// </summary>
[CustomPropertyDrawer(typeof(SceneAssetReferenceAttribute))]
public class SceneAssetReferenceAttributeDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, new GUIContent(property.name), property);
UnityEngine.Object newObject = EditorGUI.ObjectField(position, label, property.objectReferenceValue, typeof(SceneAsset), false);
if (property.objectReferenceValue != newObject)
{
property.objectReferenceValue = newObject;
EditorUtility.SetDirty(property.serializedObject.targetObject);
}
EditorGUI.EndProperty();
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.SceneSystem;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Editor
{
/// <summary>
/// Draws the scene info struct and populates its hidden fields.
/// </summary>
[CustomPropertyDrawer(typeof(SceneInfo))]
public class SceneInfoDrawer : PropertyDrawer
{
/// <summary>
/// Used to control whether to draw the tag property.
/// All scenes can have tags, but they're not always relevant based on how the scene is being used.
/// Not sure how much I like this method of controlling property drawing since it could result in unpredictable behavior in inspectors.
/// We could add an enum or bool to the SceneInfo struct to control this, but that seemed like unnecessary clutter.
/// </summary>
public static bool DrawTagProperty { get; set; }
const float iconWidth = 20f;
const float totalPropertyWidth = 410;
const float assetPropertyWidth = 400;
const float tagPropertyWidth = 400;
const float buttonPropertyWidth = 400;
const float assetLabelWidth = 150;
const float tagLabelWidth = 40;
const string enabledIconContent = "TestPassed";
const string missingIconContent = "TestIgnored";
const string disabledIconContent = "TestNormal";
const string warningIconContent = "TestInconclusive";
const string errorIconContent = "TestFailed";
static RectOffset boxOffset;
static GUIStyle italicStyle;
public static float GetPropertyHeight(bool drawTagProperty)
{
return (EditorGUIUtility.standardVerticalSpacing + EditorGUIUtility.singleLineHeight) * (drawTagProperty ? 4 : 3);
}
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return GetPropertyHeight(DrawTagProperty);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
DrawProperty(position, property, label);
}
public static void DrawProperty(Rect position, SerializedProperty property, GUIContent label, bool isActive = false, bool isSelected = false)
{
SerializedProperty assetProperty, nameProperty, pathProperty, buildIndexProperty, includedProperty, tagProperty;
SceneInfoUtils.GetSceneInfoRelativeProperties(property, out assetProperty, out nameProperty, out pathProperty, out buildIndexProperty, out includedProperty, out tagProperty);
// Set up our properties and settings
boxOffset = EditorStyles.helpBox.padding;
if (italicStyle == null) { italicStyle = new GUIStyle(EditorStyles.label); }
bool lastMode = EditorGUIUtility.wideMode;
int lastIndentLevel = EditorGUI.indentLevel;
EditorGUIUtility.wideMode = true;
EditorGUI.BeginProperty(position, label, property);
GUI.color = isActive ? Color.gray : GUI.backgroundColor;
GUI.color = isSelected ? Color.blue : GUI.backgroundColor;
// Indent our rect, then reset indent to 0 so sub-properties don't get doubly indented
position = EditorGUI.IndentedRect(position);
EditorGUI.indentLevel = 0;
// Draw a box around our item
Rect boxPosition = position;
boxPosition.height = (EditorGUIUtility.singleLineHeight * (DrawTagProperty ? 4 : 3)) - EditorGUIUtility.standardVerticalSpacing;
GUI.Box(boxPosition, GUIContent.none, EditorStyles.helpBox);
position = boxOffset.Remove(position);
Rect iconRect = position;
iconRect.width = iconWidth;
Rect assetRect = position;
assetRect.width = position.width - iconWidth;
assetRect.height = EditorGUIUtility.singleLineHeight;
assetRect.x += iconWidth;
Rect buttonRect = position;
buttonRect.height = EditorGUIUtility.singleLineHeight;
buttonRect.y += (EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing);
Rect tagRect = position;
tagRect.height = EditorGUIUtility.singleLineHeight;
tagRect.y += ((EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing) * 2) + EditorGUIUtility.standardVerticalSpacing;
bool changed = false;
UnityEngine.Object asset = assetProperty.objectReferenceValue;
if (!Application.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode && !EditorApplication.isCompiling)
{ // This is expensive so don't refresh during play mode or while other stuff is going on
changed = SceneInfoUtils.RefreshSceneInfo(asset, nameProperty, pathProperty, buildIndexProperty, includedProperty, tagProperty);
}
GUIContent labelContent = null;
GUIContent iconContent = null;
italicStyle.fontStyle = FontStyle.Normal;
bool buttonsDisabled = false;
bool tagDisabled = false;
if (asset == null)
{
string missingSceneName = nameProperty.stringValue;
if (!string.IsNullOrEmpty(missingSceneName))
{
labelContent = new GUIContent(" (" + missingSceneName + ")");
labelContent.tooltip = "The scene " + missingSceneName + " is missing. It will not be available to load.";
italicStyle.fontStyle = FontStyle.Italic;
tagDisabled = true;
iconContent = EditorGUIUtility.IconContent(missingIconContent);
}
else
{
labelContent = new GUIContent(" (Empty)");
labelContent.tooltip = "This scene is empty. You should assign a scene object before building.";
buttonsDisabled = true;
tagDisabled = true;
iconContent = EditorGUIUtility.IconContent(missingIconContent);
}
}
else
{
if (includedProperty.boolValue)
{
if (buildIndexProperty.intValue >= 0)
{
labelContent = new GUIContent(" Build index: " + buildIndexProperty.intValue);
labelContent.tooltip = "This scene is in build settings at index " + buildIndexProperty.intValue;
iconContent = EditorGUIUtility.IconContent(enabledIconContent);
}
else
{
labelContent = new GUIContent(" (Disabled)");
labelContent.tooltip = "This scene is in build settings at index " + buildIndexProperty.intValue + ", but it has been disabled and will not be available to load.";
iconContent = EditorGUIUtility.IconContent(disabledIconContent);
}
}
else
{
labelContent = new GUIContent(" (Not included in build)");
labelContent.tooltip = "This scene is not included in build settings and will not be available to load.";
iconContent = EditorGUIUtility.IconContent(errorIconContent);
}
}
// Draw our icon
EditorGUI.LabelField(iconRect, iconContent);
// Draw our object field
EditorGUI.BeginDisabledGroup(Application.isPlaying);
EditorGUIUtility.labelWidth = assetLabelWidth;
asset = EditorGUI.ObjectField(assetRect, labelContent, assetProperty.objectReferenceValue, typeof(SceneAsset), false);
EditorGUI.EndDisabledGroup();
if (DrawTagProperty)
{
// Draw our tag field
EditorGUI.BeginDisabledGroup(tagDisabled || Application.isPlaying);
EditorGUIUtility.labelWidth = tagLabelWidth;
changed |= EditorGUI.PropertyField(tagRect, tagProperty);
EditorGUI.EndDisabledGroup();
}
// Draw our button
EditorGUI.BeginDisabledGroup(buttonsDisabled || Application.isPlaying);
if (!string.IsNullOrEmpty(pathProperty.stringValue) && asset == null)
{
// The scene is missing
// This may be due to a local file ID mismatch
// Try to find it based on guid first
asset = AssetDatabase.LoadAssetAtPath<SceneAsset>(pathProperty.stringValue);
}
if (!string.IsNullOrEmpty(nameProperty.stringValue) && asset == null)
{
// If we still can't find it, draw a button that lets people attempt to recover it
if (GUI.Button(buttonRect, "Search for missing scene", EditorStyles.toolbarButton))
{
changed |= SceneInfoUtils.FindScene(nameProperty, pathProperty, ref asset);
}
}
else
{
// It's not included in build settings
if (!includedProperty.boolValue)
{
// The scene exists but it isn't in our build settings
// Show a button that lets us add it
if (GUI.Button(buttonRect, "Add to build settings", EditorStyles.toolbarButton) && asset != null)
{
List<EditorBuildSettingsScene> scenes = new List<EditorBuildSettingsScene>(EditorBuildSettings.scenes);
scenes.Add(new EditorBuildSettingsScene(pathProperty.stringValue, true));
includedProperty.boolValue = true;
EditorBuildSettings.scenes = scenes.ToArray();
SceneInfoUtils.RefreshCachedScenes();
changed = true;
}
}
else
{
bool enabledInBuild = buildIndexProperty.intValue >= 0;
// The scene exists and is in build settings
// Show enable / disable toggle
if (GUI.Button(buttonRect, enabledInBuild ? "Disable in build settings" : "Enable in build settings", EditorStyles.toolbarButton))
{
enabledInBuild = !enabledInBuild;
// Modify a local copy of our scenes instead of using the cached scenes
EditorBuildSettingsScene[] scenes = EditorBuildSettings.scenes;
// Find the scene in our build settings and enable / disable it
int sceneCount = 0;
int buildIndex = -1;
for (int i = 0; i < SceneInfoUtils.CachedScenes.Length; i++)
{
if (scenes[i].path == pathProperty.stringValue)
{
scenes[i].enabled = enabledInBuild;
if (scenes[i].enabled)
{ // Only store the build index if it's enabled
buildIndex = sceneCount;
}
break;
}
if (scenes[i].enabled)
{ // Disabled scenes don't count toward scene count
sceneCount++;
}
}
EditorBuildSettings.scenes = scenes;
SceneInfoUtils.RefreshCachedScenes();
buildIndexProperty.intValue = buildIndex;
changed = true;
}
}
}
EditorGUI.EndDisabledGroup();
if (asset != assetProperty.objectReferenceValue)
{
assetProperty.objectReferenceValue = asset;
changed = true;
}
if (changed)
{
property.serializedObject.ApplyModifiedProperties();
}
EditorGUIUtility.wideMode = lastMode;
EditorGUI.indentLevel = lastIndentLevel;
EditorGUI.EndProperty();
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.SceneSystem;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEditor.Callbacks;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Microsoft.MixedReality.Toolkit.Editor
{
/// <summary>
/// Class responsible for updating scene info structs to reflect changes made to scene assets.
/// Extends AssetPostprocessor so it can respond to asset changes.
/// </summary>
public class SceneInfoUtils : AssetPostprocessor, IProcessSceneWithReport
{
/// <summary>
/// Cached scenes used by SceneInfoDrawer to keep property drawer performant.
/// </summary>
public static EditorBuildSettingsScene[] CachedScenes { get; private set; } = Array.Empty<EditorBuildSettingsScene>();
public int callbackOrder => 0;
/// <summary>
/// The frame of the last update. Used to ensure we don't spam the system with updates.
/// </summary>
private static int frameScriptableObjectsLastUpdated;
private static int frameScenesLastUpdated;
private static List<Tuple<Type, FieldInfo>> cachedComponentTypes = new List<Tuple<Type, FieldInfo>>();
private static HashSet<Type> fieldTypesToSearch = new HashSet<Type>
{
typeof(SceneInfo),
typeof(SceneInfo[]),
typeof(List<SceneInfo>)
};
/// <summary>
/// Call this when you make a change to the build settings and need those changes to be reflected immediately.
/// </summary>
public static void RefreshCachedScenes()
{
CachedScenes = EditorBuildSettings.scenes;
}
/// <summary>
/// Finds all relative properties of a SceneInfo struct.
/// </summary>
public static void GetSceneInfoRelativeProperties(
SerializedProperty property,
out SerializedProperty assetProperty,
out SerializedProperty nameProperty,
out SerializedProperty pathProperty,
out SerializedProperty buildIndexProperty,
out SerializedProperty includedProperty,
out SerializedProperty tagProperty)
{
assetProperty = property.FindPropertyRelative("Asset");
nameProperty = property.FindPropertyRelative("Name");
pathProperty = property.FindPropertyRelative("Path");
buildIndexProperty = property.FindPropertyRelative("BuildIndex");
includedProperty = property.FindPropertyRelative("Included");
tagProperty = property.FindPropertyRelative("Tag");
}
/// <summary>
/// Finds a missing scene asset reference for a SceneInfo struct.
/// </summary>
/// <returns>True if scene was found.</returns>
public static bool FindScene(SerializedProperty nameProperty, SerializedProperty pathProperty, ref UnityEngine.Object asset)
{
// Attempt to load via the scene path
SceneAsset newSceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(pathProperty.stringValue);
if (newSceneAsset != null)
{
Debug.Log("Found missing scene at path " + pathProperty.stringValue);
asset = newSceneAsset;
return true;
}
else
{
// If we didn't find it this way, search for all scenes in the project and try a name match
foreach (string sceneGUID in AssetDatabase.FindAssets("t:Scene"))
{
string scenePath = AssetDatabase.GUIDToAssetPath(sceneGUID);
string sceneName = System.IO.Path.GetFileNameWithoutExtension(scenePath);
if (sceneName == nameProperty.stringValue)
{
pathProperty.stringValue = scenePath;
newSceneAsset = AssetDatabase.LoadAssetAtPath<SceneAsset>(scenePath);
if (newSceneAsset != null)
{
Debug.Log("Found missing scene at path " + scenePath);
asset = newSceneAsset;
return true;
}
}
}
}
return false;
}
/// <summary>
/// Implements IProcessSceneWithReport.OnProcessScene
/// </summary>
public void OnProcessScene(Scene scene, BuildReport report)
{
RefreshSceneInfoFieldsInScene(scene);
}
/// <summary>
/// Updates all the serialized properties for a SceneInfo struct.
/// </summary>
/// <returns>True if a property has changed.</returns>
public static bool RefreshSceneInfo(
UnityEngine.Object asset,
SerializedProperty nameProperty,
SerializedProperty pathProperty,
SerializedProperty buildIndexProperty,
SerializedProperty includedProperty,
SerializedProperty tagProperty)
{
bool changed = false;
if (asset == null)
{
// Leave the name and path alone, but reset the build index
if (buildIndexProperty.intValue >= 0)
{
buildIndexProperty.intValue = -1;
changed = true;
}
}
else
{
// Refreshing these values is very expensive
// Especially getting build scenes
// We may want to move this out of the property drawer
if (nameProperty.stringValue != asset.name)
{
nameProperty.stringValue = asset.name;
changed = true;
}
string scenePath = AssetDatabase.GetAssetPath(asset);
if (pathProperty.stringValue != scenePath)
{
pathProperty.stringValue = scenePath;
changed = true;
}
// The method is using scenes by path is not reliable (code included
// commented out here for reference).
// Cached scenes are used instead (see CachedScenes).
// Scene scene = EditorSceneManager.GetSceneByPath(scenePath);
// int buildIndex = scene.buildIndex;
int buildIndex = -1;
int sceneCount = 0;
bool included = false;
for (int i = 0; i < CachedScenes.Length; i++)
{
if (CachedScenes[i].path == scenePath)
{ // If it's in here it's included, even if it's not enabled
included = true;
if (CachedScenes[i].enabled)
{ // Only store the build index if it's enabled
buildIndex = sceneCount;
}
}
if (CachedScenes[i].enabled)
{ // Disabled scenes don't count toward scene count
sceneCount++;
}
}
if (buildIndex != buildIndexProperty.intValue)
{
buildIndexProperty.intValue = buildIndex;
changed = true;
}
if (included != includedProperty.boolValue)
{
includedProperty.boolValue = included;
changed = true;
}
}
if (string.IsNullOrEmpty(tagProperty.stringValue))
{
tagProperty.stringValue = "Untagged";
changed = true;
}
return changed;
}
/// <summary>
/// Searches for all components in a scene and refreshes any SceneInfo fields found.
/// </summary>
[PostProcessSceneAttribute]
public static void OnPostProcessScene()
{
RefreshSceneInfoFieldsInOpenScenes();
}
[InitializeOnLoadMethod]
private static void InitializeOnLoad()
{
EditorBuildSettings.sceneListChanged += SceneListChanged;
EditorSceneManager.sceneOpened += SceneOpened;
frameScriptableObjectsLastUpdated = -1;
frameScenesLastUpdated = -1;
RefreshCachedTypes();
RefreshCachedScenes();
RefreshSceneInfoFieldsInScriptableObjects();
RefreshSceneInfoFieldsInOpenScenes();
}
/// <summary>
/// Updates the cached component types which use SceneInfo fields.
/// </summary>
private static void RefreshCachedTypes()
{
if (EditorApplication.isCompiling || BuildPipeline.isBuildingPlayer)
{ // Don't refresh cached types if we're in the middle of something important
return;
}
cachedComponentTypes.Clear();
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (Type t in assembly.GetLoadableTypes().Where(t => t.IsSubclassOf(typeof(Component))))
{
foreach (FieldInfo f in t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (fieldTypesToSearch.Contains(f.FieldType))
{
cachedComponentTypes.Add(new Tuple<Type, FieldInfo>(t, f));
}
}
}
}
}
private static void SceneOpened(Scene scene, OpenSceneMode mode)
{
RefreshSceneInfoFieldsInOpenScenes();
}
/// <summary>
/// Updates the cached scene array when build settings change.
/// </summary>
private static void SceneListChanged()
{
RefreshCachedScenes();
RefreshSceneInfoFieldsInScriptableObjects();
RefreshSceneInfoFieldsInOpenScenes();
}
/// <summary>
/// Calls RefreshSceneInfoFieldsInScriptableObjects when an asset is modified.
/// </summary>
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths)
{
RefreshSceneInfoFieldsInScriptableObjects();
RefreshSceneInfoFieldsInOpenScenes();
}
/// <summary>
/// Searches through all ScriptableObject instances and refreshes any SceneInfo fields found.
/// </summary>
private static void RefreshSceneInfoFieldsInScriptableObjects()
{
if (Time.frameCount == frameScriptableObjectsLastUpdated && !BuildPipeline.isBuildingPlayer)
{ // Don't update more than once per frame unless we're building
return;
}
try
{
foreach (ScriptableObject source in ScriptableObjectExtensions.GetAllInstances<ScriptableObject>())
{
foreach (FieldInfo fieldInfo in source.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance))
{
if (fieldTypesToSearch.Contains(fieldInfo.FieldType))
{
CheckForChangesInField(source, fieldInfo);
}
}
}
frameScriptableObjectsLastUpdated = Time.frameCount;
}
catch (Exception)
{
Debug.LogWarning("Error when attempting to update scene info fields. Scene info data may be stale.");
}
}
private static void RefreshSceneInfoFieldsInOpenScenes()
{
if (Time.frameCount == frameScenesLastUpdated && !BuildPipeline.isBuildingPlayer)
{ // Don't update more than once per frame unless we're building
return;
}
try
{
foreach (Tuple<Type, FieldInfo> typeFieldInfoPair in cachedComponentTypes)
{
FieldInfo fieldInfo = typeFieldInfoPair.Item2;
foreach (Component source in GameObject.FindObjectsOfType(typeFieldInfoPair.Item1))
{
CheckForChangesInField(source, fieldInfo);
}
}
frameScenesLastUpdated = Time.frameCount;
}
catch (Exception)
{
Debug.LogWarning("Error when attempting to update scene info fields. Scene info data may be stale.");
}
}
private void RefreshSceneInfoFieldsInScene(Scene scene)
{
try
{
foreach (Tuple<Type, FieldInfo> typeFieldInfoPair in cachedComponentTypes)
{
FieldInfo fieldInfo = typeFieldInfoPair.Item2;
foreach (GameObject rootGameObject in scene.GetRootGameObjects())
{
foreach (Component source in rootGameObject.GetComponentsInChildren(typeFieldInfoPair.Item1))
{
CheckForChangesInField(source, fieldInfo);
}
}
}
}
catch (Exception)
{
Debug.LogWarning("Error when attempting to update scene info fields. Scene info data may be stale.");
}
}
private static void CheckForChangesInField(UnityEngine.Object source, FieldInfo fieldInfo)
{
if (fieldInfo.FieldType == typeof(SceneInfo))
{
SerializedObject serializedObject = new SerializedObject(source);
SerializedProperty property = serializedObject.FindProperty(fieldInfo.Name);
SerializedProperty assetProperty, nameProperty, pathProperty, buildIndexProperty, includedProperty, tagProperty;
GetSceneInfoRelativeProperties(property, out assetProperty, out nameProperty, out pathProperty, out buildIndexProperty, out includedProperty, out tagProperty);
if (RefreshSceneInfo(assetProperty.objectReferenceValue, nameProperty, pathProperty, buildIndexProperty, includedProperty, tagProperty))
{
if (BuildPipeline.isBuildingPlayer)
{
Debug.Log("Found out-of-date SceneInfo field '" + property.displayName + "' in asset '" + source.name + "' - The asset has been updated to: " + pathProperty.stringValue);
}
serializedObject.ApplyModifiedProperties();
}
}
else if (fieldInfo.FieldType == typeof(SceneInfo[]) || fieldInfo.FieldType == typeof(List<SceneInfo>))
{
SerializedObject serializedObject = new SerializedObject(source);
SerializedProperty arrayProperty = serializedObject.FindProperty(fieldInfo.Name);
for (int i = 0; i < arrayProperty.arraySize; i++)
{
SerializedProperty property = arrayProperty.GetArrayElementAtIndex(i);
SerializedProperty assetProperty, nameProperty, pathProperty, buildIndexProperty, includedProperty, tagProperty;
GetSceneInfoRelativeProperties(property, out assetProperty, out nameProperty, out pathProperty, out buildIndexProperty, out includedProperty, out tagProperty);
if (RefreshSceneInfo(assetProperty.objectReferenceValue, nameProperty, pathProperty, buildIndexProperty, includedProperty, tagProperty))
{
serializedObject.ApplyModifiedProperties();
// If we're building, log this change
if (BuildPipeline.isBuildingPlayer)
{
Debug.Log("Found out-of-date SceneInfo field '" + property.displayName + "' in asset '" + source.name + "' - The asset has been updated to: " + pathProperty.stringValue);
}
}
}
}
else
{
Debug.LogWarning("Attempted to refresh SceneInfo field for a type that isn't recognized: " + fieldInfo.FieldType);
}
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using UnityEngine;
using UnityEditor;
namespace Microsoft.MixedReality.Toolkit.Editor
{
/// <summary>
/// Creates a custom picker based on the list of scene in the build settings.
/// </summary>
/// <example>
/// <code>
/// [ScenePick]
/// public int SceneId;
/// </code>
/// </example>
[CustomPropertyDrawer(typeof(ScenePickAttribute))]
public class ScenePickPropertyDrawer : PropertyDrawer
{
/// <summary>
/// List of Options extracted from the Editor
/// </summary>
private static GUIContent[] Options;
/// <summary>
/// List of Scene GUIDS for the scenes
/// </summary>
private static string[] PropertyData;
/// <summary>
/// Select this option to remove the event string
/// </summary>
private static readonly string UnselectedText = "-- None --";
/// <summary>
/// Text to display when an entry is missing
/// </summary>
private static readonly string MissingText = "-- Missing --";
/// <summary>
/// Function called by unity to draw the GUI for this property
/// We are replacing the int value of the backing field with a dropdown list of scene names
/// </summary>
/// <param name="position">See base class</param>
/// <param name="property">See base class</param>
/// <param name="label">See base class</param>
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
BuildOptions();
var currentGuid = property.stringValue.Split(';')[0];
var currentId = System.Array.FindIndex(PropertyData, (x) => x.Contains(currentGuid));
if (currentId == -1)
{
// Not found, display the missing text
currentId = Options.Length - 1;
}
else if (currentId > 0 && property.stringValue != PropertyData[currentId])
{
// If the string has changed, update the property.
// This will happen if the scene is renamed.
property.stringValue = PropertyData[currentId];
EditorUtility.SetDirty(property.serializedObject.targetObject);
}
EditorGUI.BeginProperty(position, new GUIContent(property.name), property);
var newId = EditorGUI.Popup(position, label, currentId, Options);
if (newId != currentId)
{
property.stringValue = PropertyData[newId];
EditorUtility.SetDirty(property.serializedObject.targetObject);
}
EditorGUI.EndProperty();
}
/// <summary>
/// Build the list of scene names
/// Note: Scene 0 is the no-scene option.
/// </summary>
private static void BuildOptions()
{
var scenes = EditorBuildSettings.scenes;
if (scenes.Length > 0)
{
Options = new GUIContent[scenes.Length + 2];
PropertyData = new string[scenes.Length + 2];
Options[0] = new GUIContent(UnselectedText);
PropertyData[0] = string.Empty;
for (int i = 0; i < scenes.Length; i++)
{
// Right, replace '/' with '\' otherwise the list displays like a menu where '/' denotes a sub-menu.
Options[i + 1] = new GUIContent(scenes[i].path.Replace("/", "\\"));
PropertyData[i + 1] = scenes[i].guid.ToString() + ";" + scenes[i].path;
}
Options[scenes.Length + 1] = new GUIContent(MissingText);
PropertyData[scenes.Length + 1] = MissingText;
}
else
{
Options = new GUIContent[2];
PropertyData = new string[2];
Options[0] = new GUIContent(UnselectedText);
PropertyData[0] = string.Empty;
Options[1] = new GUIContent(MissingText);
PropertyData[1] = MissingText;
}
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Editor
{
public class SelectRepairedTypeWindow : EditorWindow
{
private static Type[] repairedTypeOptions;
private static SerializedProperty property;
private static SelectRepairedTypeWindow window;
public static bool WindowOpen { get { return window != null; } }
public static void Display(Type[] repairedTypeOptions, SerializedProperty property)
{
if (window != null)
{
window.Close();
}
SelectRepairedTypeWindow.repairedTypeOptions = repairedTypeOptions;
SelectRepairedTypeWindow.property = property;
window = ScriptableObject.CreateInstance(typeof(SelectRepairedTypeWindow)) as SelectRepairedTypeWindow;
window.titleContent = new GUIContent("Select repaired type");
window.ShowUtility();
}
private void OnGUI()
{
for (int i = 0; i < repairedTypeOptions.Length; i++)
{
if (GUILayout.Button(repairedTypeOptions[i].FullName, EditorStyles.miniButton))
{
property.stringValue = SystemType.GetReference(repairedTypeOptions[i]);
property.serializedObject.ApplyModifiedProperties();
Close();
}
}
}
private void OnDisable()
{
window = null;
}
private void OnInspectorUpdate()
{
Repaint();
}
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Input.Editor
{
[CustomPropertyDrawer(typeof(SpeechCommands))]
public class SpeechCommandPropertyDrawer : PropertyDrawer
{
public override void OnGUI(Rect rect, SerializedProperty property, GUIContent content)
{
EditorGUI.BeginProperty(rect, content, property);
// calculate field rectangle with half of total drawer length for each
var fieldWidth = rect.width * 0.5f;
var keywordRect = new Rect(rect.x, rect.y, fieldWidth, rect.height);
var keyCodeRect = new Rect(rect.x + fieldWidth, rect.y, fieldWidth, rect.height);
// the Keyword field without label
EditorGUI.PropertyField(keywordRect, property.FindPropertyRelative("keyword"), GUIContent.none);
// the KeyCode field without label
EditorGUI.PropertyField(keyCodeRect, property.FindPropertyRelative("keyCode"), GUIContent.none);
EditorGUI.EndProperty();
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using UnityEngine;
using UnityEditor;
namespace Microsoft.MixedReality.Toolkit.Editor
{
/// <summary>
/// Draws a Unity Tag selector in the Inspector.
/// </summary>
/// <example>
/// <code>
/// [TagProperty]
/// public string FindTag;
/// </code>
/// </example>
[CustomPropertyDrawer(typeof(TagPropertyAttribute))]
public class TagPropertyDrawer : PropertyDrawer
{
/// <summary>
/// Override this method to make your own GUI for the property.
/// </summary>
/// <param name="position">Rectangle on the screen to use for the property GUI.</param>
/// <param name="property">The SerializedProperty to make the custom GUI for.</param>
/// <param name="label">The label of this property.</param>
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, new GUIContent(property.name), property);
string tagValue = EditorGUI.TagField(position, label, property.stringValue);
if (tagValue != property.stringValue)
{
property.stringValue = tagValue;
EditorUtility.SetDirty(property.serializedObject.targetObject);
}
EditorGUI.EndProperty();
}
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using Assembly = System.Reflection.Assembly;
namespace Microsoft.MixedReality.Toolkit.Editor
{
/// <summary>
/// Custom property drawer for <see cref="Utilities.SystemType"/> properties.
/// </summary>
[CustomPropertyDrawer(typeof(SystemType))]
[CustomPropertyDrawer(typeof(SystemTypeAttribute), true)]
public class SystemTypeReferencePropertyDrawer : PropertyDrawer
{
private static int selectionControlId;
private static string selectedReference;
private static readonly Dictionary<string, Type> TypeMap = new Dictionary<string, Type>();
private static readonly int ControlHint = typeof(SystemTypeReferencePropertyDrawer).GetHashCode();
private static readonly GUIContent TempContent = new GUIContent();
private static readonly Color enabledColor = Color.white;
private static readonly Color disabledColor = Color.Lerp(Color.white, Color.clear, 0.5f);
private static readonly Color errorColor = Color.Lerp(Color.white, Color.red, 0.5f);
#region Type Filtering
/// <summary>
/// Gets or sets a function that returns a collection of types that are
/// to be excluded from drop-down. A value of <c>null</c> specifies that
/// no types are to be excluded.
/// </summary>
/// <remarks>
/// <para>This property must be set immediately before presenting a class
/// type reference property field using <see href="https://docs.unity3d.com/ScriptReference/EditorGUI.PropertyField.html">EditorGUI.PropertyField</see>
/// since the value of this property is reset to <c>null</c> each time the control is drawn.</para>
/// <para>Since filtering makes extensive use of <see cref="System.Collections.Generic.ICollection{Type}.Contains"/>
/// it is recommended to use a collection that is optimized for fast
/// look ups such as HashSet for better performance.</para>
/// </remarks>
/// <example>
/// <para>Exclude a specific type from being selected:</para>
/// <code language="csharp"><![CDATA[
/// private SerializedProperty someTypeReferenceProperty;
///
/// public override void OnInspectorGUI() {
/// serializedObject.Update();
///
/// ClassTypeReferencePropertyDrawer.ExcludedTypeCollectionGetter = GetExcludedTypeCollection;
/// EditorGUILayout.PropertyField(someTypeReferenceProperty);
///
/// serializedObject.ApplyModifiedProperties();
/// }
///
/// private ICollection<Type> GetExcludedTypeCollection() {
/// var set = new HashSet<Type>();
/// set.Add(typeof(SpecialClassToHideInDropdown));
/// return set;
/// }
/// ]]></code>
/// </example>
public static Func<ICollection<Type>> ExcludedTypeCollectionGetter { get; set; }
private static List<Type> GetFilteredTypes(SystemTypeAttribute filter)
{
var types = new List<Type>();
var excludedTypes = ExcludedTypeCollectionGetter?.Invoke();
// We prefer using this over CompilationPipeline.GetAssemblies() because
// some types may come from plugins and other sources that have already
// been compiled.
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
FilterTypes(assembly, filter, excludedTypes, types);
}
types.Sort((a, b) => string.Compare(a.FullName, b.FullName, StringComparison.Ordinal));
return types;
}
private static void FilterTypes(Assembly assembly, SystemTypeAttribute filter, ICollection<Type> excludedTypes, List<Type> output)
{
foreach (var type in assembly.GetLoadableTypes())
{
bool isValid = type.IsValueType && !type.IsEnum || type.IsClass;
if (!type.IsVisible || !isValid)
{
continue;
}
if (filter != null && !filter.IsConstraintSatisfied(type))
{
continue;
}
if (excludedTypes != null && excludedTypes.Contains(type))
{
continue;
}
output.Add(type);
}
}
#endregion Type Filtering
#region Type Utility
private static Type ResolveType(string classRef)
{
Type type;
if (!TypeMap.TryGetValue(classRef, out type))
{
type = !string.IsNullOrEmpty(classRef) ? Type.GetType(classRef) : null;
TypeMap[classRef] = type;
}
return type;
}
#endregion Type Utility
#region Control Drawing / Event Handling
private static string DrawTypeSelectionControl(Rect position, GUIContent label, string classRef, SystemTypeAttribute filter, bool typeResolved)
{
if (label != null && label != GUIContent.none)
{
position = EditorGUI.PrefixLabel(position, label);
}
int controlId = GUIUtility.GetControlID(ControlHint, FocusType.Keyboard, position);
bool triggerDropDown = false;
switch (Event.current.GetTypeForControl(controlId))
{
case EventType.ExecuteCommand:
if (Event.current.commandName == "TypeReferenceUpdated")
{
if (selectionControlId == controlId)
{
if (classRef != selectedReference)
{
classRef = selectedReference;
GUI.changed = true;
}
selectionControlId = 0;
selectedReference = null;
}
}
break;
case EventType.MouseDown:
if (GUI.enabled && position.Contains(Event.current.mousePosition))
{
GUIUtility.keyboardControl = controlId;
triggerDropDown = true;
Event.current.Use();
}
break;
case EventType.KeyDown:
if (GUI.enabled && GUIUtility.keyboardControl == controlId)
{
if (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.Space)
{
triggerDropDown = true;
Event.current.Use();
}
}
break;
case EventType.Repaint:
// Remove assembly name and namespace from content of pop-up control.
var classRefParts = classRef.Split(',');
var className = classRefParts[0].Trim();
className = className.Substring(className.LastIndexOf(".", StringComparison.Ordinal) + 1);
TempContent.text = className;
if (TempContent.text == string.Empty)
{
TempContent.text = "(None)";
}
else if (!typeResolved)
{
TempContent.text += " {Missing}";
}
EditorStyles.popup.Draw(position, TempContent, controlId);
break;
}
if (triggerDropDown)
{
selectionControlId = controlId;
selectedReference = classRef;
DisplayDropDown(position, GetFilteredTypes(filter), ResolveType(classRef), filter?.Grouping ?? TypeGrouping.ByNamespaceFlat);
}
return classRef;
}
private static void DrawTypeSelectionControl(Rect position, SerializedProperty property, GUIContent label, SystemTypeAttribute filter)
{
try
{
Color restoreColor = GUI.color;
bool restoreShowMixedValue = EditorGUI.showMixedValue;
bool typeResolved = string.IsNullOrEmpty(property.stringValue) || ResolveType(property.stringValue) != null;
EditorGUI.showMixedValue = property.hasMultipleDifferentValues;
GUI.color = enabledColor;
if (typeResolved)
{
property.stringValue = DrawTypeSelectionControl(position, label, property.stringValue, filter, true);
}
else
{
if (SelectRepairedTypeWindow.WindowOpen)
{
GUI.color = disabledColor;
DrawTypeSelectionControl(position, label, property.stringValue, filter, false);
}
else
{
Rect dropdownPosition = new Rect(position.x, position.y, position.width - 90, position.height);
Rect buttonPosition = new Rect(position.x + position.width - 75, position.y, 75, position.height);
Color defaultColor = GUI.color;
GUI.color = errorColor;
property.stringValue = DrawTypeSelectionControl(dropdownPosition, label, property.stringValue, filter, false);
GUI.color = defaultColor;
if (GUI.Button(buttonPosition, "Try Repair", EditorStyles.miniButton))
{
string typeNameWithoutAssembly = property.stringValue.Split(new string[] { "," }, StringSplitOptions.None)[0];
string typeNameWithoutNamespace = System.Text.RegularExpressions.Regex.Replace(typeNameWithoutAssembly, @"[.\w]+\.(\w+)", "$1");
Type[] repairedTypeOptions = FindTypesByName(typeNameWithoutNamespace, filter);
if (repairedTypeOptions.Length > 1)
{
SelectRepairedTypeWindow.Display(repairedTypeOptions, property);
}
else if (repairedTypeOptions.Length > 0)
{
property.stringValue = SystemType.GetReference(repairedTypeOptions[0]);
}
else
{
EditorUtility.DisplayDialog("No types found", "No types with the name '" + typeNameWithoutNamespace + "' were found.", "OK");
}
}
}
}
GUI.color = restoreColor;
EditorGUI.showMixedValue = restoreShowMixedValue;
}
finally
{
ExcludedTypeCollectionGetter = null;
}
}
private static Type[] FindTypesByName(string typeName, SystemTypeAttribute filter)
{
List<Type> types = new List<Type>();
foreach (Type t in GetFilteredTypes(filter))
{
if (t.Name.Equals(typeName))
{
types.Add(t);
}
}
return types.ToArray();
}
private static void DisplayDropDown(Rect position, List<Type> types, Type selectedType, TypeGrouping grouping)
{
var menu = new GenericMenu();
menu.AddItem(new GUIContent("(None)"), selectedType == null, OnSelectedTypeName, null);
menu.AddSeparator(string.Empty);
for (int i = 0; i < types.Count; ++i)
{
string menuLabel = FormatGroupedTypeName(types[i], grouping);
if (string.IsNullOrEmpty(menuLabel)) { continue; }
var content = new GUIContent(menuLabel);
menu.AddItem(content, types[i] == selectedType, OnSelectedTypeName, types[i]);
}
menu.DropDown(position);
}
private static string FormatGroupedTypeName(Type type, TypeGrouping grouping)
{
string name = type.FullName;
switch (grouping)
{
case TypeGrouping.None:
return name;
case TypeGrouping.ByNamespace:
return string.IsNullOrEmpty(name) ? string.Empty : name.Replace('.', '/');
case TypeGrouping.ByNamespaceFlat:
int lastPeriodIndex = string.IsNullOrEmpty(name) ? -1 : name.LastIndexOf('.');
if (lastPeriodIndex != -1)
{
name = string.IsNullOrEmpty(name)
? string.Empty
: $"{name.Substring(0, lastPeriodIndex)}/{name.Substring(lastPeriodIndex + 1)}";
}
return name;
case TypeGrouping.ByAddComponentMenu:
var addComponentMenuAttributes = type.GetCustomAttributes(typeof(AddComponentMenu), false);
if (addComponentMenuAttributes.Length == 1)
{
return ((AddComponentMenu)addComponentMenuAttributes[0]).componentMenu;
}
Debug.Assert(type.FullName != null);
return $"Scripts/{type.FullName.Replace('.', '/')}";
default:
throw new ArgumentOutOfRangeException(nameof(grouping), grouping, null);
}
}
private static void OnSelectedTypeName(object userData)
{
selectedReference = SystemType.GetReference(userData as Type);
var typeReferenceUpdatedEvent = EditorGUIUtility.CommandEvent("TypeReferenceUpdated");
EditorWindow.focusedWindow.SendEvent(typeReferenceUpdatedEvent);
}
#endregion Control Drawing / Event Handling
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorStyles.popup.CalcHeight(GUIContent.none, 0);
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
DrawTypeSelectionControl(position, property.FindPropertyRelative("reference"), label, attribute as SystemTypeAttribute);
}
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Editor
{
/// <summary>
/// Custom property drawer for <see cref="Microsoft.MixedReality.Toolkit.Vector3RangeAttribute"/> decorated <see href="https://docs.unity3d.com/ScriptReference/Vector3.html">Vector3</see> values rendered in the inspector.
/// </summary>
[CustomPropertyDrawer(typeof(Vector3RangeAttribute))]
public class Vector3RangePropertyDrawer : PropertyDrawer
{
public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
{
return EditorGUIUtility.wideMode ? EditorGUIUtility.singleLineHeight : EditorGUIUtility.singleLineHeight * 2;
}
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
var range = (Vector3RangeAttribute)attribute;
if (property.propertyType == SerializedPropertyType.Vector3)
{
EditorGUI.BeginChangeCheck();
EditorGUI.PropertyField(position, property);
if (EditorGUI.EndChangeCheck())
{
var vectorData = property.vector3Value;
vectorData.x = Mathf.Clamp(vectorData.x, range.Min, range.Max);
vectorData.y = Mathf.Clamp(vectorData.y, range.Min, range.Max);
vectorData.z = Mathf.Clamp(vectorData.z, range.Min, range.Max);
property.vector3Value = vectorData;
property.serializedObject.ApplyModifiedProperties();
}
}
else
{
EditorGUI.LabelField(position, label.text, "Use Vector3Range with Vector3 fields only.");
}
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Editor
{
[CustomEditor(typeof(ProximityLight))]
public class ProximityLightInspector : UnityEditor.Editor
{
private bool HasFrameBounds() { return true; }
private Bounds OnGetFrameBounds()
{
var light = target as ProximityLight;
Debug.Assert(light != null);
return new Bounds(light.transform.position, Vector3.one * light.Settings.FarRadius);
}
[MenuItem("GameObject/Light/Proximity Light")]
private static void CreateProximityLight(MenuCommand menuCommand)
{
GameObject proximityLight = new GameObject("Proximity Light", typeof(ProximityLight));
// Ensure the light gets re-parented to the active context.
GameObjectUtility.SetParentAndAlign(proximityLight, menuCommand.context as GameObject);
// Register the creation in the undo system.
Undo.RegisterCreatedObjectUndo(proximityLight, "Create " + proximityLight.name);
Selection.activeObject = proximityLight;
}
}
}
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
[assembly: System.Reflection.AssemblyVersion("2.5.0.0")]
[assembly: System.Reflection.AssemblyFileVersion("2.5.0.0")]
[assembly: System.Reflection.AssemblyProduct("Microsoft® Mixed Reality Toolkit aipmragent_work")]
[assembly: System.Reflection.AssemblyCopyright("Copyright © Microsoft Corporation")]
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using UnityEditor;
namespace Microsoft.MixedReality.Toolkit.Editor
{
public class BaseMixedRealityServiceInspector : IMixedRealityServiceInspector
{
public virtual bool DrawProfileField { get { return true; } }
public virtual bool AlwaysDrawSceneGUI { get { return false; } }
public virtual void DrawGizmos(object target) { }
public virtual void DrawInspectorGUI(object target) { }
public virtual void DrawSceneGUI(object target, SceneView sceneView) { }
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Input;
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Editor
{
[MixedRealityServiceInspector(typeof(IMixedRealityFocusProvider))]
public class FocusProviderInspector : BaseMixedRealityServiceInspector
{
private static readonly Color enabledColor = GUI.backgroundColor;
private static readonly Color disabledColor = Color.Lerp(enabledColor, Color.clear, 0.5f);
public override void DrawInspectorGUI(object target)
{
IMixedRealityFocusProvider focusProvider = (IMixedRealityFocusProvider)target;
EditorGUILayout.LabelField("Active Pointers", EditorStyles.boldLabel);
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("Pointers will be populated once you enter play mode.", MessageType.Info);
return;
}
bool pointerFound = false;
foreach (IMixedRealityPointer pointer in focusProvider.GetPointers<IMixedRealityPointer>())
{
GUI.color = pointer.IsInteractionEnabled ? enabledColor : disabledColor;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField(pointer.PointerName);
EditorGUILayout.Toggle("Interaction Enabled", pointer.IsInteractionEnabled);
EditorGUILayout.Toggle("Focus Locked", pointer.IsFocusLocked);
EditorGUILayout.ObjectField("Focus Result", pointer.Result?.CurrentPointerTarget, typeof(GameObject), true);
EditorGUILayout.EndVertical();
pointerFound = true;
}
if (!pointerFound)
{
EditorGUILayout.LabelField("(None found)", EditorStyles.miniLabel);
}
GUI.color = enabledColor;
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Collections.Generic;
using System;
using UnityEditor;
using UnityEngine;
using Microsoft.MixedReality.Toolkit.Input;
using Microsoft.MixedReality.Toolkit.Utilities;
namespace Microsoft.MixedReality.Toolkit.Editor
{
[MixedRealityServiceInspector(typeof(IMixedRealityHandJointService))]
public class HandJointServiceInspector : BaseMixedRealityServiceInspector
{
private const string ShowHandPreviewInSceneViewKey = "MRTK_HandJointServiceInspector_ShowHandPreviewInSceneViewKey";
private const string ShowHandJointFoldoutKey = "MRTK_HandJointServiceInspector_ShowHandJointFoldoutKey";
private const string ShowHandJointKeyPrefix = "MRTK_HandJointServiceInspector_ShowHandJointKeyPrefixKey_";
private static bool ShowHandPreviewInSceneView = false;
private static bool ShowHandJointFoldout = false;
private const float previewJointSize = 0.02f;
private static readonly Color previewJointColor = new Color(0.5f, 0.1f, 0.6f, 0.75f);
private static readonly Color enabledColor = GUI.backgroundColor;
private static readonly Color disabledColor = Color.Lerp(enabledColor, Color.clear, 0.5f);
private static Dictionary<TrackedHandJoint, bool> showHandJointSettings;
private static Dictionary<TrackedHandJoint, string> showHandJointSettingKeys;
// We want hand preview to always be visible
public override bool AlwaysDrawSceneGUI { get { return true; } }
public override void DrawInspectorGUI(object target)
{
IMixedRealityHandJointService handJointService = (IMixedRealityHandJointService)target;
EditorGUILayout.LabelField("Tracking State", EditorStyles.boldLabel);
if (!Application.isPlaying)
{
GUI.color = disabledColor;
EditorGUILayout.Toggle("Left Hand Tracked", false);
EditorGUILayout.Toggle("Right Hand Tracked", false);
}
else
{
GUI.color = enabledColor;
EditorGUILayout.Toggle("Left Hand Tracked", handJointService.IsHandTracked(Handedness.Left));
EditorGUILayout.Toggle("Right Hand Tracked", handJointService.IsHandTracked(Handedness.Right));
}
GenerateHandJointLookup();
GUI.color = enabledColor;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Editor Settings", EditorStyles.boldLabel);
ShowHandPreviewInSceneView = SessionState.GetBool(ShowHandPreviewInSceneViewKey, false);
bool showHandPreviewInSceneView = EditorGUILayout.Toggle("Show Preview in Scene View", ShowHandPreviewInSceneView);
if (ShowHandPreviewInSceneView != showHandPreviewInSceneView)
{
SessionState.SetBool(ShowHandPreviewInSceneViewKey, showHandPreviewInSceneView);
}
ShowHandJointFoldout = SessionState.GetBool(ShowHandJointFoldoutKey, false);
ShowHandJointFoldout = EditorGUILayout.Foldout(ShowHandJointFoldout, "Visible Hand Joints", true);
SessionState.SetBool(ShowHandJointFoldoutKey, ShowHandJointFoldout);
if (ShowHandJointFoldout)
{
#region setting buttons
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("All"))
{
foreach (TrackedHandJoint joint in Enum.GetValues(typeof(TrackedHandJoint)))
{
if (joint == TrackedHandJoint.None)
{
continue;
}
SessionState.SetBool(showHandJointSettingKeys[joint], true);
showHandJointSettings[joint] = true;
}
}
if (GUILayout.Button("Fingers"))
{
foreach (TrackedHandJoint joint in Enum.GetValues(typeof(TrackedHandJoint)))
{
bool setting = false;
switch (joint)
{
case TrackedHandJoint.IndexTip:
case TrackedHandJoint.IndexDistalJoint:
case TrackedHandJoint.IndexKnuckle:
case TrackedHandJoint.IndexMetacarpal:
case TrackedHandJoint.IndexMiddleJoint:
case TrackedHandJoint.MiddleTip:
case TrackedHandJoint.MiddleDistalJoint:
case TrackedHandJoint.MiddleKnuckle:
case TrackedHandJoint.MiddleMetacarpal:
case TrackedHandJoint.MiddleMiddleJoint:
case TrackedHandJoint.PinkyTip:
case TrackedHandJoint.PinkyDistalJoint:
case TrackedHandJoint.PinkyKnuckle:
case TrackedHandJoint.PinkyMetacarpal:
case TrackedHandJoint.PinkyMiddleJoint:
case TrackedHandJoint.RingTip:
case TrackedHandJoint.RingDistalJoint:
case TrackedHandJoint.RingKnuckle:
case TrackedHandJoint.RingMetacarpal:
case TrackedHandJoint.RingMiddleJoint:
case TrackedHandJoint.ThumbTip:
case TrackedHandJoint.ThumbDistalJoint:
case TrackedHandJoint.ThumbMetacarpalJoint:
case TrackedHandJoint.ThumbProximalJoint:
setting = true;
break;
default:
break;
case TrackedHandJoint.None:
continue;
}
SessionState.SetBool(showHandJointSettingKeys[joint], setting);
showHandJointSettings[joint] = setting;
}
}
if (GUILayout.Button("Fingertips"))
{
foreach (TrackedHandJoint joint in Enum.GetValues(typeof(TrackedHandJoint)))
{
bool setting = false;
switch (joint)
{
case TrackedHandJoint.IndexTip:
case TrackedHandJoint.MiddleTip:
case TrackedHandJoint.PinkyTip:
case TrackedHandJoint.RingTip:
case TrackedHandJoint.ThumbTip:
setting = true;
break;
default:
break;
case TrackedHandJoint.None:
continue;
}
SessionState.SetBool(showHandJointSettingKeys[joint], setting);
showHandJointSettings[joint] = setting;
}
}
if (GUILayout.Button("None"))
{
foreach (TrackedHandJoint joint in Enum.GetValues(typeof(TrackedHandJoint)))
{
if (joint == TrackedHandJoint.None)
{
continue;
}
SessionState.SetBool(showHandJointSettingKeys[joint], false);
showHandJointSettings[joint] = false;
}
}
EditorGUILayout.EndHorizontal();
#endregion
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
foreach (TrackedHandJoint joint in Enum.GetValues(typeof(TrackedHandJoint)))
{
if (joint == TrackedHandJoint.None)
{
continue;
}
bool prevSetting = showHandJointSettings[joint];
bool newSetting = EditorGUILayout.Toggle(joint.ToString(), prevSetting);
if (newSetting != prevSetting)
{
SessionState.SetBool(showHandJointSettingKeys[joint], newSetting);
showHandJointSettings[joint] = newSetting;
}
}
EditorGUILayout.EndVertical();
}
}
public override void DrawSceneGUI(object target, SceneView sceneView)
{
if (!Application.isPlaying || !ShowHandPreviewInSceneView)
{
return;
}
IMixedRealityHandJointService handJointService = (IMixedRealityHandJointService)target;
DrawHandPreview(handJointService, Handedness.Left);
DrawHandPreview(handJointService, Handedness.Right);
}
public override void DrawGizmos(object target) { }
public static void DrawHandPreview(IMixedRealityHandJointService handJointService, Handedness handedness)
{
if (!handJointService.IsHandTracked(handedness))
{
return;
}
GenerateHandJointLookup();
Handles.color = previewJointColor;
foreach (KeyValuePair<TrackedHandJoint, bool> setting in showHandJointSettings)
{
if (!setting.Value)
{
continue;
}
Transform jointTransform = handJointService.RequestJointTransform(setting.Key, handedness);
if (jointTransform == null)
{
continue;
}
Handles.SphereHandleCap(0, jointTransform.position, jointTransform.rotation, previewJointSize, EventType.Repaint);
}
}
private static void GenerateHandJointLookup()
{
if (showHandJointSettings != null)
{
return;
}
showHandJointSettingKeys = new Dictionary<TrackedHandJoint, string>();
showHandJointSettings = new Dictionary<TrackedHandJoint, bool>();
foreach (TrackedHandJoint joint in Enum.GetValues(typeof(TrackedHandJoint)))
{
if (joint == TrackedHandJoint.None)
{
continue;
}
string key = ShowHandJointKeyPrefix + joint;
showHandJointSettingKeys.Add(joint, key);
bool showHandJoint = SessionState.GetBool(key, true);
showHandJointSettings.Add(joint, showHandJoint);
}
}
}
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using UnityEditor;
namespace Microsoft.MixedReality.Toolkit.Editor
{
/// <summary>
/// Used to populate service facades with content.
/// To use, create a class that implements this interface
/// and mark it with the MixedRealityServiceInspector attribute.
/// </summary>
public interface IMixedRealityServiceInspector
{
/// <summary>
/// If true, inspector will include a field for the service's profile at the top (if applicable)
/// </summary>
bool DrawProfileField { get; }
/// <summary>
/// If true, DrawSceneGUI will be called even when facade object is not selected.
/// </summary>
bool AlwaysDrawSceneGUI { get; }
/// <summary>
/// Used to draw an inspector for a service facade.
/// </summary>
void DrawInspectorGUI(object target);
/// <summary>
/// Used to draw handles and visualizations in scene view.
/// </summary>
void DrawSceneGUI(object target, SceneView sceneView);
/// <summary>
/// Used to draw gizmos in the scene
/// </summary>
void DrawGizmos(object target);
}
}
{
"name": "Microsoft.MixedReality.Toolkit.Editor.ServiceInspectors",
"references": [
"Microsoft.MixedReality.Toolkit",
"Microsoft.MixedReality.Toolkit.Async",
"Microsoft.MixedReality.Toolkit.Editor.ClassExtensions",
"Microsoft.MixedReality.Toolkit.Editor.Inspectors",
"Microsoft.MixedReality.Toolkit.Editor.Utilities"
],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": []
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.SceneSystem;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Microsoft.MixedReality.Toolkit.Editor
{
[MixedRealityServiceInspector(typeof(IMixedRealitySceneSystem))]
public class SceneSystemInspector : BaseMixedRealityServiceInspector
{
private const float maxLoadButtonWidth = 50;
private const float tagLoadButtonSetWidth = 120;
private static readonly Color enabledColor = GUI.backgroundColor;
private static readonly Color disabledColor = Color.Lerp(enabledColor, Color.clear, 0.5f);
private static readonly Color errorColor = Color.Lerp(GUI.backgroundColor, Color.red, 0.5f);
private SceneActivationToken activationToken = new SceneActivationToken();
private static bool requireActivationToken = false;
private LightingSceneTransitionType transitionType = LightingSceneTransitionType.None;
private LoadSceneMode loadSceneMode = LoadSceneMode.Additive;
private float transitionSpeed = 1f;
public override bool DrawProfileField { get { return true; } }
public override void DrawInspectorGUI(object target)
{
// Get the scene system itself
IMixedRealitySceneSystem sceneSystem = target as IMixedRealitySceneSystem;
// Get the scene system's editor interface
IMixedRealitySceneSystemEditor sceneSystemEditor = target as IMixedRealitySceneSystemEditor;
if (sceneSystemEditor == null)
{
EditorGUILayout.HelpBox("This scene service implementation does not implement IMixedRealitySceneSystemEditor. Inspector will not be rendered.", MessageType.Info);
return;
}
GUI.color = enabledColor;
MixedRealitySceneSystemProfile profile = sceneSystem.ConfigurationProfile as MixedRealitySceneSystemProfile;
if (profile.UseLightingScene)
{
EditorGUILayout.LabelField("Lighting Scene", EditorStyles.boldLabel);
List<SceneInfo> lightingScenes = new List<SceneInfo>(sceneSystemEditor.LightingScenes);
if (lightingScenes.Count == 0)
{
EditorGUILayout.LabelField("(No lighting scenes found)", EditorStyles.miniLabel);
}
else
{
RenderLightingScenes(sceneSystem, sceneSystemEditor, lightingScenes);
}
EditorGUILayout.Space();
}
GUI.color = enabledColor;
EditorGUILayout.LabelField("Content Scenes", EditorStyles.boldLabel);
List<SceneInfo> contentScenes = new List<SceneInfo>(sceneSystemEditor.ContentScenes);
if (contentScenes.Count == 0)
{
EditorGUILayout.LabelField("(No content scenes found)", EditorStyles.miniLabel);
}
else
{
if (Application.isPlaying)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Current Scene Operation", EditorStyles.boldLabel);
loadSceneMode = (LoadSceneMode)EditorGUILayout.EnumPopup("Load Mode", loadSceneMode);
EditorGUILayout.Toggle("Scene Operation In Progress", sceneSystem.SceneOperationInProgress);
EditorGUILayout.FloatField("Progress", sceneSystem.SceneOperationProgress);
requireActivationToken = EditorGUILayout.Toggle("Require Manual Scene Activation", requireActivationToken);
if (requireActivationToken && activationToken.ReadyToProceed)
{
if (GUILayout.Button("Allow Scene Activation"))
{
activationToken.AllowSceneActivation = true;
}
}
else
{
activationToken.AllowSceneActivation = true;
}
EditorGUILayout.EndVertical();
}
EditorGUI.BeginDisabledGroup(sceneSystem.SceneOperationInProgress);
RenderContentScenes(sceneSystem, sceneSystemEditor, contentScenes);
EditorGUI.EndDisabledGroup();
}
EditorGUILayout.Space();
}
private void RenderLightingScenes(IMixedRealitySceneSystem sceneSystem, IMixedRealitySceneSystemEditor sceneSystemEditor, List<SceneInfo> lightingScenes)
{
EditorGUILayout.HelpBox("Select the active lighting scene by clicking its name.", MessageType.Info);
if (Application.isPlaying)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Current Scene Operation", EditorStyles.boldLabel);
EditorGUILayout.Toggle("Scene Operation In Progress", sceneSystem.LightingOperationInProgress);
EditorGUILayout.FloatField("Progress", sceneSystem.LightingOperationProgress);
transitionType = (LightingSceneTransitionType)EditorGUILayout.EnumPopup("Lighting transition type", transitionType);
if (transitionType != LightingSceneTransitionType.None)
{
transitionSpeed = EditorGUILayout.Slider("Lighting transition speed", transitionSpeed, 0f, 10f);
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.BeginVertical();
foreach (SceneInfo lightingScene in lightingScenes)
{
if (lightingScene.IsEmpty)
{
GUI.color = errorColor;
GUILayout.Button("(Scene Missing)", EditorStyles.toolbarButton);
continue;
}
bool selected = lightingScene.Name == sceneSystem.ActiveLightingScene;
GUI.color = selected ? enabledColor : disabledColor;
if (GUILayout.Button(lightingScene.Name, EditorStyles.toolbarButton) && !selected)
{
sceneSystem.SetLightingScene(lightingScene.Name, transitionType, transitionSpeed);
}
}
EditorGUILayout.EndVertical();
}
private void RenderContentScenes(IMixedRealitySceneSystem sceneSystem, IMixedRealitySceneSystemEditor sceneSystemEditor, List<SceneInfo> contentScenes)
{
EditorGUILayout.Space();
EditorGUILayout.Space();
EditorGUILayout.LabelField("Load / Unload by tag", EditorStyles.miniBoldLabel);
List<string> contentTags = new List<string>(sceneSystemEditor.ContentTags);
if (contentTags.Count == 0)
{
EditorGUILayout.LabelField("(No scenes with content tags found)", EditorStyles.miniLabel);
}
else
{
foreach (string tag in contentTags)
{
using (new EditorGUILayout.VerticalScope(GUILayout.MaxWidth(tagLoadButtonSetWidth)))
{
EditorGUILayout.LabelField(tag, EditorStyles.miniLabel);
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Load", EditorStyles.miniButton, GUILayout.MaxWidth(maxLoadButtonWidth)))
{
if (Application.isPlaying)
{
ServiceContentLoadByTag(sceneSystem, tag);
}
else
{
foreach (SceneInfo contentScene in sceneSystemEditor.ContentScenes)
{
if (contentScene.Tag == tag)
{
EditorSceneManager.OpenScene(contentScene.Path, OpenSceneMode.Additive);
}
}
}
}
if (GUILayout.Button("Unload", EditorStyles.miniButton, GUILayout.MaxWidth(maxLoadButtonWidth)))
{
if (Application.isPlaying)
{
ServiceContentUnloadByTag(sceneSystem, tag);
}
else
{
foreach (SceneInfo contentScene in sceneSystemEditor.ContentScenes)
{
if (contentScene.Tag == tag)
{
Scene scene = EditorSceneManager.GetSceneByName(contentScene.Name);
EditorSceneManager.CloseScene(scene, false);
}
}
}
}
}
}
}
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Load / Unload by build index order", EditorStyles.miniBoldLabel);
using (new EditorGUILayout.HorizontalScope())
{
EditorGUI.BeginDisabledGroup(!sceneSystem.PrevContentExists);
if (GUILayout.Button("Load Prev Content", EditorStyles.miniButton))
{
if (Application.isPlaying)
{
ServiceContentLoadPrev(sceneSystem);
}
else
{
sceneSystemEditor.EditorLoadPrevContent();
}
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(!sceneSystem.NextContentExists);
if (GUILayout.Button("Load Next Content", EditorStyles.miniButton))
{
if (Application.isPlaying)
{
ServiceContentLoadNext(sceneSystem);
}
else
{
sceneSystemEditor.EditorLoadNextContent();
}
}
EditorGUI.EndDisabledGroup();
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Load / Unload individually", EditorStyles.miniBoldLabel);
foreach (SceneInfo contentScene in sceneSystemEditor.ContentScenes)
{
if (contentScene.IsEmpty)
{
GUI.color = errorColor;
GUILayout.Button("(Scene Missing)", EditorStyles.toolbarButton);
continue;
}
Scene scene = EditorSceneManager.GetSceneByName(contentScene.Name);
bool loaded = scene.isLoaded;
GUI.color = loaded ? enabledColor : disabledColor;
if (GUILayout.Button(contentScene.Name, EditorStyles.toolbarButton))
{
if (Application.isPlaying)
{
if (loaded)
{
ServiceContentUnload(sceneSystem, contentScene.Name);
}
else
{
ServiceContentLoad(sceneSystem, contentScene.Name);
}
}
else
{
if (loaded)
{
EditorSceneManager.CloseScene(scene, false);
}
else
{
EditorSceneManager.OpenScene(contentScene.Path, OpenSceneMode.Additive);
}
}
}
}
}
private async void ServiceContentLoadNext(IMixedRealitySceneSystem sceneSystem)
{
await sceneSystem.LoadNextContent(false, LoadSceneMode.Single, activationToken);
}
private async void ServiceContentLoadPrev(IMixedRealitySceneSystem sceneSystem)
{
await sceneSystem.LoadPrevContent(false, LoadSceneMode.Single, activationToken);
}
private async void ServiceContentLoadByTag(IMixedRealitySceneSystem sceneSystem, string tag)
{
if (requireActivationToken)
{
activationToken.AllowSceneActivation = false;
}
await sceneSystem.LoadContentByTag(tag, loadSceneMode, activationToken);
}
private async void ServiceContentUnloadByTag(IMixedRealitySceneSystem sceneSystem, string tag)
{
await sceneSystem.UnloadContentByTag(tag);
}
private async void ServiceContentLoad(IMixedRealitySceneSystem sceneSystem, string sceneName)
{
if (requireActivationToken)
{
activationToken.AllowSceneActivation = false;
}
await sceneSystem.LoadContent(sceneName, loadSceneMode, activationToken);
}
private async void ServiceContentUnload(IMixedRealitySceneSystem sceneSystem, string sceneName)
{
await sceneSystem.UnloadContent(sceneName);
}
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Boundary;
using Microsoft.MixedReality.Toolkit.CameraSystem;
using Microsoft.MixedReality.Toolkit.Diagnostics;
using Microsoft.MixedReality.Toolkit.Editor;
using Microsoft.MixedReality.Toolkit.Input;
using Microsoft.MixedReality.Toolkit.SceneSystem;
using Microsoft.MixedReality.Toolkit.SpatialAwareness;
using Microsoft.MixedReality.Toolkit.Utilities.Editor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Utilities.Facades
{
[CustomEditor(typeof(ServiceFacade))]
[InitializeOnLoad]
public class ServiceFacadeEditor : UnityEditor.Editor
{
static ServiceFacadeEditor()
{
// Register this on startup so we can update whether a facade inspector is updated or not
#if UNITY_2019_1_OR_NEWER
SceneView.duringSceneGui += DrawSceneGUI;
#else
SceneView.onSceneGUIDelegate += DrawSceneGUI;
#endif
}
private static readonly List<string> dataProviderList = new List<string>();
private static readonly Dictionary<Type, Type> inspectorTypeLookup = new Dictionary<Type, Type>();
private static readonly Dictionary<Type, IMixedRealityServiceInspector> inspectorInstanceLookup = new Dictionary<Type, IMixedRealityServiceInspector>();
private static bool initializedServiceInspectorLookup = false;
private Color proHeaderColor = new Color32(56, 56, 56, 255);
private Color defaultHeaderColor = new Color32(194, 194, 194, 255);
#if UNITY_2019_1_OR_NEWER
private const int headerYOffet = -6;
private const int headerXOffset = 44;
#else
private const int headerYOffet = 0;
private const int headerXOffset = 48;
#endif
protected override void OnHeaderGUI()
{
ServiceFacade facade = (ServiceFacade)target;
// Draw a rect over the top of the existing header label
var labelRect = EditorGUILayout.GetControlRect(false, 0f);
labelRect.height = EditorGUIUtility.singleLineHeight;
labelRect.y -= labelRect.height - headerYOffet;
labelRect.x = headerXOffset;
labelRect.xMax -= labelRect.x * 2f;
EditorGUI.DrawRect(labelRect, EditorGUIUtility.isProSkin ? proHeaderColor : defaultHeaderColor);
// Draw a new label
string header = facade.name;
if (string.IsNullOrEmpty(header))
header = target.ToString();
EditorGUI.LabelField(labelRect, header, EditorStyles.boldLabel);
}
public override void OnInspectorGUI()
{
OnHeaderGUI();
ServiceFacade facade = (ServiceFacade)target;
if (facade.Service == null)
{ // Facade has likely been destroyed
return;
}
if (!MixedRealityToolkit.IsInitialized || !MixedRealityToolkit.Instance.HasActiveProfile)
{
return;
}
InitializeServiceInspectorLookup();
bool drawDataProviders = DrawDataProviders(facade.ServiceType);
bool drawProfile = DrawProfile(facade.ServiceType);
bool drawInspector = DrawInspector(facade);
// Only draw the doc link if we didn't draw a profile
// Profiles include doc links by default now
if (!drawProfile)
{
InspectorUIUtility.RenderHelpURL(facade.ServiceType);
}
bool drewSomething = drawProfile | drawInspector | drawDataProviders;
if (!drewSomething)
{
// If we haven't drawn anything at all, draw a label so people aren't confused.
EditorGUILayout.HelpBox("No inspector has been defined for this service type.", MessageType.Info);
}
}
public override bool RequiresConstantRepaint()
{
return true;
}
/// <summary>
/// Draws a list of services that use this as a data provider
/// </summary>
private bool DrawDataProviders(Type serviceType)
{
// If this is a data provider being used by other services, mention that now
dataProviderList.Clear();
foreach (MixedRealityDataProviderAttribute dataProviderAttribute in serviceType.GetCustomAttributes(typeof(MixedRealityDataProviderAttribute), true))
{
dataProviderList.Add(" • " + dataProviderAttribute.ServiceInterfaceType.Name);
}
if (dataProviderList.Count > 0)
{
EditorGUILayout.HelpBox("This data provider is used by the following services:\n " + String.Join("\n", dataProviderList.ToArray()), MessageType.Info);
EditorGUILayout.Space();
return true;
}
return false;
}
/// <summary>
/// Draws the custom inspector GUI for all of the service's interfaces that have custom inspectors.
/// </summary>
private bool DrawInspector(ServiceFacade facade)
{
bool drewInspector = false;
foreach (Type interfaceType in facade.ServiceType.GetInterfaces())
{
IMixedRealityServiceInspector inspectorInstance;
if (GetServiceInspectorInstance(interfaceType, out inspectorInstance))
{
inspectorInstance.DrawInspectorGUI(facade.Service);
drewInspector = true;
}
}
return drewInspector;
}
/// <summary>
/// Draws the profile for all of the service's interfaces that have custom inspectors, if wanted by inspector and found.
/// </summary>
private bool DrawProfile(Type serviceType)
{
bool drawProfileField = true;
foreach (Type interfaceType in serviceType.GetInterfaces())
{
IMixedRealityServiceInspector inspectorInstance;
if (GetServiceInspectorInstance(interfaceType, out inspectorInstance))
{
drawProfileField &= inspectorInstance.DrawProfileField;
}
}
if (!drawProfileField)
{ // We've been instructed to skip drawing a profile by the inspector
return false;
}
bool foundAndDrewProfile = false;
// Draw the base profile stuff
if (typeof(BaseCoreSystem).IsAssignableFrom(serviceType))
{
SerializedObject activeProfileObject = new SerializedObject(MixedRealityToolkit.Instance.ActiveProfile);
// Would be nice to handle this using some other method
// Would be nice to handle this with a lookup instead
if (typeof(IMixedRealityInputSystem).IsAssignableFrom(serviceType))
{
SerializedProperty serviceProfileProp = activeProfileObject.FindProperty("inputSystemProfile");
BaseMixedRealityProfileInspector.RenderReadOnlyProfile(serviceProfileProp);
EditorGUILayout.Space();
foundAndDrewProfile = true;
}
else if (typeof(IMixedRealityBoundarySystem).IsAssignableFrom(serviceType))
{
SerializedProperty serviceProfileProp = activeProfileObject.FindProperty("boundaryVisualizationProfile");
BaseMixedRealityProfileInspector.RenderReadOnlyProfile(serviceProfileProp);
EditorGUILayout.Space();
foundAndDrewProfile = true;
}
else if (typeof(IMixedRealityDiagnosticsSystem).IsAssignableFrom(serviceType))
{
SerializedProperty serviceProfileProp = activeProfileObject.FindProperty("diagnosticsSystemProfile");
BaseMixedRealityProfileInspector.RenderReadOnlyProfile(serviceProfileProp);
EditorGUILayout.Space();
foundAndDrewProfile = true;
}
else if (typeof(IMixedRealitySpatialAwarenessSystem).IsAssignableFrom(serviceType))
{
SerializedProperty serviceProfileProp = activeProfileObject.FindProperty("spatialAwarenessSystemProfile");
BaseMixedRealityProfileInspector.RenderReadOnlyProfile(serviceProfileProp);
EditorGUILayout.Space();
foundAndDrewProfile = true;
}
else if (typeof(IMixedRealityCameraSystem).IsAssignableFrom(serviceType))
{
SerializedProperty serviceProfileProp = activeProfileObject.FindProperty("cameraProfile");
BaseMixedRealityProfileInspector.RenderReadOnlyProfile(serviceProfileProp);
EditorGUILayout.Space();
foundAndDrewProfile = true;
}
else if (typeof(IMixedRealitySceneSystem).IsAssignableFrom(serviceType))
{
SerializedProperty serviceProfileProp = activeProfileObject.FindProperty("sceneSystemProfile");
BaseMixedRealityProfileInspector.RenderReadOnlyProfile(serviceProfileProp);
EditorGUILayout.Space();
foundAndDrewProfile = true;
}
}
else if (typeof(BaseExtensionService).IsAssignableFrom(serviceType))
{
// Make sure the extension service profile isn't null
if (MixedRealityToolkit.Instance.ActiveProfile.RegisteredServiceProvidersProfile != null)
{
// If this is an extension service, see if it uses a profile
MixedRealityServiceConfiguration[] serviceConfigs = MixedRealityToolkit.Instance.ActiveProfile.RegisteredServiceProvidersProfile.Configurations;
for (int serviceIndex = 0; serviceIndex < serviceConfigs.Length; serviceIndex++)
{
MixedRealityServiceConfiguration serviceConfig = serviceConfigs[serviceIndex];
if (serviceConfig.ComponentType.Type.IsAssignableFrom(serviceType) && serviceConfig.Profile != null)
{
// We found the service that this type uses - draw the profile
SerializedObject serviceConfigObject = new SerializedObject(MixedRealityToolkit.Instance.ActiveProfile.RegisteredServiceProvidersProfile);
SerializedProperty serviceConfigArray = serviceConfigObject.FindProperty("configurations");
SerializedProperty serviceConfigProp = serviceConfigArray.GetArrayElementAtIndex(serviceIndex);
SerializedProperty serviceProfileProp = serviceConfigProp.FindPropertyRelative("configurationProfile");
BaseMixedRealityProfileInspector.RenderReadOnlyProfile(serviceProfileProp);
EditorGUILayout.Space();
foundAndDrewProfile = true;
break;
}
}
}
}
return foundAndDrewProfile;
}
/// <summary>
/// Gathers service types from all assemblies.
/// </summary>
private static void InitializeServiceInspectorLookup()
{
if (initializedServiceInspectorLookup)
{
return;
}
inspectorTypeLookup.Clear();
var typesWithMyAttribute =
from assembly in AppDomain.CurrentDomain.GetAssemblies().AsParallel()
from classType in assembly.GetLoadableTypes()
let attribute = classType.GetCustomAttribute<MixedRealityServiceInspectorAttribute>(true)
where attribute != null
select new { ClassType = classType, Attribute = attribute };
foreach (var result in typesWithMyAttribute)
{
inspectorTypeLookup.Add(result.Attribute.ServiceType, result.ClassType);
}
initializedServiceInspectorLookup = true;
}
/// <summary>
/// Draws gizmos for facade.
/// </summary>
[DrawGizmo(GizmoType.NonSelected | GizmoType.Selected | GizmoType.Active)]
private static void DrawGizmos(ServiceFacade facade, GizmoType type)
{
if (facade.Service == null)
{
return;
}
if (!MixedRealityToolkit.IsInitialized || !MixedRealityToolkit.Instance.HasActiveProfile)
{
return;
}
InitializeServiceInspectorLookup();
// Find and draw the custom inspector
foreach (Type interfaceType in facade.ServiceType.GetInterfaces())
{
IMixedRealityServiceInspector inspectorInstance;
if (GetServiceInspectorInstance(interfaceType, out inspectorInstance))
{
// If we've implemented a facade inspector, draw gizmos now
inspectorInstance.DrawGizmos(facade.Service);
}
}
}
/// <summary>
/// Draws scene GUI for facade.
/// </summary>
private static void DrawSceneGUI(SceneView sceneView)
{
if (!MixedRealityToolkit.IsInitialized || !MixedRealityToolkit.Instance.HasActiveProfile)
{
return;
}
InitializeServiceInspectorLookup();
foreach (KeyValuePair<Type, Type> inspectorTypePair in inspectorTypeLookup)
{
// Find the facade associated with this service
ServiceFacade facade;
// If no facade exists for this service type, move on
if (!ServiceFacade.FacadeServiceLookup.TryGetValue(inspectorTypePair.Key, out facade) || facade.Destroyed || facade == null)
{
continue;
}
foreach (Type interfaceType in inspectorTypePair.Key.GetInterfaces())
{
IMixedRealityServiceInspector inspectorInstance;
if (!GetServiceInspectorInstance(interfaceType, out inspectorInstance))
{
continue;
}
if (Selection.Contains(facade) || inspectorInstance.AlwaysDrawSceneGUI)
{
inspectorInstance.DrawSceneGUI(facade.Service, sceneView);
}
}
}
}
/// <summary>
/// Gets an instance of the service type. Returns false if no instance is found.
/// </summary>
private static bool GetServiceInspectorInstance(Type interfaceType, out IMixedRealityServiceInspector inspectorInstance)
{
inspectorInstance = null;
Type inspectorType;
if (inspectorTypeLookup.TryGetValue(interfaceType, out inspectorType))
{
if (!inspectorInstanceLookup.TryGetValue(inspectorType, out inspectorInstance))
{
// If an instance of the class doesn't exist yet, create it now
try
{
inspectorInstance = (IMixedRealityServiceInspector)Activator.CreateInstance(inspectorType);
inspectorInstanceLookup.Add(inspectorType, inspectorInstance);
return true;
}
catch (Exception e)
{
Debug.LogError("Couldn't create instance of inspector type " + inspectorType);
Debug.LogException(e);
}
}
else
{
return true;
}
}
return inspectorInstance != null;
}
}
}
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.SpatialAwareness;
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEditor;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Editor
{
[MixedRealityServiceInspector(typeof(IMixedRealitySpatialAwarenessSystem))]
public class SpatialAwarenessSystemInspector : BaseMixedRealityServiceInspector
{
private const string ShowObserverBoundaryKey = "MRTK_SpatialAwarenessSystemInspector_ShowObserverBoundaryKey";
private const string ShowObserverOriginKey = "MRTK_SpatialAwarenessSystemInspector_ShowObserverOriginKey";
private static bool ShowObserverBoundary = false;
private static bool ShowObserverOrigin = false;
private static readonly Color[] observerColors = new Color[] { Color.blue, Color.cyan, Color.green, Color.magenta, Color.red, Color.yellow };
private static readonly Color originColor = new Color(0.75f, 0.1f, 0.75f, 0.75f);
private static readonly Color enabledColor = GUI.backgroundColor;
private static readonly Color disabledColor = Color.Lerp(enabledColor, Color.clear, 0.5f);
public override bool AlwaysDrawSceneGUI { get { return false; } }
public override void DrawInspectorGUI(object target)
{
IMixedRealitySpatialAwarenessSystem spatial = (IMixedRealitySpatialAwarenessSystem)target;
IMixedRealityDataProviderAccess dataProviderAccess = (IMixedRealityDataProviderAccess)spatial;
EditorGUILayout.LabelField("Observers", EditorStyles.boldLabel);
int observerIndex = 0;
var dataProviders = dataProviderAccess?.GetDataProviders();
if (dataProviders != null)
{
foreach (IMixedRealitySpatialAwarenessObserver observer in dataProviders)
{
GUI.color = observer.IsRunning ? enabledColor : disabledColor;
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
GUI.color = GetObserverColor(observerIndex);
GUILayout.Button(observer.Name);
GUI.color = observer.IsRunning ? enabledColor : disabledColor;
EditorGUILayout.Toggle("Running", observer.IsRunning);
EditorGUILayout.LabelField("Source", observer.SourceName);
EditorGUILayout.Toggle("Is Stationary", observer.IsStationaryObserver);
EditorGUILayout.FloatField("Update Interval", observer.UpdateInterval);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Volume Properties", EditorStyles.boldLabel);
EditorGUILayout.EnumPopup("Volume Type", observer.ObserverVolumeType);
EditorGUILayout.Vector3Field("Origin", observer.ObserverOrigin);
EditorGUILayout.Vector3Field("Rotation", observer.ObserverRotation.eulerAngles);
EditorGUILayout.Vector3Field("Extents", observer.ObservationExtents);
}
observerIndex++;
}
}
GUI.color = enabledColor;
if (!Application.isPlaying)
{
EditorGUILayout.HelpBox("Observers will be populated once you enter play mode.", MessageType.Info);
}
else if (observerIndex == 0)
{
EditorGUILayout.LabelField("(None found)", EditorStyles.miniLabel);
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Editor Options", EditorStyles.boldLabel);
ShowObserverBoundary = SessionState.GetBool(ShowObserverBoundaryKey, false);
ShowObserverBoundary = EditorGUILayout.Toggle("Show Observer Boundaries", ShowObserverBoundary);
SessionState.SetBool(ShowObserverBoundaryKey, ShowObserverBoundary);
ShowObserverOrigin = SessionState.GetBool(ShowObserverOriginKey, false);
ShowObserverOrigin = EditorGUILayout.Toggle("Show Observer Origins", ShowObserverOrigin);
SessionState.SetBool(ShowObserverOriginKey, ShowObserverOrigin);
}
public override void DrawSceneGUI(object target, SceneView sceneView) { }
public override void DrawGizmos(object target)
{
if (!(ShowObserverBoundary || ShowObserverOrigin))
{
return;
}
IMixedRealitySpatialAwarenessSystem spatial = (IMixedRealitySpatialAwarenessSystem)target;
IMixedRealityDataProviderAccess dataProviderAccess = (IMixedRealityDataProviderAccess)spatial;
var dataProviders = dataProviderAccess?.GetDataProviders();
if (dataProviders != null)
{
int observerIndex = 0;
foreach (IMixedRealitySpatialAwarenessObserver observer in dataProviders)
{
Gizmos.color = GetObserverColor(observerIndex);
if (ShowObserverBoundary)
{
switch (observer.ObserverVolumeType)
{
case VolumeType.None:
break;
case VolumeType.AxisAlignedCube:
Gizmos.DrawWireCube(observer.ObserverOrigin, observer.ObservationExtents);
break;
case VolumeType.Sphere:
Gizmos.DrawWireSphere(observer.ObserverOrigin, observer.ObservationExtents.x);
break;
case VolumeType.UserAlignedCube:
Gizmos.DrawWireCube(observer.ObserverOrigin, observer.ObservationExtents);
break;
}
}
Gizmos.matrix = Matrix4x4.identity;
if (ShowObserverOrigin)
{
Gizmos.DrawSphere(observer.ObserverOrigin, 0.1f);
}
observerIndex++;
}
}
}
private Color GetObserverColor(int observerIndex)
{
if (observerIndex >= observerColors.Length)
{
observerIndex = 0;
}
return Color.Lerp(Color.white, observerColors[observerIndex], 0.35f);
}
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment