1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018 |
- #if UNITY_EDITOR
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using UnityEngine.InputSystem.LowLevel;
- using UnityEditor;
- using UnityEditorInternal;
- using UnityEditor.IMGUI.Controls;
- using UnityEditor.Networking.PlayerConnection;
- using UnityEngine.InputSystem.Layouts;
- using UnityEngine.InputSystem.Users;
- using UnityEngine.InputSystem.Utilities;
-
- ////FIXME: Generate proper IDs for the individual tree view items; the current sequential numbering scheme just causes lots of
- //// weird expansion/collapsing to happen.
-
- ////TODO: add way to load and replay event traces
-
- ////TODO: refresh metrics on demand
-
- ////TODO: when an action is triggered and when a device changes state, make them bold in the list for a brief moment
-
- ////TODO: show input users and their actions and devices
-
- ////TODO: append " (Disabled) to disabled devices and grey them out
-
- ////TODO: split 'Local' and 'Remote' at root rather than inside subnodes
-
- ////TODO: refresh when unrecognized device pops up
-
- namespace UnityEngine.InputSystem.Editor
- {
- // Allows looking at input activity in the editor.
- internal class InputDebuggerWindow : EditorWindow, ISerializationCallbackReceiver
- {
- private static int s_Disabled;
- private static InputDebuggerWindow s_Instance;
-
- [MenuItem("Window/Analysis/Input Debugger", false, 2100)]
- public static void CreateOrShow()
- {
- if (s_Instance == null)
- {
- s_Instance = GetWindow<InputDebuggerWindow>();
- s_Instance.Show();
- s_Instance.titleContent = new GUIContent("Input Debug");
- }
- else
- {
- s_Instance.Show();
- s_Instance.Focus();
- }
- }
-
- public static void Enable()
- {
- if (s_Disabled == 0)
- return;
-
- --s_Disabled;
- if (s_Disabled == 0 && s_Instance != null)
- {
- s_Instance.InstallHooks();
- s_Instance.Refresh();
- }
- }
-
- public static void Disable()
- {
- ++s_Disabled;
- if (s_Disabled == 1 && s_Instance != null)
- {
- s_Instance.UninstallHooks();
- s_Instance.Refresh();
- }
- }
-
- private void OnDeviceChange(InputDevice device, InputDeviceChange change)
- {
- // Update tree if devices are added or removed.
- if (change == InputDeviceChange.Added || change == InputDeviceChange.Removed)
- Refresh();
- }
-
- private void OnLayoutChange(string name, InputControlLayoutChange change)
- {
- // Update tree if layout setup has changed.
- Refresh();
- }
-
- private void OnActionChange(object actionOrMap, InputActionChange change)
- {
- switch (change)
- {
- // When an action is triggered, we only need a repaint.
- case InputActionChange.ActionStarted:
- case InputActionChange.ActionPerformed:
- case InputActionChange.ActionCanceled:
- Repaint();
- break;
-
- case InputActionChange.ActionEnabled:
- case InputActionChange.ActionDisabled:
- case InputActionChange.ActionMapDisabled:
- case InputActionChange.ActionMapEnabled:
- case InputActionChange.BoundControlsChanged:
- Refresh();
- break;
- }
- }
-
- private void OnSettingsChange()
- {
- Refresh();
- }
-
- private string OnFindLayout(ref InputDeviceDescription description, string matchedLayout,
- InputDeviceExecuteCommandDelegate executeCommandDelegate)
- {
- // If there's no matched layout, there's a chance this device will go in
- // the unsupported list. There's no direct notification for that so we
- // preemptively trigger a refresh.
- if (string.IsNullOrEmpty(matchedLayout))
- Refresh();
-
- return null;
- }
-
- private void Refresh()
- {
- m_NeedReload = true;
- Repaint();
- }
-
- public void OnDestroy()
- {
- UninstallHooks();
- }
-
- private void InstallHooks()
- {
- InputSystem.onDeviceChange += OnDeviceChange;
- InputSystem.onLayoutChange += OnLayoutChange;
- InputSystem.onFindLayoutForDevice += OnFindLayout;
- InputSystem.onActionChange += OnActionChange;
- InputSystem.onSettingsChange += OnSettingsChange;
- }
-
- private void UninstallHooks()
- {
- InputSystem.onDeviceChange -= OnDeviceChange;
- InputSystem.onLayoutChange -= OnLayoutChange;
- InputSystem.onFindLayoutForDevice -= OnFindLayout;
- InputSystem.onActionChange -= OnActionChange;
- InputSystem.onSettingsChange -= OnSettingsChange;
- }
-
- private void Initialize()
- {
- InstallHooks();
-
- var newTreeViewState = m_TreeViewState == null;
- if (newTreeViewState)
- m_TreeViewState = new TreeViewState();
-
- m_TreeView = new InputSystemTreeView(m_TreeViewState);
-
- // Set default expansion states.
- if (newTreeViewState)
- m_TreeView.SetExpanded(m_TreeView.devicesItem.id, true);
-
- m_Initialized = true;
- }
-
- public void OnGUI()
- {
- if (s_Disabled > 0)
- {
- EditorGUILayout.LabelField("Disabled");
- return;
- }
-
- // If the new backends aren't enabled, show a warning in the debugger.
- if (!EditorPlayerSettingHelpers.newSystemBackendsEnabled)
- {
- EditorGUILayout.HelpBox(
- "Platform backends for the new input system are not enabled. " +
- "No devices and input from hardware will come through in the new input system APIs.\n\n" +
- "To enable the backends, set 'Active Input Handling' in the player settings to either 'Input System (Preview)' " +
- "or 'Both' and restart the editor.", MessageType.Warning);
- }
-
- // This also brings us back online after a domain reload.
- if (!m_Initialized)
- {
- Initialize();
- }
- else if (m_NeedReload)
- {
- m_TreeView.Reload();
- m_NeedReload = false;
- }
-
- DrawToolbarGUI();
-
- var rect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true));
- m_TreeView.OnGUI(rect);
- }
-
- private static void ResetDevice(InputDevice device, bool hard)
- {
- var playerUpdateType = InputDeviceDebuggerWindow.DetermineUpdateTypeToShow(device);
- var currentUpdateType = InputState.currentUpdateType;
- InputStateBuffers.SwitchTo(InputSystem.s_Manager.m_StateBuffers, playerUpdateType);
- InputSystem.ResetDevice(device, alsoResetDontResetControls: hard);
- InputStateBuffers.SwitchTo(InputSystem.s_Manager.m_StateBuffers, currentUpdateType);
- }
-
- private static void ToggleAddDevicesNotSupportedByProject()
- {
- InputEditorUserSettings.addDevicesNotSupportedByProject =
- !InputEditorUserSettings.addDevicesNotSupportedByProject;
- }
-
- private void ToggleDiagnosticMode()
- {
- if (InputSystem.s_Manager.m_Diagnostics != null)
- {
- InputSystem.s_Manager.m_Diagnostics = null;
- }
- else
- {
- if (m_Diagnostics == null)
- m_Diagnostics = new InputDiagnostics();
- InputSystem.s_Manager.m_Diagnostics = m_Diagnostics;
- }
- }
-
- private static void ToggleTouchSimulation()
- {
- InputEditorUserSettings.simulateTouch = !InputEditorUserSettings.simulateTouch;
- }
-
- private static void EnableRemoteDevices(bool enable = true)
- {
- foreach (var player in EditorConnection.instance.ConnectedPlayers)
- {
- EditorConnection.instance.Send(enable ? RemoteInputPlayerConnection.kStartSendingMsg : RemoteInputPlayerConnection.kStopSendingMsg, new byte[0], player.playerId);
- if (!enable)
- InputSystem.remoting.RemoveRemoteDevices(player.playerId);
- }
- }
-
- private static void DrawConnectionGUI()
- {
- if (GUILayout.Button("Remote Devices…", EditorStyles.toolbarDropDown))
- {
- var menu = new GenericMenu();
- var haveRemotes = InputSystem.devices.Any(x => x.remote);
- if (EditorConnection.instance.ConnectedPlayers.Count > 0)
- menu.AddItem(new GUIContent("Show remote devices"), haveRemotes, () =>
- {
- EnableRemoteDevices(!haveRemotes);
- });
- else
- menu.AddDisabledItem(new GUIContent("Show remote input devices"));
-
- menu.AddSeparator("");
-
- var availableProfilers = ProfilerDriver.GetAvailableProfilers();
- foreach (var profiler in availableProfilers)
- {
- var enabled = ProfilerDriver.IsIdentifierConnectable(profiler);
- var profilerName = ProfilerDriver.GetConnectionIdentifier(profiler);
- var isConnected = ProfilerDriver.connectedProfiler == profiler;
- if (enabled)
- menu.AddItem(new GUIContent(profilerName), isConnected, () => {
- ProfilerDriver.connectedProfiler = profiler;
- EnableRemoteDevices();
- });
- else
- menu.AddDisabledItem(new GUIContent(profilerName));
- }
-
- foreach (var device in UnityEditor.Hardware.DevDeviceList.GetDevices())
- {
- var supportsPlayerConnection = (device.features & UnityEditor.Hardware.DevDeviceFeatures.PlayerConnection) != 0;
- if (!device.isConnected || !supportsPlayerConnection)
- continue;
-
- var url = "device://" + device.id;
- var isConnected = ProfilerDriver.connectedProfiler == 0xFEEE && ProfilerDriver.directConnectionUrl == url;
- menu.AddItem(new GUIContent(device.name), isConnected, () => {
- ProfilerDriver.DirectURLConnect(url);
- EnableRemoteDevices();
- });
- }
-
- menu.ShowAsContext();
- }
- }
-
- private void DrawToolbarGUI()
- {
- EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
-
- if (GUILayout.Button(Contents.optionsContent, EditorStyles.toolbarDropDown))
- {
- var menu = new GenericMenu();
-
- menu.AddItem(Contents.addDevicesNotSupportedByProjectContent, InputEditorUserSettings.addDevicesNotSupportedByProject,
- ToggleAddDevicesNotSupportedByProject);
- menu.AddItem(Contents.diagnosticsModeContent, InputSystem.s_Manager.m_Diagnostics != null,
- ToggleDiagnosticMode);
- menu.AddItem(Contents.touchSimulationContent, InputEditorUserSettings.simulateTouch, ToggleTouchSimulation);
-
- // Add the inverse of "Copy Device Description" which adds a device with the description from
- // the clipboard to the system. This is most useful for debugging and makes it very easy to
- // have a first pass at device descriptions supplied by users.
- try
- {
- var copyBuffer = EditorHelpers.GetSystemCopyBufferContents();
- if (!string.IsNullOrEmpty(copyBuffer) &&
- copyBuffer.StartsWith("{") && !InputDeviceDescription.FromJson(copyBuffer).empty)
- {
- menu.AddItem(Contents.pasteDeviceDescriptionAsDevice, false, () =>
- {
- var description = InputDeviceDescription.FromJson(copyBuffer);
- InputSystem.AddDevice(description);
- });
- }
- }
- catch (ArgumentException)
- {
- // Catch and ignore exception if buffer doesn't actually contain an InputDeviceDescription
- // in (proper) JSON format.
- }
-
- menu.ShowAsContext();
- }
-
- DrawConnectionGUI();
-
- GUILayout.FlexibleSpace();
- EditorGUILayout.EndHorizontal();
- }
-
- [SerializeField] private TreeViewState m_TreeViewState;
-
- [NonSerialized] private InputDiagnostics m_Diagnostics;
- [NonSerialized] private InputSystemTreeView m_TreeView;
- [NonSerialized] private bool m_Initialized;
- [NonSerialized] private bool m_NeedReload;
-
- internal static void ReviveAfterDomainReload()
- {
- if (s_Instance != null)
- {
- // Trigger initial repaint. Will call Initialize() to install hooks and
- // refresh tree.
- s_Instance.Repaint();
- }
- }
-
- private static class Contents
- {
- public static readonly GUIContent optionsContent = new GUIContent("Options");
- public static readonly GUIContent touchSimulationContent = new GUIContent("Simulate Touch Input From Mouse or Pen");
- public static readonly GUIContent pasteDeviceDescriptionAsDevice = new GUIContent("Paste Device Description as Device");
- public static readonly GUIContent addDevicesNotSupportedByProjectContent = new GUIContent("Add Devices Not Listed in 'Supported Devices'");
- public static readonly GUIContent diagnosticsModeContent = new GUIContent("Enable Event Diagnostics");
- public static readonly GUIContent openDebugView = new GUIContent("Open Device Debug View");
- public static readonly GUIContent copyDeviceDescription = new GUIContent("Copy Device Description");
- public static readonly GUIContent copyLayoutAsJSON = new GUIContent("Copy Layout as JSON");
- public static readonly GUIContent createDeviceFromLayout = new GUIContent("Create Device from Layout");
- public static readonly GUIContent generateCodeFromLayout = new GUIContent("Generate Precompiled Layout");
- public static readonly GUIContent removeDevice = new GUIContent("Remove Device");
- public static readonly GUIContent enableDevice = new GUIContent("Enable Device");
- public static readonly GUIContent disableDevice = new GUIContent("Disable Device");
- public static readonly GUIContent syncDevice = new GUIContent("Try to Sync Device");
- public static readonly GUIContent softResetDevice = new GUIContent("Reset Device (Soft)");
- public static readonly GUIContent hardResetDevice = new GUIContent("Reset Device (Hard)");
- }
-
- void ISerializationCallbackReceiver.OnBeforeSerialize()
- {
- }
-
- void ISerializationCallbackReceiver.OnAfterDeserialize()
- {
- s_Instance = this;
- }
-
- private class InputSystemTreeView : TreeView
- {
- public TreeViewItem actionsItem { get; private set; }
- public TreeViewItem devicesItem { get; private set; }
- public TreeViewItem layoutsItem { get; private set; }
- public TreeViewItem settingsItem { get; private set; }
- public TreeViewItem metricsItem { get; private set; }
- public TreeViewItem usersItem { get; private set; }
-
- public InputSystemTreeView(TreeViewState state)
- : base(state)
- {
- Reload();
- }
-
- protected override void ContextClickedItem(int id)
- {
- var item = FindItem(id, rootItem);
- if (item == null)
- return;
-
- if (item is DeviceItem deviceItem)
- {
- var menu = new GenericMenu();
- menu.AddItem(Contents.openDebugView, false, () => InputDeviceDebuggerWindow.CreateOrShowExisting(deviceItem.device));
- menu.AddItem(Contents.copyDeviceDescription, false,
- () => EditorHelpers.SetSystemCopyBufferContents(deviceItem.device.description.ToJson()));
- menu.AddItem(Contents.removeDevice, false, () => InputSystem.RemoveDevice(deviceItem.device));
- if (deviceItem.device.enabled)
- menu.AddItem(Contents.disableDevice, false, () => InputSystem.DisableDevice(deviceItem.device));
- else
- menu.AddItem(Contents.enableDevice, false, () => InputSystem.EnableDevice(deviceItem.device));
- menu.AddItem(Contents.syncDevice, false, () => InputSystem.TrySyncDevice(deviceItem.device));
- menu.AddItem(Contents.softResetDevice, false, () => ResetDevice(deviceItem.device, false));
- menu.AddItem(Contents.hardResetDevice, false, () => ResetDevice(deviceItem.device, true));
- menu.ShowAsContext();
- }
-
- if (item is UnsupportedDeviceItem unsupportedDeviceItem)
- {
- var menu = new GenericMenu();
- menu.AddItem(Contents.copyDeviceDescription, false,
- () => EditorHelpers.SetSystemCopyBufferContents(unsupportedDeviceItem.description.ToJson()));
- menu.ShowAsContext();
- }
-
- if (item is LayoutItem layoutItem)
- {
- var layout = EditorInputControlLayoutCache.TryGetLayout(layoutItem.layoutName);
- if (layout != null)
- {
- var menu = new GenericMenu();
- menu.AddItem(Contents.copyLayoutAsJSON, false,
- () => EditorHelpers.SetSystemCopyBufferContents(layout.ToJson()));
- if (layout.isDeviceLayout)
- {
- menu.AddItem(Contents.createDeviceFromLayout, false,
- () => InputSystem.AddDevice(layout.name));
- menu.AddItem(Contents.generateCodeFromLayout, false, () =>
- {
- var fileName = EditorUtility.SaveFilePanel("Generate InputDevice Code", "", "Fast" + layoutItem.layoutName, "cs");
- var isInAssets = fileName.StartsWith(Application.dataPath, StringComparison.OrdinalIgnoreCase);
- if (isInAssets)
- fileName = "Assets/" + fileName.Substring(Application.dataPath.Length + 1);
- if (!string.IsNullOrEmpty(fileName))
- {
- var code = InputLayoutCodeGenerator.GenerateCodeFileForDeviceLayout(layoutItem.layoutName, fileName, prefix: "Fast");
- File.WriteAllText(fileName, code);
- if (isInAssets)
- AssetDatabase.Refresh();
- }
- });
- }
- menu.ShowAsContext();
- }
- }
- }
-
- protected override void DoubleClickedItem(int id)
- {
- var item = FindItem(id, rootItem);
-
- if (item is DeviceItem deviceItem)
- InputDeviceDebuggerWindow.CreateOrShowExisting(deviceItem.device);
- }
-
- protected override TreeViewItem BuildRoot()
- {
- var id = 0;
-
- var root = new TreeViewItem
- {
- id = id++,
- depth = -1
- };
-
- ////TODO: this will need to be improved for multi-user scenarios
- // Actions.
- m_EnabledActions.Clear();
- InputSystem.ListEnabledActions(m_EnabledActions);
- if (m_EnabledActions.Count > 0)
- {
- actionsItem = AddChild(root, "", ref id);
- AddEnabledActions(actionsItem, ref id);
-
- if (!actionsItem.hasChildren)
- {
- // We are culling actions that are assigned to users so we may end up with an empty
- // list even if we have enabled actions. If we do, remove the "Actions" item from the tree.
- root.children.Remove(actionsItem);
- }
- else
- {
- // Update title to include action count.
- actionsItem.displayName = $"Actions ({actionsItem.children.Count})";
- }
- }
-
- // Users.
- var userCount = InputUser.all.Count;
- if (userCount > 0)
- {
- usersItem = AddChild(root, $"Users ({userCount})", ref id);
- foreach (var user in InputUser.all)
- AddUser(usersItem, user, ref id);
- }
-
- // Devices.
- var devices = InputSystem.devices;
- devicesItem = AddChild(root, $"Devices ({devices.Count})", ref id);
- var haveRemotes = devices.Any(x => x.remote);
- TreeViewItem localDevicesNode = null;
- if (haveRemotes)
- {
- // Split local and remote devices into groups.
-
- localDevicesNode = AddChild(devicesItem, "Local", ref id);
- AddDevices(localDevicesNode, devices, ref id);
-
- var remoteDevicesNode = AddChild(devicesItem, "Remote", ref id);
- foreach (var player in EditorConnection.instance.ConnectedPlayers)
- {
- var playerNode = AddChild(remoteDevicesNode, player.name, ref id);
- AddDevices(playerNode, devices, ref id, player.playerId);
- }
- }
- else
- {
- // We don't have remote devices so don't add an extra group for local devices.
- // Put them all directly underneath the "Devices" node.
- AddDevices(devicesItem, devices, ref id);
- }
-
- ////TDO: unsupported and disconnected devices should also be shown for remotes
-
- if (m_UnsupportedDevices == null)
- m_UnsupportedDevices = new List<InputDeviceDescription>();
- m_UnsupportedDevices.Clear();
- InputSystem.GetUnsupportedDevices(m_UnsupportedDevices);
- if (m_UnsupportedDevices.Count > 0)
- {
- var parent = haveRemotes ? localDevicesNode : devicesItem;
- var unsupportedDevicesNode = AddChild(parent, $"Unsupported ({m_UnsupportedDevices.Count})", ref id);
- foreach (var device in m_UnsupportedDevices)
- {
- var item = new UnsupportedDeviceItem
- {
- id = id++,
- depth = unsupportedDevicesNode.depth + 1,
- displayName = device.ToString(),
- description = device
- };
- unsupportedDevicesNode.AddChild(item);
- }
- unsupportedDevicesNode.children.Sort((a, b) =>
- string.Compare(a.displayName, b.displayName, StringComparison.InvariantCulture));
- }
-
- var disconnectedDevices = InputSystem.disconnectedDevices;
- if (disconnectedDevices.Count > 0)
- {
- var parent = haveRemotes ? localDevicesNode : devicesItem;
- var disconnectedDevicesNode = AddChild(parent, $"Disconnected ({disconnectedDevices.Count})", ref id);
- foreach (var device in disconnectedDevices)
- AddChild(disconnectedDevicesNode, device.ToString(), ref id);
- disconnectedDevicesNode.children.Sort((a, b) =>
- string.Compare(a.displayName, b.displayName, StringComparison.InvariantCulture));
- }
-
- // Layouts.
- layoutsItem = AddChild(root, "Layouts", ref id);
- AddControlLayouts(layoutsItem, ref id);
-
- ////FIXME: this shows local configuration only
- // Settings.
- var settings = InputSystem.settings;
- var settingsAssetPath = AssetDatabase.GetAssetPath(settings);
- var settingsLabel = "Settings";
- if (!string.IsNullOrEmpty(settingsAssetPath))
- settingsLabel = $"Settings ({Path.GetFileName(settingsAssetPath)})";
- settingsItem = AddChild(root, settingsLabel, ref id);
- AddValueItem(settingsItem, "Update Mode", settings.updateMode, ref id);
- AddValueItem(settingsItem, "Compensate For Screen Orientation", settings.compensateForScreenOrientation, ref id);
- AddValueItem(settingsItem, "Default Button Press Point", settings.defaultButtonPressPoint, ref id);
- AddValueItem(settingsItem, "Default Deadzone Min", settings.defaultDeadzoneMin, ref id);
- AddValueItem(settingsItem, "Default Deadzone Max", settings.defaultDeadzoneMax, ref id);
- AddValueItem(settingsItem, "Default Tap Time", settings.defaultTapTime, ref id);
- AddValueItem(settingsItem, "Default Slow Tap Time", settings.defaultSlowTapTime, ref id);
- AddValueItem(settingsItem, "Default Hold Time", settings.defaultHoldTime, ref id);
- if (settings.supportedDevices.Count > 0)
- {
- var supportedDevices = AddChild(settingsItem, "Supported Devices", ref id);
- foreach (var item in settings.supportedDevices)
- {
- var icon = EditorInputControlLayoutCache.GetIconForLayout(item);
- AddChild(supportedDevices, item, ref id, icon);
- }
- }
- settingsItem.children.Sort((a, b) => string.Compare(a.displayName, b.displayName, StringComparison.InvariantCultureIgnoreCase));
-
- // Metrics.
- var metrics = InputSystem.metrics;
- metricsItem = AddChild(root, "Metrics", ref id);
- AddChild(metricsItem,
- "Current State Size in Bytes: " + StringHelpers.NicifyMemorySize(metrics.currentStateSizeInBytes),
- ref id);
- AddValueItem(metricsItem, "Current Control Count", metrics.currentControlCount, ref id);
- AddValueItem(metricsItem, "Current Layout Count", metrics.currentLayoutCount, ref id);
-
- return root;
- }
-
- private void AddUser(TreeViewItem parent, InputUser user, ref int id)
- {
- ////REVIEW: can we get better identification? allow associating GameObject with user?
- var userItem = AddChild(parent, "User #" + user.index, ref id);
-
- // Control scheme.
- var controlScheme = user.controlScheme;
- if (controlScheme != null)
- AddChild(userItem, "Control Scheme: " + controlScheme, ref id);
-
- // Paired and lost devices.
- AddDeviceListToUser("Paired Devices", user.pairedDevices, ref id, userItem);
- AddDeviceListToUser("Lost Devices", user.lostDevices, ref id, userItem);
-
- // Actions.
- var actions = user.actions;
- if (actions != null)
- {
- var actionsItem = AddChild(userItem, "Actions", ref id);
- foreach (var action in actions)
- AddActionItem(actionsItem, action, ref id);
-
- parent.children?.Sort((a, b) => string.Compare(a.displayName, b.displayName, StringComparison.CurrentCultureIgnoreCase));
- }
- }
-
- private void AddDeviceListToUser(string title, ReadOnlyArray<InputDevice> devices, ref int id, TreeViewItem userItem)
- {
- if (devices.Count == 0)
- return;
-
- var devicesItem = AddChild(userItem, title, ref id);
- foreach (var device in devices)
- {
- Debug.Assert(device != null, title + " has a null item!");
- if (device == null)
- continue;
-
- var item = new DeviceItem
- {
- id = id++,
- depth = devicesItem.depth + 1,
- displayName = device.ToString(),
- device = device,
- icon = EditorInputControlLayoutCache.GetIconForLayout(device.layout),
- };
- devicesItem.AddChild(item);
- }
- }
-
- private static void AddDevices(TreeViewItem parent, IEnumerable<InputDevice> devices, ref int id, int participantId = InputDevice.kLocalParticipantId)
- {
- foreach (var device in devices)
- {
- if (device.m_ParticipantId != participantId)
- continue;
-
- var displayName = device.name;
- if (device.usages.Count > 0)
- displayName += " (" + string.Join(",", device.usages) + ")";
-
- var item = new DeviceItem
- {
- id = id++,
- depth = parent.depth + 1,
- displayName = displayName,
- device = device,
- icon = EditorInputControlLayoutCache.GetIconForLayout(device.layout),
- };
- parent.AddChild(item);
- }
-
- parent.children?.Sort((a, b) => string.Compare(a.displayName, b.displayName));
- }
-
- private void AddControlLayouts(TreeViewItem parent, ref int id)
- {
- // Split root into three different groups:
- // 1) Control layouts
- // 2) Device layouts that don't match specific products
- // 3) Device layouts that match specific products
-
- var controls = AddChild(parent, "Controls", ref id);
- var devices = AddChild(parent, "Abstract Devices", ref id);
- var products = AddChild(parent, "Specific Devices", ref id);
-
- foreach (var layout in EditorInputControlLayoutCache.allControlLayouts)
- AddControlLayoutItem(layout, controls, ref id);
- foreach (var layout in EditorInputControlLayoutCache.allDeviceLayouts)
- AddControlLayoutItem(layout, devices, ref id);
- foreach (var layout in EditorInputControlLayoutCache.allProductLayouts)
- {
- var rootBaseLayoutName = InputControlLayout.s_Layouts.GetRootLayoutName(layout.name).ToString();
- var groupName = string.IsNullOrEmpty(rootBaseLayoutName) ? "Other" : rootBaseLayoutName + "s";
-
- var group = products.children?.FirstOrDefault(x => x.displayName == groupName);
- if (group == null)
- {
- group = AddChild(products, groupName, ref id);
- if (!string.IsNullOrEmpty(rootBaseLayoutName))
- group.icon = EditorInputControlLayoutCache.GetIconForLayout(rootBaseLayoutName);
- }
-
- AddControlLayoutItem(layout, group, ref id);
- }
-
- controls.children?.Sort((a, b) => string.Compare(a.displayName, b.displayName));
- devices.children?.Sort((a, b) => string.Compare(a.displayName, b.displayName));
-
- if (products.children != null)
- {
- products.children.Sort((a, b) => string.Compare(a.displayName, b.displayName));
- foreach (var productGroup in products.children)
- productGroup.children.Sort((a, b) => string.Compare(a.displayName, b.displayName));
- }
- }
-
- private TreeViewItem AddControlLayoutItem(InputControlLayout layout, TreeViewItem parent, ref int id)
- {
- var item = new LayoutItem
- {
- parent = parent,
- depth = parent.depth + 1,
- id = id++,
- displayName = layout.displayName ?? layout.name,
- layoutName = layout.name,
- };
- item.icon = EditorInputControlLayoutCache.GetIconForLayout(layout.name);
- parent.AddChild(item);
-
- // Header.
- AddChild(item, "Type: " + layout.type?.Name, ref id);
- if (!string.IsNullOrEmpty(layout.m_DisplayName))
- AddChild(item, "Display Name: " + layout.m_DisplayName, ref id);
- if (!string.IsNullOrEmpty(layout.name))
- AddChild(item, "Name: " + layout.name, ref id);
- var baseLayouts = StringHelpers.Join(layout.baseLayouts, ", ");
- if (!string.IsNullOrEmpty(baseLayouts))
- AddChild(item, "Extends: " + baseLayouts, ref id);
- if (layout.stateFormat != 0)
- AddChild(item, "Format: " + layout.stateFormat, ref id);
- if (layout.m_UpdateBeforeRender != null)
- {
- var value = layout.m_UpdateBeforeRender.Value ? "Update" : "Disabled";
- AddChild(item, "Before Render: " + value, ref id);
- }
- if (layout.commonUsages.Count > 0)
- {
- AddChild(item,
- "Common Usages: " +
- string.Join(", ", layout.commonUsages.Select(x => x.ToString()).ToArray()),
- ref id);
- }
- if (layout.appliedOverrides.Count() > 0)
- {
- AddChild(item,
- "Applied Overrides: " +
- string.Join(", ", layout.appliedOverrides),
- ref id);
- }
-
- ////TODO: find a more elegant solution than multiple "Matching Devices" parents when having multiple
- //// matchers
- // Device matchers.
- foreach (var matcher in EditorInputControlLayoutCache.GetDeviceMatchers(layout.name))
- {
- var node = AddChild(item, "Matching Devices", ref id);
- foreach (var pattern in matcher.patterns)
- AddChild(node, $"{pattern.Key} => \"{pattern.Value}\"", ref id);
- }
-
- // Controls.
- if (layout.controls.Count > 0)
- {
- var controls = AddChild(item, "Controls", ref id);
- foreach (var control in layout.controls)
- AddControlItem(control, controls, ref id);
-
- controls.children.Sort((a, b) => string.Compare(a.displayName, b.displayName));
- }
-
- return item;
- }
-
- private void AddControlItem(InputControlLayout.ControlItem control, TreeViewItem parent, ref int id)
- {
- var item = AddChild(parent, control.variants.IsEmpty() ? control.name : string.Format("{0} ({1})",
- control.name, control.variants), ref id);
-
- if (!control.layout.IsEmpty())
- item.icon = EditorInputControlLayoutCache.GetIconForLayout(control.layout);
-
- ////TODO: fully merge TreeViewItems from isModifyingExistingControl control layouts into the control they modify
-
- ////TODO: allow clicking this field to jump to the layout
- if (!control.layout.IsEmpty())
- AddChild(item, $"Layout: {control.layout}", ref id);
- if (!control.variants.IsEmpty())
- AddChild(item, $"Variant: {control.variants}", ref id);
- if (!string.IsNullOrEmpty(control.displayName))
- AddChild(item, $"Display Name: {control.displayName}", ref id);
- if (!string.IsNullOrEmpty(control.shortDisplayName))
- AddChild(item, $"Short Display Name: {control.shortDisplayName}", ref id);
- if (control.format != 0)
- AddChild(item, $"Format: {control.format}", ref id);
- if (control.offset != InputStateBlock.InvalidOffset)
- AddChild(item, $"Offset: {control.offset}", ref id);
- if (control.bit != InputStateBlock.InvalidOffset)
- AddChild(item, $"Bit: {control.bit}", ref id);
- if (control.sizeInBits != 0)
- AddChild(item, $"Size In Bits: {control.sizeInBits}", ref id);
- if (control.isArray)
- AddChild(item, $"Array Size: {control.arraySize}", ref id);
- if (!string.IsNullOrEmpty(control.useStateFrom))
- AddChild(item, $"Use State From: {control.useStateFrom}", ref id);
- if (!control.defaultState.isEmpty)
- AddChild(item, $"Default State: {control.defaultState.ToString()}", ref id);
- if (!control.minValue.isEmpty)
- AddChild(item, $"Min Value: {control.minValue.ToString()}", ref id);
- if (!control.maxValue.isEmpty)
- AddChild(item, $"Max Value: {control.maxValue.ToString()}", ref id);
-
- if (control.usages.Count > 0)
- AddChild(item, "Usages: " + string.Join(", ", control.usages.Select(x => x.ToString()).ToArray()), ref id);
- if (control.aliases.Count > 0)
- AddChild(item, "Aliases: " + string.Join(", ", control.aliases.Select(x => x.ToString()).ToArray()), ref id);
-
- if (control.isNoisy || control.isSynthetic)
- {
- var flags = "Flags: ";
- if (control.isNoisy)
- flags += "Noisy";
- if (control.isSynthetic)
- {
- if (control.isNoisy)
- flags += ", Synthetic";
- else
- flags += "Synthetic";
- }
- AddChild(item, flags, ref id);
- }
-
- if (control.parameters.Count > 0)
- {
- var parameters = AddChild(item, "Parameters", ref id);
- foreach (var parameter in control.parameters)
- AddChild(parameters, parameter.ToString(), ref id);
- }
-
- if (control.processors.Count > 0)
- {
- var processors = AddChild(item, "Processors", ref id);
- foreach (var processor in control.processors)
- {
- var processorItem = AddChild(processors, processor.name, ref id);
- foreach (var parameter in processor.parameters)
- AddChild(processorItem, parameter.ToString(), ref id);
- }
- }
- }
-
- private void AddValueItem<TValue>(TreeViewItem parent, string name, TValue value, ref int id)
- {
- var item = new ConfigurationItem
- {
- id = id++,
- depth = parent.depth + 1,
- displayName = $"{name}: {value.ToString()}",
- name = name
- };
- parent.AddChild(item);
- }
-
- private void AddEnabledActions(TreeViewItem parent, ref int id)
- {
- foreach (var action in m_EnabledActions)
- {
- // If we have users, find out if the action is owned by a user. If so, don't display
- // it separately.
- var isOwnedByUser = false;
- foreach (var user in InputUser.all)
- {
- var userActions = user.actions;
- if (userActions != null && userActions.Contains(action))
- {
- isOwnedByUser = true;
- break;
- }
- }
-
- if (!isOwnedByUser)
- AddActionItem(parent, action, ref id);
- }
-
- parent.children?.Sort((a, b) => string.Compare(a.displayName, b.displayName, StringComparison.CurrentCultureIgnoreCase));
- }
-
- private unsafe void AddActionItem(TreeViewItem parent, InputAction action, ref int id)
- {
- // Add item for action.
- var name = action.actionMap != null ? $"{action.actionMap.name}/{action.name}" : action.name;
- if (!action.enabled)
- name += " (Disabled)";
- if (action.actionMap != null && action.actionMap.m_Asset != null)
- {
- name += $" ({action.actionMap.m_Asset.name})";
- }
- else
- {
- name += " (no asset)";
- }
-
- var item = AddChild(parent, name, ref id);
-
- // Grab state.
- var actionMap = action.GetOrCreateActionMap();
- actionMap.ResolveBindingsIfNecessary();
- var state = actionMap.m_State;
-
- // Add list of resolved controls.
- var actionIndex = action.m_ActionIndexInState;
- var totalBindingCount = state.totalBindingCount;
- for (var i = 0; i < totalBindingCount; ++i)
- {
- ref var bindingState = ref state.bindingStates[i];
- if (bindingState.actionIndex != actionIndex)
- continue;
- if (bindingState.isComposite)
- continue;
-
- var binding = state.GetBinding(i);
- var controlCount = bindingState.controlCount;
- var controlStartIndex = bindingState.controlStartIndex;
- for (var n = 0; n < controlCount; ++n)
- {
- var control = state.controls[controlStartIndex + n];
- var interactions =
- StringHelpers.Join(new[] {binding.effectiveInteractions, action.interactions}, ",");
-
- var text = control.path;
- if (!string.IsNullOrEmpty(interactions))
- {
- var namesAndParameters = NameAndParameters.ParseMultiple(interactions);
- text += " [";
- text += string.Join(",", namesAndParameters.Select(x => x.name));
- text += "]";
- }
-
- AddChild(item, text, ref id);
- }
- }
- }
-
- private TreeViewItem AddChild(TreeViewItem parent, string displayName, ref int id, Texture2D icon = null)
- {
- var item = new TreeViewItem
- {
- id = id++,
- depth = parent.depth + 1,
- displayName = displayName,
- icon = icon,
- };
- parent.AddChild(item);
- return item;
- }
-
- private List<InputDeviceDescription> m_UnsupportedDevices;
- private List<InputAction> m_EnabledActions = new List<InputAction>();
-
- private class DeviceItem : TreeViewItem
- {
- public InputDevice device;
- }
-
- private class UnsupportedDeviceItem : TreeViewItem
- {
- public InputDeviceDescription description;
- }
-
- private class ConfigurationItem : TreeViewItem
- {
- public string name;
- }
-
- private class LayoutItem : TreeViewItem
- {
- public InternedString layoutName;
- }
- }
- }
- }
- #endif // UNITY_EDITOR
|