com.alicizax.unity.ui.exten.../Editor/UX/Controller/UXBindingEditor.cs

417 lines
18 KiB
C#
Raw Normal View History

#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
namespace AlicizaX.UI
{
[CustomEditor(typeof(UXBinding))]
public sealed class UXBindingEditor : UnityEditor.Editor
{
private SerializedProperty _controllerProp;
private SerializedProperty _entriesProp;
private readonly Dictionary<int, bool> _foldouts = new Dictionary<int, bool>();
private readonly List<UXBindingProperty> _supportedProperties = new List<UXBindingProperty>();
private void OnEnable()
{
_controllerProp = serializedObject.FindProperty("_controller");
_entriesProp = serializedObject.FindProperty("_entries");
}
public override void OnInspectorGUI()
{
serializedObject.Update();
var binding = (UXBinding)target;
DrawHeader(binding);
EditorGUILayout.Space(6f);
DrawControllerField(binding);
EditorGUILayout.Space(6f);
DrawEntries(binding);
serializedObject.ApplyModifiedProperties();
}
private void DrawHeader(UXBinding binding)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("UX Binding", EditorStyles.boldLabel);
EditorGUILayout.LabelField($"Target: {binding.gameObject.name}", EditorStyles.miniLabel);
EditorGUILayout.LabelField($"Rules: {_entriesProp.arraySize}", EditorStyles.miniLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Add Rule"))
{
AddEntry(binding);
}
if (GUILayout.Button("Capture Defaults"))
{
binding.CaptureDefaults();
EditorUtility.SetDirty(binding);
}
if (GUILayout.Button("Reset To Defaults"))
{
binding.ResetToDefaults();
EditorUtility.SetDirty(binding);
SceneView.RepaintAll();
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
private void DrawControllerField(UXBinding binding)
{
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.LabelField("Controller", EditorStyles.boldLabel);
EditorGUI.BeginChangeCheck();
UXController newController = (UXController)EditorGUILayout.ObjectField("Reference", binding.Controller, typeof(UXController), true);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(binding, "Change UX Binding Controller");
binding.SetController(newController);
_controllerProp.objectReferenceValue = newController;
EditorUtility.SetDirty(binding);
}
if (binding.Controller == null)
{
EditorGUILayout.HelpBox("Assign a UXController on this object or one of its parents.", MessageType.Warning);
}
else
{
EditorGUILayout.LabelField($"Bound To: {binding.Controller.name}", EditorStyles.miniLabel);
}
EditorGUILayout.EndVertical();
}
private void DrawEntries(UXBinding binding)
{
if (_entriesProp.arraySize == 0)
{
EditorGUILayout.HelpBox("No rules yet. Add one and choose a controller + property pair.", MessageType.Info);
return;
}
UXBindingPropertyUtility.GetSupportedProperties(binding.gameObject, _supportedProperties);
for (int i = 0; i < _entriesProp.arraySize; i++)
{
DrawEntry(binding, i);
EditorGUILayout.Space(4f);
}
}
private void DrawEntry(UXBinding binding, int index)
{
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
UXBindingProperty property = (UXBindingProperty)propertyProp.enumValueIndex;
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
string label = UXBindingPropertyUtility.GetMetadata(property).DisplayName;
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {label}", true);
GUILayout.FlexibleSpace();
if (GUILayout.Button("Preview", EditorStyles.miniButtonLeft, GUILayout.Width(60f)))
{
binding.PreviewEntry(index);
SceneView.RepaintAll();
}
if (GUILayout.Button("X", EditorStyles.miniButtonRight, GUILayout.Width(24f)))
{
DeleteEntry(binding, index);
}
EditorGUILayout.EndHorizontal();
_foldouts[index] = expanded;
if (expanded)
{
DrawControllerSelector(binding, controllerIdProp, controllerIndexProp);
DrawPropertySelector(entryProp, binding.gameObject, propertyProp);
property = (UXBindingProperty)propertyProp.enumValueIndex;
UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(property);
EditorGUILayout.Space(2f);
EditorGUILayout.LabelField("Value", EditorStyles.boldLabel);
DrawValueField(valueProp, metadata, "Matched Value");
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Use Current"))
{
binding.CaptureEntryValue(index);
EditorUtility.SetDirty(binding);
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space(2f);
EditorGUILayout.PropertyField(fallbackModeProp, new GUIContent("Fallback"));
UXBindingFallbackMode fallbackMode = (UXBindingFallbackMode)fallbackModeProp.enumValueIndex;
if (fallbackMode == UXBindingFallbackMode.UseCustomValue)
{
DrawValueField(fallbackValueProp, metadata, "Fallback Value");
if (GUILayout.Button("Use Current As Fallback"))
{
binding.CaptureEntryFallbackValue(index);
EditorUtility.SetDirty(binding);
}
}
if (!_supportedProperties.Contains(property))
{
EditorGUILayout.HelpBox("This property is not supported by the components on the current GameObject.", MessageType.Error);
}
}
EditorGUILayout.EndVertical();
}
private void DrawControllerSelector(UXBinding binding, SerializedProperty controllerIdProp, SerializedProperty controllerIndexProp)
{
UXController controller = binding.Controller;
if (controller == null || controller.Controllers.Count == 0)
{
EditorGUILayout.HelpBox("Create a controller definition first.", MessageType.Info);
return;
}
string[] names = new string[controller.Controllers.Count];
int selectedController = 0;
for (int i = 0; i < controller.Controllers.Count; i++)
{
UXController.ControllerDefinition definition = controller.Controllers[i];
names[i] = definition.Name;
if (definition.Id == controllerIdProp.stringValue)
{
selectedController = i;
}
}
EditorGUI.BeginChangeCheck();
selectedController = EditorGUILayout.Popup("Controller", selectedController, names);
if (EditorGUI.EndChangeCheck())
{
controllerIdProp.stringValue = controller.Controllers[selectedController].Id;
controllerIndexProp.intValue = 0;
}
UXController.ControllerDefinition selectedDefinition = controller.Controllers[selectedController];
int maxIndex = Mathf.Max(1, selectedDefinition.Length);
controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, maxIndex - 1);
string[] indexNames = new string[maxIndex];
for (int i = 0; i < maxIndex; i++)
{
indexNames[i] = i.ToString();
}
controllerIndexProp.intValue = EditorGUILayout.Popup("Index", controllerIndexProp.intValue, indexNames);
}
private void DrawPropertySelector(SerializedProperty entryProp, GameObject targetObject, SerializedProperty propertyProp)
{
var options = new List<UXBindingProperty>(_supportedProperties);
UXBindingProperty current = (UXBindingProperty)propertyProp.enumValueIndex;
if (!options.Contains(current))
{
options.Add(current);
}
string[] displayNames = new string[options.Count];
int selectedIndex = 0;
for (int i = 0; i < options.Count; i++)
{
UXBindingProperty option = options[i];
UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(option);
bool supported = UXBindingPropertyUtility.IsSupported(targetObject, option);
displayNames[i] = supported ? metadata.DisplayName : $"{metadata.DisplayName} (Unsupported)";
if (option == current)
{
selectedIndex = i;
}
}
EditorGUI.BeginChangeCheck();
selectedIndex = EditorGUILayout.Popup("Property", selectedIndex, displayNames);
if (EditorGUI.EndChangeCheck())
{
propertyProp.enumValueIndex = (int)options[selectedIndex];
entryProp.FindPropertyRelative("_hasCapturedDefault").boolValue = false;
entryProp.FindPropertyRelative("_capturedProperty").enumValueIndex = propertyProp.enumValueIndex;
ResetValue(entryProp.FindPropertyRelative("_capturedDefault"));
ApplyDefaultFallbackForProperty(entryProp, (UXBindingProperty)propertyProp.enumValueIndex);
}
}
private void DrawValueField(SerializedProperty valueProp, UXBindingPropertyMetadata metadata, string label)
{
switch (metadata.ValueKind)
{
case UXBindingValueKind.Boolean:
{
SerializedProperty boolProp = valueProp.FindPropertyRelative("_boolValue");
boolProp.boolValue = EditorGUILayout.Toggle(label, boolProp.boolValue);
break;
}
case UXBindingValueKind.Float:
{
SerializedProperty floatProp = valueProp.FindPropertyRelative("_floatValue");
floatProp.floatValue = EditorGUILayout.FloatField(label, floatProp.floatValue);
break;
}
case UXBindingValueKind.String:
{
SerializedProperty stringProp = valueProp.FindPropertyRelative("_stringValue");
EditorGUILayout.LabelField(label);
stringProp.stringValue = EditorGUILayout.TextArea(stringProp.stringValue, GUILayout.MinHeight(54f));
break;
}
case UXBindingValueKind.Color:
{
SerializedProperty colorProp = valueProp.FindPropertyRelative("_colorValue");
colorProp.colorValue = EditorGUILayout.ColorField(label, colorProp.colorValue);
break;
}
case UXBindingValueKind.Vector2:
{
SerializedProperty vector2Prop = valueProp.FindPropertyRelative("_vector2Value");
vector2Prop.vector2Value = EditorGUILayout.Vector2Field(label, vector2Prop.vector2Value);
break;
}
case UXBindingValueKind.Vector3:
{
SerializedProperty vector3Prop = valueProp.FindPropertyRelative("_vector3Value");
vector3Prop.vector3Value = EditorGUILayout.Vector3Field(label, vector3Prop.vector3Value);
break;
}
case UXBindingValueKind.ObjectReference:
{
SerializedProperty objectProp = valueProp.FindPropertyRelative("_objectValue");
objectProp.objectReferenceValue = EditorGUILayout.ObjectField(
label,
objectProp.objectReferenceValue,
metadata.ObjectReferenceType,
false);
break;
}
}
}
private void AddEntry(UXBinding binding)
{
int index = _entriesProp.arraySize;
_entriesProp.InsertArrayElementAtIndex(index);
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
SerializedProperty capturedDefaultProp = entryProp.FindPropertyRelative("_capturedDefault");
SerializedProperty hasCapturedDefaultProp = entryProp.FindPropertyRelative("_hasCapturedDefault");
SerializedProperty capturedPropertyProp = entryProp.FindPropertyRelative("_capturedProperty");
controllerIdProp.stringValue = string.Empty;
controllerIndexProp.intValue = 0;
propertyProp.enumValueIndex = (int)UXBindingProperty.GameObjectActive;
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault;
ResetValue(valueProp);
ResetValue(fallbackValueProp);
ResetValue(capturedDefaultProp);
hasCapturedDefaultProp.boolValue = false;
capturedPropertyProp.enumValueIndex = (int)UXBindingProperty.GameObjectActive;
ApplyDefaultFallbackForProperty(entryProp, UXBindingProperty.GameObjectActive);
if (binding.Controller != null && binding.Controller.Controllers.Count > 0)
{
controllerIdProp.stringValue = binding.Controller.Controllers[0].Id;
}
_foldouts[index] = true;
}
private static void ResetValue(SerializedProperty valueProp)
{
valueProp.FindPropertyRelative("_boolValue").boolValue = false;
valueProp.FindPropertyRelative("_floatValue").floatValue = 0f;
valueProp.FindPropertyRelative("_stringValue").stringValue = string.Empty;
valueProp.FindPropertyRelative("_colorValue").colorValue = Color.white;
valueProp.FindPropertyRelative("_vector2Value").vector2Value = Vector2.zero;
valueProp.FindPropertyRelative("_vector3Value").vector3Value = Vector3.zero;
valueProp.FindPropertyRelative("_objectValue").objectReferenceValue = null;
}
private void DeleteEntry(UXBinding binding, int index)
{
if (index < 0 || index >= _entriesProp.arraySize)
{
return;
}
Undo.RecordObject(binding, "Delete UX Binding Rule");
_entriesProp.DeleteArrayElementAtIndex(index);
CleanupFoldouts(index);
serializedObject.ApplyModifiedProperties();
EditorUtility.SetDirty(binding);
GUIUtility.ExitGUI();
}
private void CleanupFoldouts(int removedIndex)
{
_foldouts.Remove(removedIndex);
var remapped = new Dictionary<int, bool>();
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 ApplyDefaultFallbackForProperty(SerializedProperty entryProp, UXBindingProperty property)
{
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
if (property == UXBindingProperty.GameObjectActive)
{
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.UseCustomValue;
fallbackValueProp.FindPropertyRelative("_boolValue").boolValue = false;
return;
}
if ((UXBindingFallbackMode)fallbackModeProp.enumValueIndex == UXBindingFallbackMode.UseCustomValue)
{
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault;
ResetValue(fallbackValueProp);
}
}
}
}
#endif