using System;
using System.Collections.Generic;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.XR;
namespace UnityEditor.XR.Interaction.Toolkit
{
///
/// Multi-column that shows Input Devices.
///
class XRInputDevicesTreeView : TreeView
{
public static XRInputDevicesTreeView Create(ref TreeViewState treeState, ref MultiColumnHeaderState headerState)
{
if (treeState == null)
treeState = new TreeViewState();
var newHeaderState = CreateHeaderState();
if (headerState != null)
MultiColumnHeaderState.OverwriteSerializedFields(headerState, newHeaderState);
headerState = newHeaderState;
var header = new MultiColumnHeader(headerState);
return new XRInputDevicesTreeView(treeState, header);
}
const float k_RowHeight = 20f;
///
/// Temporary scratch list to store the method call results into.
///
static readonly List s_InputDevices = new List();
///
/// Temporary scratch list to store the method call results into.
///
static readonly List s_FeatureUsages = new List();
///
/// Dictionary containing all valid instances and the values.
///
static readonly Dictionary> s_NodesForAllInputDevices = new Dictionary>();
///
/// Array containing all values of the .
///
static XRNode[] s_XRNodes;
class DeviceItem : TreeViewItem
{
public string characteristics;
public string role;
public string xrNodes;
public string manufacturer;
public string subsystem;
}
class FeatureItem : TreeViewItem
{
public InputDevice inputDevice;
public InputFeatureUsage featureUsage;
public string typeString;
}
enum ColumnId
{
Name,
Type,
Value,
Characteristics,
Role,
Node,
Manufacturer,
Subsystem,
Count,
}
static MultiColumnHeaderState CreateHeaderState()
{
var columns = new MultiColumnHeaderState.Column[(int)ColumnId.Count];
columns[(int)ColumnId.Name] =
new MultiColumnHeaderState.Column
{
width = 240f,
minWidth = 60f,
headerContent = EditorGUIUtility.TrTextContent("Name"),
};
columns[(int)ColumnId.Type] =
new MultiColumnHeaderState.Column
{
width = 200f,
headerContent = EditorGUIUtility.TrTextContent("Type"),
};
columns[(int)ColumnId.Value] =
new MultiColumnHeaderState.Column
{
width = 250f,
headerContent = EditorGUIUtility.TrTextContent("Value"),
};
columns[(int)ColumnId.Characteristics] =
new MultiColumnHeaderState.Column
{
width = 200f,
minWidth = 60f,
headerContent = EditorGUIUtility.TrTextContent("Characteristics"),
};
columns[(int)ColumnId.Role] =
new MultiColumnHeaderState.Column
{
width = 150f,
headerContent = EditorGUIUtility.TrTextContent("Role (Deprecated)"),
};
columns[(int)ColumnId.Node] =
new MultiColumnHeaderState.Column
{
width = 150f,
headerContent = EditorGUIUtility.TrTextContent("XR Nodes"),
};
columns[(int)ColumnId.Manufacturer] =
new MultiColumnHeaderState.Column
{
width = 150f,
headerContent = EditorGUIUtility.TrTextContent("Manufacturer"),
};
columns[(int)ColumnId.Subsystem] =
new MultiColumnHeaderState.Column
{
width = 200f,
headerContent = EditorGUIUtility.TrTextContent("Input Subsystem"),
};
return new MultiColumnHeaderState(columns);
}
XRInputDevicesTreeView(TreeViewState state, MultiColumnHeader header)
: base(state, header)
{
showBorder = false;
rowHeight = k_RowHeight;
Reload();
InputDevices.deviceConnected += OnDeviceConnected;
InputDevices.deviceDisconnected += OnDeviceDisconnected;
InputDevices.deviceConfigChanged += OnDeviceConfigChanged;
}
///
/// Call this when this tree has no more use.
///
public void Release()
{
InputDevices.deviceConnected -= OnDeviceConnected;
InputDevices.deviceDisconnected -= OnDeviceDisconnected;
InputDevices.deviceConfigChanged -= OnDeviceConfigChanged;
}
void OnDeviceConnected(InputDevice inputDevice) => Reload();
void OnDeviceDisconnected(InputDevice inputDevice) => Reload();
void OnDeviceConfigChanged(InputDevice inputDevice) => Reload();
protected override TreeViewItem BuildRoot()
{
// Wrap root control in invisible item required by TreeView.
var root = new TreeViewItem
{
id = 0,
depth = -1,
};
root.children = BuildInputDevicesTree(root);
return root;
}
static List BuildInputDevicesTree(TreeViewItem rootItem)
{
// Initialize XRNodes array with all enum values.
if (s_XRNodes == null)
{
var array = Enum.GetValues(typeof(XRNode));
s_XRNodes = new XRNode[array.Length];
Array.Copy(array, s_XRNodes, array.Length);
}
// To identify all the XRNode values associated with each InputDevice, we have to
// piece it together by getting all input devices for each XRNode value.
s_NodesForAllInputDevices.Clear();
foreach (var xrNode in s_XRNodes)
{
s_InputDevices.Clear();
InputDevices.GetDevicesAtXRNode(xrNode, s_InputDevices);
foreach (var device in s_InputDevices)
{
if (!s_NodesForAllInputDevices.TryGetValue(device, out var deviceXRNodes))
{
deviceXRNodes = new List();
s_NodesForAllInputDevices[device] = deviceXRNodes;
}
deviceXRNodes.Add(xrNode);
}
}
// Build children
var items = new List();
// Build device items
foreach (var kvp in s_NodesForAllInputDevices)
{
var device = kvp.Key;
var xrNodes = kvp.Value;
var deviceItem = new DeviceItem
{
id = UniqueIdGenerator.GetUniqueTreeViewId(device),
displayName = device.name,
characteristics = device.characteristics.ToString(),
#pragma warning disable 612, 618
role = device.role.ToString(),
#pragma warning restore 612, 618
xrNodes = string.Join(", ", xrNodes),
manufacturer = device.manufacturer,
subsystem = device.subsystem.subsystemDescriptor.id,
depth = 0,
parent = rootItem,
};
// Build feature items
s_FeatureUsages.Clear();
device.TryGetFeatureUsages(s_FeatureUsages);
var featureChildren = new List();
for (var index = 0; index < s_FeatureUsages.Count; ++index)
{
var featureUsage = s_FeatureUsages[index];
var featureItem = new FeatureItem
{
id = UniqueIdGenerator.GetUniqueTreeViewId(device, featureUsage, index),
displayName = featureUsage.name,
inputDevice = device,
featureUsage = featureUsage,
typeString = featureUsage.type.ToString(),
depth = 1,
parent = deviceItem,
};
featureChildren.Add(featureItem);
}
deviceItem.children = featureChildren;
items.Add(deviceItem);
}
// Sort devices by name, then by id (to create a stable order when the names match)
items.Sort((a, b) =>
{
var nameCompare = string.Compare(a.displayName, b.displayName);
return nameCompare != 0 ? nameCompare : a.id.CompareTo(b.id);
});
return items;
}
static string GetFeatureValue(InputDevice device, InputFeatureUsage featureUsage)
{
// InputFeatureType.Custom
if (featureUsage.type == typeof(byte[]))
return "System.Byte[]";
// InputFeatureType.Binary
if (featureUsage.type == typeof(bool))
return device.TryGetFeatureValue(featureUsage.As(), out var boolValue) ? boolValue.ToString() : string.Empty;
// InputFeatureType.DiscreteStates
if (featureUsage.type == typeof(uint))
{
if (device.TryGetFeatureValue(featureUsage.As(), out var uintValue))
{
return featureUsage.name.Contains("TrackingState") ? ((InputTrackingState)uintValue).ToString() : uintValue.ToString();
}
return string.Empty;
}
// InputFeatureType.Axis1D
if (featureUsage.type == typeof(float))
return device.TryGetFeatureValue(featureUsage.As(), out var floatValue) ? floatValue.ToString() : string.Empty;
// InputFeatureType.Axis2D
if (featureUsage.type == typeof(Vector2))
return device.TryGetFeatureValue(featureUsage.As(), out var vector2Value) ? vector2Value.ToString() : string.Empty;
// InputFeatureType.Axis3D
if (featureUsage.type == typeof(Vector3))
return device.TryGetFeatureValue(featureUsage.As(), out var vector3Value) ? vector3Value.ToString() : string.Empty;
// InputFeatureType.Rotation
if (featureUsage.type == typeof(Quaternion))
return device.TryGetFeatureValue(featureUsage.As(), out var quaternionValue) ? quaternionValue.ToString() : string.Empty;
// InputFeatureType.Hand
if (featureUsage.type == typeof(Hand))
return device.TryGetFeatureValue(featureUsage.As(), out var handValue) ? handValue.ToString() : string.Empty;
// InputFeatureType.Bone
if (featureUsage.type == typeof(Bone))
{
if (device.TryGetFeatureValue(featureUsage.As(), out var boneValue))
{
if (boneValue.TryGetPosition(out var bonePosition) &&
boneValue.TryGetRotation(out var boneRotation))
return $"{bonePosition}, {boneRotation}";
}
}
// InputFeatureType.Eyes
if (featureUsage.type == typeof(Eyes))
{
if (device.TryGetFeatureValue(featureUsage.As(), out var eyesValue))
{
if (eyesValue.TryGetFixationPoint(out var fixation) &&
eyesValue.TryGetLeftEyeOpenAmount(out var leftOpen) &&
eyesValue.TryGetLeftEyePosition(out var leftPosition) &&
eyesValue.TryGetLeftEyeRotation(out var leftRotation) &&
eyesValue.TryGetRightEyeOpenAmount(out var rightOpen) &&
eyesValue.TryGetRightEyePosition(out var rightPosition) &&
eyesValue.TryGetRightEyeRotation(out var rightRotation))
return $"{fixation}, Left {{{leftOpen}, {leftPosition}, {leftRotation}}}, Right {{{rightOpen}, {rightPosition}, {rightRotation}}}";
}
}
return string.Empty;
}
protected override void RowGUI(RowGUIArgs args)
{
if (!Application.isPlaying)
return;
var columnCount = args.GetNumVisibleColumns();
for (var i = 0; i < columnCount; ++i)
{
ColumnGUI(args.GetCellRect(i), args.item, args.GetColumn(i), ref args);
}
}
void ColumnGUI(Rect cellRect, TreeViewItem item, int column, ref RowGUIArgs args)
{
CenterRectUsingSingleLineHeight(ref cellRect);
if (column == (int)ColumnId.Name)
{
args.rowRect = cellRect;
base.RowGUI(args);
}
var deviceItem = item as DeviceItem;
var featureItem = item as FeatureItem;
switch (column)
{
case (int)ColumnId.Type:
if (item is FeatureItem)
GUI.Label(cellRect, featureItem.typeString);
break;
case (int)ColumnId.Value:
if (item is FeatureItem)
GUI.Label(cellRect, GetFeatureValue(featureItem.inputDevice, featureItem.featureUsage));
break;
case (int)ColumnId.Characteristics:
if (item is DeviceItem)
GUI.Label(cellRect, deviceItem.characteristics);
break;
case (int)ColumnId.Role:
if (item is DeviceItem)
GUI.Label(cellRect, deviceItem.role);
break;
case (int)ColumnId.Node:
if (item is DeviceItem)
GUI.Label(cellRect, deviceItem.xrNodes);
break;
case (int)ColumnId.Manufacturer:
if (item is DeviceItem)
GUI.Label(cellRect, deviceItem.manufacturer);
break;
case (int)ColumnId.Subsystem:
if (item is DeviceItem)
GUI.Label(cellRect, deviceItem.subsystem);
break;
}
}
///
/// Alternate version of which works
/// with multiple values that seed each row in this tree.
///
static class UniqueIdGenerator
{
// Incrementing unique ID counter, which is shared by all the row types in this tree
static int s_UniqueIdCounter = 1;
// Maps from the source for each row to the unique ID for the row
static readonly Dictionary s_DeviceGeneratedIds = new Dictionary();
static readonly Dictionary<(InputDevice, InputFeatureUsage, int), int> s_FeatureGeneratedIds = new Dictionary<(InputDevice, InputFeatureUsage, int), int>();
public static int GetUniqueTreeViewId(InputDevice inputDevice)
{
if (s_DeviceGeneratedIds.TryGetValue(inputDevice, out var id))
return id;
id = CreateId();
s_DeviceGeneratedIds.Add(inputDevice, id);
return id;
}
public static int GetUniqueTreeViewId(InputDevice inputDevice, InputFeatureUsage featureUsage, int index)
{
if (s_FeatureGeneratedIds.TryGetValue((inputDevice, featureUsage, index), out var id))
return id;
id = CreateId();
s_FeatureGeneratedIds.Add((inputDevice, featureUsage, index), id);
return id;
}
static int CreateId()
{
return s_UniqueIdCounter++;
}
}
}
}