using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEditor; using UnityEngine; namespace UltraCombos { [CustomEditor(typeof(DebugSettingsGUI))] public class DebugSettingsGUIEditor : Editor { // ── Supported field types ───────────────────────────────────────────────── private static readonly HashSet supportedTypes = new() { typeof(bool), typeof(int), typeof(float), typeof(string), typeof(Vector2), typeof(Vector3), typeof(Color) }; private static bool IsSupported(Type t) => supportedTypes.Contains(t) || t.IsEnum; // ── Cached SerializedProperties ─────────────────────────────────────────── private SerializedProperty toggleKeyProp; private SerializedProperty initialRectProp; private void OnEnable() { toggleKeyProp = serializedObject.FindProperty("toggleKey"); initialRectProp = serializedObject.FindProperty("initialRect"); } // ── Inspector ───────────────────────────────────────────────────────────── public override void OnInspectorGUI() { var gui = (DebugSettingsGUI)target; serializedObject.Update(); EditorGUILayout.LabelField("General", EditorStyles.boldLabel); EditorGUILayout.PropertyField(toggleKeyProp, new GUIContent("Toggle Key")); EditorGUILayout.PropertyField(initialRectProp, new GUIContent("Initial Rect")); serializedObject.ApplyModifiedProperties(); EditorGUILayout.Space(10); EditorGUILayout.LabelField("Entries", EditorStyles.boldLabel); for (var i = 0; i < gui.entries.Count; i++) { var removed = DrawEntry(gui, gui.entries[i], i); if (removed) { Undo.RecordObject(gui, "Remove Entry"); gui.entries.RemoveAt(i); EditorUtility.SetDirty(gui); i--; } EditorGUILayout.Space(2); } EditorGUILayout.Space(4); if (GUILayout.Button("+ Add Entry", GUILayout.Height(26))) { Undo.RecordObject(gui, "Add Entry"); gui.entries.Add(new DebugSettingsGUI.Entry()); EditorUtility.SetDirty(gui); } } // ── Draw one Entry — returns true when the user clicked Remove ───────────── private static bool DrawEntry(DebugSettingsGUI gui, DebugSettingsGUI.Entry entry, int index) { var removed = false; using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { // Header row using (new EditorGUILayout.HorizontalScope()) { var title = string.IsNullOrEmpty(entry.groupLabel) ? $"Entry {index}" : $"Entry {index} — {entry.groupLabel}"; EditorGUILayout.LabelField(title, EditorStyles.boldLabel); GUILayout.FlexibleSpace(); if (GUILayout.Button("✕", GUILayout.Width(22), GUILayout.Height(18))) { removed = true; } } // Group label EditorGUI.BeginChangeCheck(); var newLabel = EditorGUILayout.TextField("Group Label", entry.groupLabel); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(gui, "Edit Group Label"); entry.groupLabel = newLabel; EditorUtility.SetDirty(gui); } // Target object. // Drag any Component or ScriptableObject here. // Dropping a GameObject picks its first non-Transform Component automatically. EditorGUI.BeginChangeCheck(); var newTarget = EditorGUILayout.ObjectField( new GUIContent("Target", "Drag any Component or ScriptableObject here.\n" + "Dropping a GameObject picks its first non-Transform Component."), entry.target, typeof(UnityEngine.Object), allowSceneObjects: true); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(gui, "Set Target"); if (newTarget is GameObject go) { var comps = go.GetComponents(); newTarget = comps.FirstOrDefault(c => c is not Transform) ?? comps.FirstOrDefault() ?? (UnityEngine.Object)go; } entry.target = newTarget; entry.fields.Clear(); EditorUtility.SetDirty(gui); } // Field selection if (entry.target != null) { DrawFieldSelection(gui, entry); } } return removed; } // ── Field checkboxes ────────────────────────────────────────────────────── private static void DrawFieldSelection(DebugSettingsGUI gui, DebugSettingsGUI.Entry entry) { var type = entry.target.GetType(); var fields = CollectFields(type); if (fields.Count == 0) { EditorGUILayout.HelpBox( "No displayable fields found on this object.\n" + "Supported types: bool · int · float · string · Vector2 · Vector3 · Color · Enum\n" + "Fields must be public or marked [SerializeField].", MessageType.Info); return; } EditorGUILayout.Space(4); // Select All / None using (new EditorGUILayout.HorizontalScope()) { EditorGUILayout.LabelField("Fields to Display", EditorStyles.boldLabel); GUILayout.FlexibleSpace(); if (GUILayout.Button("All", GUILayout.Width(36))) { Undo.RecordObject(gui, "Select All Fields"); entry.fields = fields.Select(f => f.Name).ToList(); EditorUtility.SetDirty(gui); } if (GUILayout.Button("None", GUILayout.Width(38))) { Undo.RecordObject(gui, "Deselect All Fields"); entry.fields.Clear(); EditorUtility.SetDirty(gui); } } // Per-field toggles EditorGUI.indentLevel++; foreach (var fi in fields) { var isSelected = entry.fields.Contains(fi.Name); var niceName = ObjectNames.NicifyVariableName(fi.Name); var typeName = FriendlyType(fi.FieldType); var hasRange = fi.GetCustomAttribute() != null; var label = new GUIContent( $"{niceName} ({typeName}{(hasRange ? ", Range" : string.Empty)})", tooltip: $"Field: {fi.Name}\nType: {fi.FieldType.FullName}"); EditorGUI.BeginChangeCheck(); var next = EditorGUILayout.ToggleLeft(label, isSelected); if (EditorGUI.EndChangeCheck()) { Undo.RecordObject(gui, next ? "Add Field" : "Remove Field"); if (next) { entry.fields.Add(fi.Name); } else { entry.fields.Remove(fi.Name); } EditorUtility.SetDirty(gui); } } EditorGUI.indentLevel--; } // ── Reflection helpers ──────────────────────────────────────────────────── private static List CollectFields(Type type) { const BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; var result = new List(); for (var t = type; t != null && t != typeof(UnityEngine.Object); t = t.BaseType) { foreach (var fi in t.GetFields(flags | BindingFlags.DeclaredOnly)) { var exposed = fi.IsPublic || fi.GetCustomAttribute() != null; if (!exposed) { continue; } if (fi.GetCustomAttribute() != null) { continue; } if (!IsSupported(fi.FieldType)) { continue; } if (!result.Any(r => r.Name == fi.Name)) { result.Add(fi); } } } return result; } private static string FriendlyType(Type t) { if (t == typeof(float)) { return "float"; } if (t == typeof(int)) { return "int"; } if (t == typeof(bool)) { return "bool"; } if (t == typeof(string)) { return "string"; } if (t == typeof(Vector2)) { return "Vector2"; } if (t == typeof(Vector3)) { return "Vector3"; } if (t == typeof(Color)) { return "Color"; } if (t.IsEnum) { return t.Name; } return t.Name; } } }