#if UNITY_EDITOR using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace UnityEngine.UI { [CustomEditor(typeof(UXController))] public sealed class UXControllerEditor : UnityEditor.Editor { private SerializedProperty _controllersProp; private SerializedProperty _bindingsProp; private readonly Dictionary _foldouts = new Dictionary(); private void OnEnable() { _controllersProp = serializedObject.FindProperty("_controllers"); _bindingsProp = serializedObject.FindProperty("_bindings"); } public override void OnInspectorGUI() { serializedObject.Update(); var controller = (UXController)target; DrawHeader(controller); EditorGUILayout.Space(6f); DrawControllers(controller); EditorGUILayout.Space(6f); DrawBindings(); serializedObject.ApplyModifiedProperties(); } private void DrawHeader(UXController controller) { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("UX Controller", EditorStyles.boldLabel); EditorGUILayout.LabelField($"Controllers: {_controllersProp.arraySize}", EditorStyles.miniLabel); EditorGUILayout.LabelField($"Bindings: {_bindingsProp.arraySize}", EditorStyles.miniLabel); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Add Controller")) { AddController(); } if (GUILayout.Button("Reset Preview")) { controller.ResetAllControllers(); EditorUtility.SetDirty(controller); SceneView.RepaintAll(); } EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } private void DrawControllers(UXController controller) { if (_controllersProp.arraySize == 0) { EditorGUILayout.HelpBox("No controllers yet. Add one to start driving bindings.", MessageType.Info); return; } for (int i = 0; i < _controllersProp.arraySize; i++) { DrawController(controller, i); EditorGUILayout.Space(4f); } } private void DrawController(UXController controller, int index) { SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index); SerializedProperty idProp = entryProp.FindPropertyRelative("_id"); SerializedProperty nameProp = entryProp.FindPropertyRelative("_name"); SerializedProperty lengthProp = entryProp.FindPropertyRelative("_length"); SerializedProperty descriptionProp = entryProp.FindPropertyRelative("_description"); bool expanded = _foldouts.ContainsKey(index) && _foldouts[index]; EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(); expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {nameProp.stringValue}", true); GUILayout.FlexibleSpace(); if (GUILayout.Button("Up", EditorStyles.miniButtonLeft, GUILayout.Width(34f)) && index > 0) { _controllersProp.MoveArrayElement(index, index - 1); GUIUtility.ExitGUI(); } if (GUILayout.Button("Down", EditorStyles.miniButtonMid, GUILayout.Width(44f)) && index < _controllersProp.arraySize - 1) { _controllersProp.MoveArrayElement(index, index + 1); GUIUtility.ExitGUI(); } if (GUILayout.Button("X", EditorStyles.miniButtonRight, GUILayout.Width(24f))) { DeleteController(controller, index); } EditorGUILayout.EndHorizontal(); _foldouts[index] = expanded; if (expanded) { EditorGUILayout.PropertyField(nameProp, new GUIContent("Name")); lengthProp.intValue = Mathf.Max(1, EditorGUILayout.IntField("Length", Mathf.Max(1, lengthProp.intValue))); EditorGUILayout.PropertyField(descriptionProp, new GUIContent("Description")); EditorGUI.BeginDisabledGroup(true); EditorGUILayout.TextField("Id", idProp.stringValue); EditorGUI.EndDisabledGroup(); UXController.ControllerDefinition definition = controller.GetControllerAt(index); if (definition != null) { DrawPreview(controller, definition); } } EditorGUILayout.EndVertical(); } private void DrawPreview(UXController controller, UXController.ControllerDefinition definition) { EditorGUILayout.Space(4f); EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel); int currentIndex = Mathf.Max(0, definition.SelectedIndex); int length = Mathf.Max(1, definition.Length); string[] options = new string[length]; for (int i = 0; i < length; i++) { options[i] = i.ToString(); } EditorGUI.BeginChangeCheck(); int newIndex = GUILayout.SelectionGrid(currentIndex, options, Mathf.Min(length, 6)); if (EditorGUI.EndChangeCheck()) { controller.SetControllerIndex(definition.Id, newIndex); EditorUtility.SetDirty(controller); SceneView.RepaintAll(); } } private void DrawBindings() { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("Registered Bindings", EditorStyles.boldLabel); if (_bindingsProp.arraySize == 0) { EditorGUILayout.HelpBox("No UXBinding components are registered under this controller.", MessageType.Info); } else { for (int i = 0; i < _bindingsProp.arraySize; i++) { SerializedProperty bindingProp = _bindingsProp.GetArrayElementAtIndex(i); EditorGUI.BeginDisabledGroup(true); EditorGUILayout.ObjectField($"Binding {i}", bindingProp.objectReferenceValue, typeof(UXBinding), true); EditorGUI.EndDisabledGroup(); } } EditorGUILayout.EndVertical(); } private void AddController() { int index = _controllersProp.arraySize; _controllersProp.InsertArrayElementAtIndex(index); SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index); entryProp.FindPropertyRelative("_id").stringValue = string.Empty; entryProp.FindPropertyRelative("_name").stringValue = $"Controller {index + 1}"; entryProp.FindPropertyRelative("_length").intValue = 2; entryProp.FindPropertyRelative("_description").stringValue = string.Empty; _foldouts[index] = true; } private void DeleteController(UXController controller, int index) { if (index < 0 || index >= _controllersProp.arraySize) { return; } SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index); string deletedControllerId = entryProp.FindPropertyRelative("_id").stringValue; Undo.RecordObject(controller, "Delete UX Controller"); _controllersProp.DeleteArrayElementAtIndex(index); CleanupFoldouts(index); serializedObject.ApplyModifiedProperties(); ClearBindingReferences(controller, deletedControllerId); EditorUtility.SetDirty(controller); GUIUtility.ExitGUI(); } private void CleanupFoldouts(int removedIndex) { _foldouts.Remove(removedIndex); var remapped = new Dictionary(); foreach (var pair in _foldouts) { int nextIndex = pair.Key > removedIndex ? pair.Key - 1 : pair.Key; remapped[nextIndex] = pair.Value; } _foldouts.Clear(); foreach (var pair in remapped) { _foldouts[pair.Key] = pair.Value; } } private static void ClearBindingReferences(UXController controller, string deletedControllerId) { if (controller == null || string.IsNullOrEmpty(deletedControllerId)) { return; } IReadOnlyList bindings = controller.Bindings; for (int i = 0; i < bindings.Count; i++) { UXBinding binding = bindings[i]; if (binding == null) { continue; } var so = new SerializedObject(binding); SerializedProperty entriesProp = so.FindProperty("_entries"); bool changed = false; for (int entryIndex = 0; entryIndex < entriesProp.arraySize; entryIndex++) { SerializedProperty bindingEntry = entriesProp.GetArrayElementAtIndex(entryIndex); SerializedProperty controllerIdProp = bindingEntry.FindPropertyRelative("_controllerId"); if (controllerIdProp.stringValue == deletedControllerId) { controllerIdProp.stringValue = string.Empty; changed = true; } } if (changed) { so.ApplyModifiedProperties(); EditorUtility.SetDirty(binding); } } } } } #endif