using System; using System.Collections.Generic; using System.Reflection; using SepCore.InputModule.Runtime; using UnityEditor; using UnityEngine; using UnityEngine.InputSystem; namespace SepCore.InputModule.Editor { public sealed class InputModuleDebugger : EditorWindow { private static readonly FieldInfo ContextStackField = typeof(InputModuleComponent).GetField("_contextStack", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo ActionMapsField = typeof(InputModuleComponent).GetField("_actionMaps", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly PropertyInfo StackItemsProperty = typeof(InputContextStack).GetProperty("Items", BindingFlags.Instance | BindingFlags.Public); private static readonly PropertyInfo StackCountProperty = typeof(InputContextStack).GetProperty("Count", BindingFlags.Instance | BindingFlags.Public); private InputModuleComponent _target; private Vector2 _scrollPosition; private readonly Dictionary _mapFoldouts = new Dictionary(); private double _nextRefresh; private const double RefreshInterval = 0.1; [MenuItem("Window/Input Module Debugger")] public static void ShowWindow() { GetWindow("Input Module"); } private void OnGUI() { double now = EditorApplication.timeSinceStartup; if (now >= _nextRefresh) { _nextRefresh = now + RefreshInterval; Repaint(); } _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); DrawHeader(); DrawRuntimeStatus(); DrawContextStack(); DrawActionMaps(); EditorGUILayout.EndScrollView(); } private void DrawHeader() { EditorGUILayout.Space(4); EditorGUILayout.LabelField("Input Module Debugger", EditorStyles.largeLabel); EditorGUILayout.Space(4); } private void DrawRuntimeStatus() { if (_target == null) { _target = FindInputModuleComponent(); } if (_target == null) { EditorGUILayout.HelpBox("No InputModuleComponent found in the current scene.", MessageType.Warning); return; } EditorGUILayout.LabelField("Runtime Status", EditorStyles.boldLabel); EditorGUI.BeginDisabledGroup(true); EditorGUILayout.Toggle("Initialized", _target.IsInitialized); EditorGUILayout.EnumPopup("Device Kind", _target.CurrentDeviceKind); EditorGUILayout.EnumPopup("Current Context", _target.CurrentContext); EditorGUILayout.Toggle("Rebinding", _target.IsRebinding); EditorGUI.EndDisabledGroup(); EditorGUILayout.Space(8); } private void DrawContextStack() { if (_target == null || !_target.IsInitialized) { return; } object stack = ContextStackField?.GetValue(_target); if (stack == null) { return; } int count = StackCountProperty != null ? (int)StackCountProperty.GetValue(stack) : 0; IReadOnlyList items = StackItemsProperty?.GetValue(stack) as IReadOnlyList; EditorGUILayout.LabelField($"Context Stack (depth: {count})", EditorStyles.boldLabel); if (items == null || count == 0) { EditorGUILayout.LabelField(" (empty)", EditorStyles.miniLabel); } else { for (int i = 0; i < items.Count; i++) { bool isTop = i == items.Count - 1; string prefix = isTop ? "▶" : " "; GUIStyle style = isTop ? EditorStyles.label : EditorStyles.miniLabel; EditorGUILayout.LabelField($" {prefix} [{i}] {items[i]}", style); } } EditorGUILayout.Space(8); } private void DrawActionMaps() { if (_target == null || !_target.IsInitialized) { return; } object mapsObj = ActionMapsField?.GetValue(_target); if (!(mapsObj is Dictionary maps)) { return; } EditorGUILayout.LabelField("Action Maps", EditorStyles.boldLabel); foreach (KeyValuePair pair in maps) { InputContextId contextId = pair.Key; InputActionMap map = pair.Value; bool enabled = map?.enabled ?? false; bool isActive = contextId == InputContextId.Global || contextId == _target.CurrentContext; Color previousColor = GUI.color; if (enabled) { GUI.color = isActive ? Color.green : Color.yellow; } else { GUI.color = Color.gray; } if (!_mapFoldouts.ContainsKey(contextId)) { _mapFoldouts[contextId] = false; } _mapFoldouts[contextId] = EditorGUILayout.Foldout(_mapFoldouts[contextId], $"{contextId} [{StateLabel(enabled, isActive)}]", true); GUI.color = previousColor; if (_mapFoldouts[contextId] && map != null) { EditorGUI.indentLevel++; foreach (InputAction action in map.actions) { DrawAction(action); } EditorGUI.indentLevel--; } } } private static void DrawAction(InputAction action) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField(action.name, GUILayout.Width(120)); if (action.bindings.Count > 0) { var parts = new System.Collections.Generic.List(action.bindings.Count); foreach (InputBinding binding in action.bindings) { string path = string.IsNullOrEmpty(binding.overridePath) ? binding.path : binding.overridePath; bool hasOverride = !string.IsNullOrEmpty(binding.overridePath); string display = string.IsNullOrEmpty(path) ? "(none)" : path; if (hasOverride) { display += " *"; } parts.Add(display); } bool anyOverride = false; foreach (InputBinding binding in action.bindings) { if (!string.IsNullOrEmpty(binding.overridePath)) { anyOverride = true; break; } } GUIStyle style = anyOverride ? EditorStyles.boldLabel : EditorStyles.miniLabel; EditorGUILayout.LabelField(string.Join(" | ", parts), style); } else { EditorGUILayout.LabelField("(no bindings)", EditorStyles.miniLabel); } EditorGUILayout.EndHorizontal(); } private static string StateLabel(bool enabled, bool isActive) { if (!enabled) { return "disabled"; } return isActive ? "active" : "enabled"; } private static InputModuleComponent FindInputModuleComponent() { if (Application.isPlaying) { InputModuleComponent found = UnityEngine.Object.FindObjectOfType(); if (found != null) { return found; } } return null; } } }