You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

267 lines
10 KiB

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<Type> 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<Component>();
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<RangeAttribute>() != 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<FieldInfo> CollectFields(Type type)
{
const BindingFlags flags =
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var result = new List<FieldInfo>();
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<SerializeField>() != null;
if (!exposed)
{
continue;
}
if (fi.GetCustomAttribute<HideInInspector>() != 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;
}
}
}