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
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;
|
|
}
|
|
}
|
|
}
|
|
|