using System.Collections.Generic;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.XR.Interaction.Toolkit;
using UnityEngine.XR.Interaction.Toolkit.Interactables;
namespace UnityEditor.XR.Interaction.Toolkit
{
///
/// Multi-column that shows Interactables.
///
class XRInteractablesTreeView : TreeView
{
public static XRInteractablesTreeView Create(List interactionManagers, 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 XRInteractablesTreeView(interactionManagers, treeState, header);
}
const float k_RowHeight = 20f;
const int k_LayerSize = 32;
const string k_LayerMaskOn = "\u25A0";
const string k_LayerMaskOff = "\u25A1";
class Item : TreeViewItem
{
public IXRInteractable interactable;
}
enum ColumnId
{
Name,
Type,
LayerMask,
LayerMaskList,
Colliders,
Hovered,
Selected,
Count,
}
static bool exitingPlayMode => EditorApplication.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode;
readonly List m_InteractionManagers = new List();
static MultiColumnHeaderState CreateHeaderState()
{
var columns = new MultiColumnHeaderState.Column[(int)ColumnId.Count];
columns[(int)ColumnId.Name] = new MultiColumnHeaderState.Column { width = 180f, minWidth = 80f, headerContent = EditorGUIUtility.TrTextContent("Name") };
columns[(int)ColumnId.Type] = new MultiColumnHeaderState.Column { width = 120f, minWidth = 80f, headerContent = EditorGUIUtility.TrTextContent("Type") };
columns[(int)ColumnId.LayerMask] = new MultiColumnHeaderState.Column { width = 240f, minWidth = 80f, headerContent = EditorGUIUtility.TrTextContent("Layer Mask") };
columns[(int)ColumnId.LayerMaskList] = new MultiColumnHeaderState.Column { width = 120f, minWidth = 80f, headerContent = EditorGUIUtility.TrTextContent("Layer Mask List") };
columns[(int)ColumnId.Colliders] = new MultiColumnHeaderState.Column { width = 120f, minWidth = 80f, headerContent = EditorGUIUtility.TrTextContent("Colliders") };
columns[(int)ColumnId.Hovered] = new MultiColumnHeaderState.Column { width = 80f, minWidth = 80f, headerContent = EditorGUIUtility.TrTextContent("Hovered") };
columns[(int)ColumnId.Selected] = new MultiColumnHeaderState.Column { width = 80f, minWidth = 80f, headerContent = EditorGUIUtility.TrTextContent("Selected") };
return new MultiColumnHeaderState(columns);
}
XRInteractablesTreeView(List managers, TreeViewState state, MultiColumnHeader header)
: base(state, header)
{
foreach (var manager in managers)
AddManager(manager);
showBorder = false;
rowHeight = k_RowHeight;
Reload();
}
public void UpdateManagersList(List currentManagers)
{
var managerListChanged = false;
// Check for Removal
for (var i = 0; i < m_InteractionManagers.Count; i++)
{
var manager = m_InteractionManagers[i];
if (!currentManagers.Contains(manager))
{
RemoveManager(manager);
managerListChanged = true;
--i;
}
}
// Check for Add
foreach (var manager in currentManagers)
{
if (!m_InteractionManagers.Contains(manager))
{
AddManager(manager);
managerListChanged = true;
}
}
if (managerListChanged)
Reload();
}
void AddManager(XRInteractionManager manager)
{
if (m_InteractionManagers.Contains(manager))
return;
manager.interactableRegistered += OnInteractableRegistered;
manager.interactableUnregistered += OnInteractableUnregistered;
m_InteractionManagers.Add(manager);
Reload();
}
void RemoveManager(XRInteractionManager manager)
{
if (!m_InteractionManagers.Contains(manager))
return;
if (manager != null)
{
manager.interactableRegistered -= OnInteractableRegistered;
manager.interactableUnregistered -= OnInteractableUnregistered;
}
m_InteractionManagers.Remove(manager);
Reload();
}
void OnInteractableRegistered(InteractableRegisteredEventArgs eventArgs)
{
Reload();
}
void OnInteractableUnregistered(InteractableUnregisteredEventArgs eventArgs)
{
// Skip reloading as each interactable is being destroyed when exiting Play mode
if (!exitingPlayMode)
Reload();
}
///
protected override TreeViewItem BuildRoot()
{
// Wrap root control in invisible item required by TreeView.
return new Item
{
id = 0,
children = BuildInteractableTree(),
depth = -1,
};
}
List BuildInteractableTree()
{
var items = new List();
var interactables = new List();
foreach (var interactionManager in m_InteractionManagers)
{
if (interactionManager == null)
continue;
var rootTreeItem = new Item
{
id = XRInteractionDebuggerWindow.GetUniqueTreeViewId(interactionManager),
displayName = XRInteractionDebuggerWindow.GetDisplayName(interactionManager),
depth = 0,
};
// Build children.
interactionManager.GetRegisteredInteractables(interactables);
if (interactables.Count > 0)
{
var children = new List();
foreach (var interactable in interactables)
{
var childItem = new Item
{
id = XRInteractionDebuggerWindow.GetUniqueTreeViewId(interactable),
displayName = XRInteractionDebuggerWindow.GetDisplayName(interactable),
interactable = interactable,
depth = 1,
parent = rootTreeItem,
};
children.Add(childItem);
}
// Sort children by name.
children.Sort((a, b) => string.Compare(a.displayName, b.displayName));
rootTreeItem.children = children;
}
items.Add(rootTreeItem);
}
return items;
}
///
protected override void RowGUI(RowGUIArgs args)
{
if (!Application.isPlaying || exitingPlayMode)
return;
var item = (Item)args.item;
var columnCount = args.GetNumVisibleColumns();
for (var i = 0; i < columnCount; ++i)
{
ColumnGUI(args.GetCellRect(i), item, args.GetColumn(i), ref args);
}
}
void ColumnGUI(Rect cellRect, Item item, int column, ref RowGUIArgs args)
{
CenterRectUsingSingleLineHeight(ref cellRect);
if (column == (int)ColumnId.Name)
{
args.rowRect = cellRect;
base.RowGUI(args);
}
if (item.interactable != null)
{
switch (column)
{
case (int)ColumnId.Type:
GUI.Label(cellRect, item.interactable.GetType().Name);
break;
case (int)ColumnId.LayerMask:
GUI.Label(cellRect, XRInteractionDebuggerWindow.GetLayerMaskDisplay(k_LayerSize, item.interactable.interactionLayers.value, k_LayerMaskOn, k_LayerMaskOff));
break;
case (int)ColumnId.LayerMaskList:
var activeLayers = XRInteractionDebuggerWindow.GetActiveLayers(k_LayerSize, item.interactable.interactionLayers.value);
GUI.Label(cellRect, string.Join(", ", activeLayers));
break;
case (int)ColumnId.Colliders:
GUI.Label(cellRect, XRInteractionDebuggerWindow.JoinNames(",", item.interactable.colliders));
break;
case (int)ColumnId.Hovered:
if (item.interactable is IXRHoverInteractable hoverable)
GUI.Label(cellRect, hoverable.isHovered ? "True" : "False");
break;
case (int)ColumnId.Selected:
if (item.interactable is IXRSelectInteractable selectable)
GUI.Label(cellRect, selectable.isSelected ? "True" : "False");
break;
}
}
}
///
protected override void DoubleClickedItem(int id)
{
base.DoubleClickedItem(id);
EditorGUIUtility.PingObject(id);
Selection.activeInstanceID = id;
}
}
}