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; } } }