vampire-like/Assets/Plugins/InputModule/Editor/InputModuleDebugger.cs

238 lines
8.1 KiB
C#

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<InputContextId, bool> _mapFoldouts = new Dictionary<InputContextId, bool>();
private double _nextRefresh;
private const double RefreshInterval = 0.1;
[MenuItem("Window/Input Module Debugger")]
public static void ShowWindow()
{
GetWindow<InputModuleDebugger>("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<InputContextId> items = StackItemsProperty?.GetValue(stack) as IReadOnlyList<InputContextId>;
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<InputContextId, InputActionMap> maps))
{
return;
}
EditorGUILayout.LabelField("Action Maps", EditorStyles.boldLabel);
foreach (KeyValuePair<InputContextId, InputActionMap> 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<string>(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<InputModuleComponent>();
if (found != null)
{
return found;
}
}
return null;
}
}
}