diff --git a/Editor/UX/Controller/StateTypePopup.cs b/Editor/UX/Controller/StateTypePopup.cs deleted file mode 100644 index 143e141..0000000 --- a/Editor/UX/Controller/StateTypePopup.cs +++ /dev/null @@ -1,86 +0,0 @@ -#if UNITY_EDITOR -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using UnityEditor; -using UnityEditor.Experimental.GraphView; -using UnityEngine; -using AlicizaX.UI; -using AlicizaX.UI.Runtime; - -public class StateTypeSearchProvider : ScriptableObject, ISearchWindowProvider -{ - private Action _onSelect; - private List _entries; - - public void Init(Action onSelect) - { - _onSelect = onSelect; - BuildEntries(); - } - - private void BuildEntries() - { - _entries = new List(); - - _entries.Add(new SearchTreeGroupEntry(new GUIContent("States"))); - - var all = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(a => - { - try { return a.GetTypes(); } - catch { return new Type[0]; } - }) - .Where(t => typeof(ControllerStateBase).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract) - .Select(t => - { - var attr = t.GetCustomAttribute(); - var tag = attr != null ? attr.StateName : t.Name; - return (type: t, tag); - }) - .OrderBy(x => x.tag) - .ToArray(); - - - var groups = new Dictionary>(); - foreach (var item in all) - { - string[] parts = item.tag.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - string g = parts.Length > 0 ? parts[0] : "Other"; - string child = parts.Length > 1 ? string.Join("/", parts.Skip(1)) : (parts.Length == 1 ? parts[0] : item.type.Name); - string display = $"{child}"; - - if (!groups.TryGetValue(g, out var list)) { list = new List<(Type, string, string)>(); groups[g] = list; } - list.Add((item.type, item.tag, display)); - } - - foreach (var kv in groups.OrderBy(k => k.Key)) - { - _entries.Add(new SearchTreeGroupEntry(new GUIContent(kv.Key)) { level = 1 }); - foreach (var child in kv.Value.OrderBy(c => c.fullTag)) - { - var entry = new SearchTreeEntry(new GUIContent(child.display)) { level = 2, userData = child.type }; - _entries.Add(entry); - } - } - } - - public List CreateSearchTree(SearchWindowContext context) - { - if (_entries == null) BuildEntries(); - return _entries; - } - - public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context) - { - var t = entry.userData as Type; - if (t != null) - { - _onSelect?.Invoke(t); - return true; - } - return false; - } -} -#endif diff --git a/Editor/UX/Controller/StateTypePopup.cs.meta b/Editor/UX/Controller/StateTypePopup.cs.meta deleted file mode 100644 index 6001102..0000000 --- a/Editor/UX/Controller/StateTypePopup.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 37183e7bcfe8411fba14fb9eb6bdfe9d -timeCreated: 1764136414 \ No newline at end of file diff --git a/Editor/UX/Controller/UXBindingEditor.cs b/Editor/UX/Controller/UXBindingEditor.cs new file mode 100644 index 0000000..40b605e --- /dev/null +++ b/Editor/UX/Controller/UXBindingEditor.cs @@ -0,0 +1,416 @@ +#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 _foldouts = new Dictionary(); + private readonly List _supportedProperties = new List(); + + 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(_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(); + 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 diff --git a/Runtime/UXComponent/Controller/Property/CanvasGroupAlphaState.cs.meta b/Editor/UX/Controller/UXBindingEditor.cs.meta similarity index 83% rename from Runtime/UXComponent/Controller/Property/CanvasGroupAlphaState.cs.meta rename to Editor/UX/Controller/UXBindingEditor.cs.meta index a2f4a06..b607bc3 100644 --- a/Runtime/UXComponent/Controller/Property/CanvasGroupAlphaState.cs.meta +++ b/Editor/UX/Controller/UXBindingEditor.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 39cce3f21841b1f4aa459d3909001607 +guid: 034c6da8785dcdb47a6f4350865dec51 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/UX/Controller/UXControllerEditor.cs b/Editor/UX/Controller/UXControllerEditor.cs index 0ff483e..16a0fbb 100644 --- a/Editor/UX/Controller/UXControllerEditor.cs +++ b/Editor/UX/Controller/UXControllerEditor.cs @@ -1,424 +1,266 @@ +#if UNITY_EDITOR +using System.Collections.Generic; using UnityEditor; using UnityEngine; -using AlicizaX.UI; -using System.Collections.Generic; namespace AlicizaX.UI { [CustomEditor(typeof(UXController))] - public class UXControllerEditor : UnityEditor.Editor + public sealed class UXControllerEditor : UnityEditor.Editor { private SerializedProperty _controllersProp; - private bool _showRecorders = true; - private bool _previewMode = false; - private Dictionary _controllerFoldouts = new Dictionary(); - - private GUIStyle _headerStyle; - private GUIStyle _toolbarStyle; - private GUIStyle _controllerHeaderStyle; + private SerializedProperty _bindingsProp; + private readonly Dictionary _foldouts = new Dictionary(); private void OnEnable() { _controllersProp = serializedObject.FindProperty("_controllers"); - } - - private void InitStyles() - { - if (_headerStyle == null) - { - _headerStyle = new GUIStyle(EditorStyles.boldLabel) - { - fontSize = 13, - fontStyle = FontStyle.Bold - }; - } - - if (_toolbarStyle == null) - { - _toolbarStyle = new GUIStyle(EditorStyles.toolbar) - { - fixedHeight = 25 - }; - } - - if (_controllerHeaderStyle == null) - { - _controllerHeaderStyle = new GUIStyle(EditorStyles.foldout) - { - fontStyle = FontStyle.Bold - }; - } + _bindingsProp = serializedObject.FindProperty("_bindings"); } public override void OnInspectorGUI() { - InitStyles(); serializedObject.Update(); - var controller = target as UXController; + var controller = (UXController)target; - // Header - DrawHeader(); - EditorGUILayout.Space(5); - - // Preview Mode - DrawPreviewMode(controller); - EditorGUILayout.Space(5); - - // Toolbar - DrawToolbar(); - EditorGUILayout.Space(5); - - // Controllers List - DrawControllersList(controller); - EditorGUILayout.Space(5); - - // Recorders Section - DrawRecorders(controller); + DrawHeader(controller); + EditorGUILayout.Space(6f); + DrawControllers(controller); + EditorGUILayout.Space(6f); + DrawBindings(); serializedObject.ApplyModifiedProperties(); } - private void DrawHeader() + 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(); - - EditorGUILayout.LabelField("UX 控制器", _headerStyle, GUILayout.Width(130)); - EditorGUILayout.LabelField($"控制器数量: {_controllersProp.arraySize}", EditorStyles.miniLabel); - - GUILayout.FlexibleSpace(); - - if (GUILayout.Button("+ 添加控制器", GUILayout.Width(110))) + if (GUILayout.Button("Add Controller")) { - AddNewController(); + AddController(); } + if (GUILayout.Button("Reset Preview")) + { + controller.ResetAllControllers(); + EditorUtility.SetDirty(controller); + SceneView.RepaintAll(); + } EditorGUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } - private void DrawPreviewMode(UXController controller) - { - Color oldColor = GUI.backgroundColor; - if (_previewMode) GUI.backgroundColor = new Color(0.5f, 1f, 0.5f); - - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - GUI.backgroundColor = oldColor; - - EditorGUILayout.BeginHorizontal(); - - EditorGUI.BeginChangeCheck(); - _previewMode = EditorGUILayout.Toggle("预览模式", _previewMode); - if (EditorGUI.EndChangeCheck()) - { - if (!_previewMode) - { - ResetAllControllers(controller); - } - } - - if (_previewMode) - { - GUILayout.FlexibleSpace(); - if (GUILayout.Button("重置全部", GUILayout.Width(80))) - { - ResetAllControllers(controller); - } - } - - EditorGUILayout.EndHorizontal(); - - if (_previewMode) - { - EditorGUILayout.HelpBox("预览模式已激活。点击状态按钮进行预览。", MessageType.Info); - } - - EditorGUILayout.EndVertical(); - } - - private void DrawToolbar() - { - EditorGUILayout.BeginHorizontal(_toolbarStyle); - - if (GUILayout.Button("全部展开", EditorStyles.toolbarButton, GUILayout.Width(70))) - { - for (int i = 0; i < _controllersProp.arraySize; i++) - { - _controllerFoldouts[i] = true; - } - } - - if (GUILayout.Button("全部折叠", EditorStyles.toolbarButton, GUILayout.Width(75))) - { - _controllerFoldouts.Clear(); - } - - GUILayout.FlexibleSpace(); - - EditorGUILayout.EndHorizontal(); - } - - private void DrawControllersList(UXController controller) + private void DrawControllers(UXController controller) { if (_controllersProp.arraySize == 0) { - EditorGUILayout.HelpBox("未定义控制器。点击 '+ 添加控制器' 创建一个。", MessageType.Info); + EditorGUILayout.HelpBox("No controllers yet. Add one to start driving bindings.", MessageType.Info); return; } for (int i = 0; i < _controllersProp.arraySize; i++) { - DrawControllerEntry(controller, i); - EditorGUILayout.Space(3); + DrawController(controller, i); + EditorGUILayout.Space(4f); } } - private void DrawControllerEntry(UXController controller, int index) + private void DrawController(UXController controller, int index) { - var element = _controllersProp.GetArrayElementAtIndex(index); - var nameProp = element.FindPropertyRelative("_name"); - var lengthProp = element.FindPropertyRelative("_length"); - var descProp = element.FindPropertyRelative("_description"); + 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"); - if (!_controllerFoldouts.ContainsKey(index)) - { - _controllerFoldouts[index] = false; - } - - bool isFolded = _controllerFoldouts[index]; + bool expanded = _foldouts.ContainsKey(index) && _foldouts[index]; EditorGUILayout.BeginVertical(EditorStyles.helpBox); - - // Header - DrawControllerHeader(index, nameProp, ref isFolded); - _controllerFoldouts[index] = isFolded; - - // Expanded Content - if (isFolded) - { - EditorGUI.indentLevel++; - - // Name - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("名称", GUILayout.Width(70)); - nameProp.stringValue = EditorGUILayout.TextField(nameProp.stringValue); - EditorGUILayout.EndHorizontal(); - - // Length - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("长度", GUILayout.Width(70)); - lengthProp.intValue = Mathf.Max(1, EditorGUILayout.IntField(lengthProp.intValue)); - EditorGUILayout.EndHorizontal(); - - // Description - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("描述", GUILayout.Width(70)); - descProp.stringValue = EditorGUILayout.TextField(descProp.stringValue); - EditorGUILayout.EndHorizontal(); - - // Preview - if (_previewMode && !Application.isPlaying) - { - EditorGUILayout.Space(5); - DrawControllerPreview(controller, index, lengthProp.intValue); - } - - EditorGUI.indentLevel--; - } - - EditorGUILayout.EndVertical(); - } - - private void DrawControllerHeader(int index, SerializedProperty nameProp, ref bool isFolded) - { EditorGUILayout.BeginHorizontal(); - - // Foldout - isFolded = EditorGUILayout.Foldout(isFolded, $"[{index}] {nameProp.stringValue}", true, _controllerHeaderStyle); - + expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {nameProp.stringValue}", true); GUILayout.FlexibleSpace(); - // Buttons - if (GUILayout.Button("▲", EditorStyles.miniButtonLeft, GUILayout.Width(25)) && index > 0) + if (GUILayout.Button("Up", EditorStyles.miniButtonLeft, GUILayout.Width(34f)) && index > 0) { _controllersProp.MoveArrayElement(index, index - 1); GUIUtility.ExitGUI(); } - if (GUILayout.Button("▼", EditorStyles.miniButtonMid, GUILayout.Width(25)) && index < _controllersProp.arraySize - 1) + if (GUILayout.Button("Down", EditorStyles.miniButtonMid, GUILayout.Width(44f)) && index < _controllersProp.arraySize - 1) { _controllersProp.MoveArrayElement(index, index + 1); GUIUtility.ExitGUI(); } - if (GUILayout.Button("⎘", EditorStyles.miniButtonMid, GUILayout.Width(25))) + if (GUILayout.Button("X", EditorStyles.miniButtonRight, GUILayout.Width(24f))) { - DuplicateController(index); - GUIUtility.ExitGUI(); + DeleteController(controller, index); } - - if (GUILayout.Button("×", EditorStyles.miniButtonRight, GUILayout.Width(25))) - { - if (EditorUtility.DisplayDialog("删除控制器", - $"删除控制器 '{nameProp.stringValue}'?", "删除", "取消")) - { - DeleteController(index); - } - } - - EditorGUILayout.EndHorizontal(); - } - - private void DrawControllerPreview(UXController controller, int controllerIndex, int length) - { - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - EditorGUILayout.LabelField("预览", EditorStyles.boldLabel); - - var handle = controller.GetControllerAt(controllerIndex); - if (handle != null) - { - int currentIndex = handle.SelectedIndex; - - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField($"状态: {currentIndex}", GUILayout.Width(60)); - - 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.SetControllerIndexInternal(controllerIndex, newIndex); - EditorUtility.SetDirty(controller); - SceneView.RepaintAll(); - } - - EditorGUILayout.EndHorizontal(); - } - - EditorGUILayout.EndVertical(); - } - - private void DrawRecorders(UXController controller) - { - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - - EditorGUILayout.BeginHorizontal(); - _showRecorders = EditorGUILayout.Foldout(_showRecorders, $"状态记录器 ({controller.Recorders.Count})", true); EditorGUILayout.EndHorizontal(); - if (_showRecorders) + _foldouts[index] = expanded; + + if (expanded) { - if (controller.Recorders.Count == 0) - { - EditorGUILayout.HelpBox("未附加状态记录器。", MessageType.Info); - } - else - { - EditorGUI.indentLevel++; + 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")); - for (int i = 0; i < controller.Recorders.Count; i++) - { - DrawRecorderEntry(controller.Recorders[i], i); - } + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.TextField("Id", idProp.stringValue); + EditorGUI.EndDisabledGroup(); - EditorGUI.indentLevel--; + UXController.ControllerDefinition definition = controller.GetControllerAt(index); + if (definition != null) + { + DrawPreview(controller, definition); } } EditorGUILayout.EndVertical(); } - private void DrawRecorderEntry(UXControllerStateRecorder recorder, int index) + private void DrawPreview(UXController controller, UXController.ControllerDefinition definition) { - if (recorder == null) return; + EditorGUILayout.Space(4f); + EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel); - EditorGUILayout.BeginHorizontal(EditorStyles.helpBox); - - EditorGUILayout.LabelField($"[{index}]", GUILayout.Width(30)); - EditorGUILayout.LabelField($"ID: {recorder.ID}", GUILayout.Width(100)); - - EditorGUI.BeginDisabledGroup(true); - EditorGUILayout.ObjectField(recorder, typeof(UXControllerStateRecorder), true); - EditorGUI.EndDisabledGroup(); - - EditorGUILayout.LabelField($"状态数: {recorder.StateEntries.Count}", GUILayout.Width(60)); - - if (GUILayout.Button("选择", GUILayout.Width(60))) + 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++) { - Selection.activeGameObject = recorder.gameObject; + options[i] = i.ToString(); } - EditorGUILayout.EndHorizontal(); + 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 AddNewController() + private void DrawBindings() { - _controllersProp.arraySize++; - var newElement = _controllersProp.GetArrayElementAtIndex(_controllersProp.arraySize - 1); - newElement.FindPropertyRelative("_name").stringValue = $"Controller{_controllersProp.arraySize}"; - newElement.FindPropertyRelative("_length").intValue = 2; - newElement.FindPropertyRelative("_description").stringValue = ""; - _controllerFoldouts[_controllersProp.arraySize - 1] = true; + 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 DuplicateController(int index) + private void AddController() { + int index = _controllersProp.arraySize; _controllersProp.InsertArrayElementAtIndex(index); - var newProp = _controllersProp.GetArrayElementAtIndex(index + 1); - var nameProp = newProp.FindPropertyRelative("_name"); - nameProp.stringValue += " 副本"; + 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; - _controllerFoldouts[index + 1] = true; + _foldouts[index] = true; } - private void DeleteController(int index) + private void DeleteController(UXController controller, int index) { if (index < 0 || index >= _controllersProp.arraySize) { return; } - Undo.RecordObject(serializedObject.targetObject, "删除控制器"); + SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index); + string deletedControllerId = entryProp.FindPropertyRelative("_id").stringValue; + Undo.RecordObject(controller, "Delete UX Controller"); _controllersProp.DeleteArrayElementAtIndex(index); - _controllerFoldouts.Remove(index); - - // Rebuild foldout dictionary with corrected indices - var newFoldouts = new Dictionary(); - foreach (var kvp in _controllerFoldouts) - { - if (kvp.Key < index) - { - newFoldouts[kvp.Key] = kvp.Value; - } - else if (kvp.Key > index) - { - newFoldouts[kvp.Key - 1] = kvp.Value; - } - } - _controllerFoldouts = newFoldouts; - + CleanupFoldouts(index); serializedObject.ApplyModifiedProperties(); - EditorUtility.SetDirty(serializedObject.targetObject); - + ClearBindingReferences(controller, deletedControllerId); + EditorUtility.SetDirty(controller); GUIUtility.ExitGUI(); } - private void ResetAllControllers(UXController controller) + private void CleanupFoldouts(int removedIndex) { - for (int i = 0; i < controller.ControllerCount; i++) + _foldouts.Remove(removedIndex); + + var remapped = new Dictionary(); + foreach (var pair in _foldouts) { - controller.SetControllerIndexInternal(i, 0); + 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); + } } - EditorUtility.SetDirty(controller); - SceneView.RepaintAll(); } } } +#endif diff --git a/Editor/UX/Controller/UXControllerEditor.cs.meta b/Editor/UX/Controller/UXControllerEditor.cs.meta index 7783ec2..d68b347 100644 --- a/Editor/UX/Controller/UXControllerEditor.cs.meta +++ b/Editor/UX/Controller/UXControllerEditor.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: 627b7a51244f46ce8bcbd31e6cedf4f8 -timeCreated: 1764127335 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/UX/Controller/UXControllerSceneOverlayWindow.cs b/Editor/UX/Controller/UXControllerSceneOverlayWindow.cs new file mode 100644 index 0000000..d0b899e --- /dev/null +++ b/Editor/UX/Controller/UXControllerSceneOverlayWindow.cs @@ -0,0 +1,268 @@ +#if UNITY_EDITOR +using System.Collections.Generic; +using UnityEditor; +using UnityEngine; + +namespace AlicizaX.UI +{ + public sealed class UXControllerSceneOverlayWindow : EditorWindow + { + private const string OverlayEnabledKey = "AlicizaX.UI.UXControllerSceneOverlay.Enabled"; + private const string OverlayAutoFocusKey = "AlicizaX.UI.UXControllerSceneOverlay.AutoFocus"; + private const float OverlayWidth = 360f; + private const float OverlayMargin = 12f; + + private static bool s_overlayEnabled; + private static bool s_autoFocusSceneView; + private static Vector2 s_scrollPosition; + private static bool s_registered; + + [MenuItem("Window/UX/Controller Scene Overlay")] + public static void ShowWindow() + { + var window = GetWindow("UX Controller Overlay"); + window.minSize = new Vector2(320f, 140f); + window.Show(); + EnsureRegistered(); + } + + [InitializeOnLoadMethod] + private static void Initialize() + { + s_overlayEnabled = EditorPrefs.GetBool(OverlayEnabledKey, false); + s_autoFocusSceneView = EditorPrefs.GetBool(OverlayAutoFocusKey, false); + EnsureRegistered(); + } + + private static void EnsureRegistered() + { + if (s_registered) + { + return; + } + + SceneView.duringSceneGui += OnSceneGui; + s_registered = true; + } + + private void OnGUI() + { + EditorGUILayout.LabelField("Scene View Overlay", EditorStyles.boldLabel); + EditorGUILayout.HelpBox( + "When enabled, the Scene View will display all UXController components in the current loaded scenes and let you preview them directly.", + MessageType.Info); + + EditorGUI.BeginChangeCheck(); + bool overlayEnabled = EditorGUILayout.Toggle("Enable Overlay", s_overlayEnabled); + bool autoFocus = EditorGUILayout.Toggle("Focus SceneView On Open", s_autoFocusSceneView); + if (EditorGUI.EndChangeCheck()) + { + s_overlayEnabled = overlayEnabled; + s_autoFocusSceneView = autoFocus; + EditorPrefs.SetBool(OverlayEnabledKey, s_overlayEnabled); + EditorPrefs.SetBool(OverlayAutoFocusKey, s_autoFocusSceneView); + SceneView.RepaintAll(); + } + + EditorGUILayout.Space(8f); + if (GUILayout.Button("Repaint Scene View")) + { + SceneView.RepaintAll(); + } + + if (GUILayout.Button("Open Scene View")) + { + SceneView.FocusWindowIfItsOpen(); + } + } + + private static void OnSceneGui(SceneView sceneView) + { + if (!s_overlayEnabled) + { + return; + } + + if (s_autoFocusSceneView && sceneView != null) + { + s_autoFocusSceneView = false; + EditorPrefs.SetBool(OverlayAutoFocusKey, false); + sceneView.Focus(); + } + + List controllers = GetSceneControllers(); + + Handles.BeginGUI(); + float height = Mathf.Max(160f, sceneView.position.height - OverlayMargin * 2f); + Rect area = new Rect( + sceneView.position.width - OverlayWidth - OverlayMargin, + OverlayMargin, + OverlayWidth, + height); + + GUILayout.BeginArea(area, EditorStyles.helpBox); + DrawOverlayContent(controllers); + GUILayout.EndArea(); + Handles.EndGUI(); + } + + private static void DrawOverlayContent(List controllers) + { + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("UX Controllers", EditorStyles.boldLabel); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("Refresh", GUILayout.Width(64f))) + { + SceneView.RepaintAll(); + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.Space(4f); + + if (controllers.Count == 0) + { + EditorGUILayout.HelpBox("No UXController found in the current loaded scenes.", MessageType.Info); + return; + } + + s_scrollPosition = EditorGUILayout.BeginScrollView(s_scrollPosition); + + for (int i = 0; i < controllers.Count; i++) + { + DrawControllerCard(controllers[i], i); + EditorGUILayout.Space(4f); + } + + EditorGUILayout.EndScrollView(); + } + + private static void DrawControllerCard(UXController controller, int index) + { + if (controller == null) + { + return; + } + + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + + EditorGUILayout.BeginHorizontal(); + EditorGUILayout.LabelField($"{index + 1}. {controller.gameObject.name}", EditorStyles.boldLabel); + GUILayout.FlexibleSpace(); + + if (GUILayout.Button("Select", GUILayout.Width(52f))) + { + Selection.activeGameObject = controller.gameObject; + EditorGUIUtility.PingObject(controller.gameObject); + } + + if (GUILayout.Button("Reset", GUILayout.Width(52f))) + { + controller.ResetAllControllers(); + EditorUtility.SetDirty(controller); + SceneView.RepaintAll(); + } + EditorGUILayout.EndHorizontal(); + + EditorGUILayout.LabelField(GetHierarchyPath(controller.transform), EditorStyles.miniLabel); + EditorGUILayout.LabelField($"Bindings: {controller.Bindings.Count}", EditorStyles.miniLabel); + + for (int controllerIndex = 0; controllerIndex < controller.ControllerCount; controllerIndex++) + { + UXController.ControllerDefinition definition = controller.GetControllerAt(controllerIndex); + if (definition == null) + { + continue; + } + + DrawDefinitionPreview(controller, definition); + } + + EditorGUILayout.EndVertical(); + } + + private static void DrawDefinitionPreview(UXController controller, UXController.ControllerDefinition definition) + { + EditorGUILayout.Space(3f); + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + EditorGUILayout.LabelField(definition.Name, EditorStyles.boldLabel); + + if (!string.IsNullOrWhiteSpace(definition.Description)) + { + EditorGUILayout.LabelField(definition.Description, EditorStyles.miniLabel); + } + + 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, 5)); + if (EditorGUI.EndChangeCheck()) + { + controller.SetControllerIndex(definition.Id, newIndex); + EditorUtility.SetDirty(controller); + SceneView.RepaintAll(); + } + + EditorGUILayout.EndVertical(); + } + + private static List GetSceneControllers() + { + var results = new List(); + UXController[] allControllers = Resources.FindObjectsOfTypeAll(); + + for (int i = 0; i < allControllers.Length; i++) + { + UXController controller = allControllers[i]; + if (controller == null) + { + continue; + } + + if (EditorUtility.IsPersistent(controller)) + { + continue; + } + + if (!controller.gameObject.scene.IsValid() || !controller.gameObject.scene.isLoaded) + { + continue; + } + + if ((controller.hideFlags & HideFlags.HideInHierarchy) != 0) + { + continue; + } + + results.Add(controller); + } + + results.Sort((left, right) => string.CompareOrdinal(GetHierarchyPath(left.transform), GetHierarchyPath(right.transform))); + return results; + } + + private static string GetHierarchyPath(Transform target) + { + if (target == null) + { + return string.Empty; + } + + string path = target.name; + Transform current = target.parent; + while (current != null) + { + path = $"{current.name}/{path}"; + current = current.parent; + } + + return path; + } + } +} +#endif diff --git a/Runtime/UXComponent/Controller/Property/CanvasGroupInteractableState.cs.meta b/Editor/UX/Controller/UXControllerSceneOverlayWindow.cs.meta similarity index 83% rename from Runtime/UXComponent/Controller/Property/CanvasGroupInteractableState.cs.meta rename to Editor/UX/Controller/UXControllerSceneOverlayWindow.cs.meta index 575fc9b..ac343f0 100644 --- a/Runtime/UXComponent/Controller/Property/CanvasGroupInteractableState.cs.meta +++ b/Editor/UX/Controller/UXControllerSceneOverlayWindow.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 671716e6e16c2da419a6a7a374e699e6 +guid: 7c62b8151e77e4b428e4e79a17d89e3b MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Editor/UX/Controller/UXControllerSceneView.cs b/Editor/UX/Controller/UXControllerSceneView.cs deleted file mode 100644 index be50c51..0000000 --- a/Editor/UX/Controller/UXControllerSceneView.cs +++ /dev/null @@ -1,210 +0,0 @@ -#if UNITY_EDITOR -using UnityEditor; -using UnityEngine; -namespace AlicizaX.UI -{ - public class UXControllerSceneViewWindow : EditorWindow - { - private Vector2 _scrollPosition; - private bool _autoRefresh = false; - private double _lastRefreshTime; - - [MenuItem("Window/UX/Controller Scene View")] - public static void ShowWindow() - { - var window = GetWindow("UX Controller Viewer"); - window.minSize = new Vector2(350, 400); - window.Show(); - } - - private void OnEnable() - { - _lastRefreshTime = EditorApplication.timeSinceStartup; - } - - private void Update() - { - if (_autoRefresh && EditorApplication.timeSinceStartup - _lastRefreshTime > 0.5) - { - _lastRefreshTime = EditorApplication.timeSinceStartup; - Repaint(); - } - } - - private void OnGUI() - { - DrawToolbar(); - - EditorGUILayout.Space(10); - - DrawControllers(); - } - - private void DrawToolbar() - { - EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); - - if (GUILayout.Button("Refresh", EditorStyles.toolbarButton, GUILayout.Width(60))) - { - Repaint(); - } - - _autoRefresh = GUILayout.Toggle(_autoRefresh, "Auto Refresh", EditorStyles.toolbarButton, GUILayout.Width(100)); - - GUILayout.FlexibleSpace(); - - if (GUILayout.Button("Reset All", EditorStyles.toolbarButton, GUILayout.Width(80))) - { - ResetAllControllers(); - } - - EditorGUILayout.EndHorizontal(); - } - - private void DrawControllers() - { - var controllers = GameObject.FindObjectsOfType(); - - if (controllers.Length == 0) - { - EditorGUILayout.HelpBox("No UXControllers found in the current scene.", MessageType.Info); - return; - } - - _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); - - foreach (var controller in controllers) - { - DrawControllerPanel(controller); - EditorGUILayout.Space(5); - } - - EditorGUILayout.EndScrollView(); - } - - private void DrawControllerPanel(UXController controller) - { - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - - EditorGUILayout.BeginHorizontal(); - - var boldLabelStyle = new GUIStyle(EditorStyles.boldLabel) - { - fontSize = 13 - }; - EditorGUILayout.LabelField(controller.name, boldLabelStyle); - - GUILayout.FlexibleSpace(); - - if (GUILayout.Button("Select", GUILayout.Width(70))) - { - Selection.activeGameObject = controller.gameObject; - SceneView.FrameLastActiveSceneView(); - } - - if (GUILayout.Button("Reset", GUILayout.Width(70))) - { - ResetController(controller); - } - - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.Space(5); - - EditorGUILayout.LabelField($"Path: {GetGameObjectPath(controller.gameObject)}", EditorStyles.miniLabel); - - EditorGUILayout.Space(5); - - for (int i = 0; i < controller.ControllerCount; i++) - { - DrawControllerStatePanel(controller, i); - } - - EditorGUILayout.Space(3); - EditorGUILayout.LabelField($"State Recorders: {controller.Recorders.Count}", EditorStyles.miniLabel); - - EditorGUILayout.EndVertical(); - } - - private void DrawControllerStatePanel(UXController controller, int controllerIndex) - { - var handle = controller.GetControllerAt(controllerIndex); - if (handle == null) return; - - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField(handle.Name, EditorStyles.boldLabel, GUILayout.Width(120)); - - if (!string.IsNullOrEmpty(handle.Description)) - { - EditorGUILayout.LabelField($"({handle.Description})", EditorStyles.miniLabel); - } - - EditorGUILayout.EndHorizontal(); - - int currentIndex = handle.SelectedIndex; - int length = handle.Length; - - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("State:", GUILayout.Width(50)); - - string[] options = new string[length]; - for (int i = 0; i < length; i++) - { - options[i] = $"Index {i}"; - } - - EditorGUI.BeginChangeCheck(); - int newIndex = GUILayout.SelectionGrid(currentIndex, options, Mathf.Min(length, 5)); - if (EditorGUI.EndChangeCheck() && newIndex != currentIndex) - { - Undo.RecordObject(controller, "Change Controller State"); - controller.SetControllerIndexInternal(controllerIndex, newIndex); - EditorUtility.SetDirty(controller); - } - - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.EndVertical(); - } - - private void ResetController(UXController controller) - { - Undo.RecordObject(controller, "Reset Controller"); - - for (int i = 0; i < controller.ControllerCount; i++) - { - controller.SetControllerIndexInternal(i, 0); - } - - EditorUtility.SetDirty(controller); - Repaint(); - } - - private void ResetAllControllers() - { - var controllers = GameObject.FindObjectsOfType(); - - foreach (var controller in controllers) - { - ResetController(controller); - } - } - - private string GetGameObjectPath(GameObject obj) - { - string path = obj.name; - Transform current = obj.transform.parent; - - while (current != null) - { - path = current.name + "/" + path; - current = current.parent; - } - - return path; - } - } -} -#endif diff --git a/Editor/UX/Controller/UXControllerStateRecorderEditor.cs b/Editor/UX/Controller/UXControllerStateRecorderEditor.cs deleted file mode 100644 index f6f3e70..0000000 --- a/Editor/UX/Controller/UXControllerStateRecorderEditor.cs +++ /dev/null @@ -1,1121 +0,0 @@ -#if UNITY_EDITOR -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using UnityEditor; -using UnityEditor.Experimental.GraphView; -using UnityEditor.Experimental.SceneManagement; -using UnityEngine; -using AlicizaX.UI; -using TMPro; -using UnityEditor.SceneManagement; -using UnityEngine.UI; - -namespace AlicizaX.UI -{ - [CustomEditor(typeof(UXControllerStateRecorder))] - public class UXControllerStateRecorderEditor : UnityEditor.Editor - { - private SerializedProperty _stateEntriesProp; - private SerializedProperty _controller; - private SerializedProperty _id; - private UXController _cacheController; - - private Dictionary _stateFoldouts = new Dictionary(); - private bool _showControllerInfo = true; - private bool _compactMode = false; - - private GUIStyle _headerStyle; - private GUIStyle _toolbarStyle; - private GUIStyle _stateHeaderStyle; - - private void OnEnable() - { - _controller = serializedObject.FindProperty("_controller"); - _id = serializedObject.FindProperty("_id"); - _stateEntriesProp = serializedObject.FindProperty("_stateEntries"); - } - - private void OnDestroy() - { - if (target == null) - { - UnRegisterSelf(); - } - } - - private void InitStyles() - { - if (_headerStyle == null) - { - _headerStyle = new GUIStyle(EditorStyles.boldLabel) - { - fontSize = 13, - fontStyle = FontStyle.Bold - }; - } - - if (_toolbarStyle == null) - { - _toolbarStyle = new GUIStyle(EditorStyles.toolbar) - { - fixedHeight = 25 - }; - } - - if (_stateHeaderStyle == null) - { - _stateHeaderStyle = new GUIStyle(EditorStyles.foldout) - { - fontStyle = FontStyle.Bold - }; - } - } - - public override void OnInspectorGUI() - { - InitStyles(); - serializedObject.Update(); - - var recorder = target as UXControllerStateRecorder; - UXController prefabCtrl = GetPrefabStageController(); - - recorder.GenerateID(); - - // Header - DrawHeader(recorder); - EditorGUILayout.Space(5); - - // Controller Reference - DrawControllerSection(recorder, prefabCtrl); - EditorGUILayout.Space(5); - - // Toolbar - DrawToolbar(); - EditorGUILayout.Space(5); - - // States List - DrawStatesList(recorder); - - serializedObject.ApplyModifiedProperties(); - } - - private void DrawHeader(UXControllerStateRecorder recorder) - { - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - EditorGUILayout.BeginHorizontal(); - - EditorGUILayout.LabelField("UX 状态记录器", _headerStyle, GUILayout.Width(150)); - EditorGUILayout.LabelField($"ID: {_id.intValue}", EditorStyles.miniLabel); - - GUILayout.FlexibleSpace(); - - if (GUILayout.Button("刷新", EditorStyles.miniButtonLeft, GUILayout.Width(60))) - { - recorder.Initialize(); - EditorUtility.SetDirty(recorder); - } - - Color oldColor = GUI.backgroundColor; - if (_compactMode) GUI.backgroundColor = Color.green; - if (GUILayout.Button("紧凑", EditorStyles.miniButtonRight, GUILayout.Width(60))) - { - _compactMode = !_compactMode; - _stateFoldouts.Clear(); - } - GUI.backgroundColor = oldColor; - - EditorGUILayout.EndHorizontal(); - EditorGUILayout.EndVertical(); - } - - private void DrawControllerSection(UXControllerStateRecorder recorder, UXController prefabCtrl) - { - if (prefabCtrl != null && prefabCtrl != _controller.objectReferenceValue) - { - _controller.objectReferenceValue = prefabCtrl; - recorder.SetController(prefabCtrl); - } - - if (prefabCtrl != null && !prefabCtrl.HasRecorder(recorder)) - { - RegisterSelfToController(prefabCtrl); - } - - if (_controller.objectReferenceValue != null) - { - _cacheController = _controller.objectReferenceValue as UXController; - } - - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - - EditorGUILayout.BeginHorizontal(); - _showControllerInfo = EditorGUILayout.Foldout(_showControllerInfo, "控制器引用", true); - EditorGUILayout.EndHorizontal(); - - // Controller dropdown - var availableControllers = GetAvailableControllers(recorder); - - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("控制器", GUILayout.Width(70)); - - if (availableControllers.Count > 0) - { - int currentIndex = -1; - var currentController = _controller.objectReferenceValue as UXController; - if (currentController != null) - { - currentIndex = availableControllers.IndexOf(currentController); - } - - string[] options = new string[availableControllers.Count]; - for (int i = 0; i < availableControllers.Count; i++) - { - var ctrl = availableControllers[i]; - string path = GetGameObjectPath(ctrl.gameObject, recorder.gameObject); - options[i] = $"{ctrl.gameObject.name} ({path})"; - } - - EditorGUI.BeginChangeCheck(); - int newIndex = EditorGUILayout.Popup(currentIndex, options); - if (EditorGUI.EndChangeCheck() && newIndex >= 0 && newIndex < availableControllers.Count) - { - _controller.objectReferenceValue = availableControllers[newIndex]; - recorder.SetController(availableControllers[newIndex]); - if (!availableControllers[newIndex].HasRecorder(recorder)) - { - RegisterSelfToController(availableControllers[newIndex]); - } - } - - if (currentController != null && GUILayout.Button("选择", GUILayout.Width(60))) - { - Selection.activeGameObject = currentController.gameObject; - } - } - else - { - EditorGUILayout.LabelField("(在父对象中未找到控制器)", EditorStyles.miniLabel); - } - - EditorGUILayout.EndHorizontal(); - - // Controller info - if (_showControllerInfo && _controller.objectReferenceValue != null) - { - UXController ctl = _controller.objectReferenceValue as UXController; - - EditorGUI.indentLevel++; - EditorGUILayout.LabelField($"控制器数量: {ctl.ControllerCount}", EditorStyles.miniLabel); - - for (int i = 0; i < ctl.ControllerCount; i++) - { - var handle = ctl.GetControllerAt(i); - if (handle != null) - { - string desc = string.IsNullOrEmpty(handle.Description) ? "" : $" - {handle.Description}"; - EditorGUILayout.LabelField($" [{i}] {handle.Name} (长度: {handle.Length}){desc}", EditorStyles.miniLabel); - } - } - EditorGUI.indentLevel--; - } - - if (_controller.objectReferenceValue == null) - { - EditorGUILayout.HelpBox("未分配控制器。请在父对象上添加 UXController。", MessageType.Warning); - } - - EditorGUILayout.EndVertical(); - } - - private void DrawToolbar() - { - EditorGUILayout.BeginHorizontal(_toolbarStyle); - - if (GUILayout.Button("+ 添加状态", EditorStyles.toolbarButton, GUILayout.Width(100))) - { - ShowAddStateMenu(); - } - - EditorGUI.BeginDisabledGroup(_stateEntriesProp.arraySize == 0); - if (GUILayout.Button("清空全部", EditorStyles.toolbarButton, GUILayout.Width(70))) - { - if (EditorUtility.DisplayDialog("清空所有状态", - "移除所有状态?", "清空", "取消")) - { - ClearAllStates(); - } - } - EditorGUI.EndDisabledGroup(); - - if (GUILayout.Button("全部展开", EditorStyles.toolbarButton, GUILayout.Width(70))) - { - for (int i = 0; i < _stateEntriesProp.arraySize; i++) - { - _stateFoldouts[i] = true; - } - } - - if (GUILayout.Button("全部折叠", EditorStyles.toolbarButton, GUILayout.Width(75))) - { - _stateFoldouts.Clear(); - } - - GUILayout.FlexibleSpace(); - - EditorGUILayout.LabelField($"状态数: {_stateEntriesProp.arraySize}", EditorStyles.miniLabel, GUILayout.Width(70)); - - EditorGUILayout.EndHorizontal(); - } - - private void DrawStatesList(UXControllerStateRecorder recorder) - { - if (_stateEntriesProp.arraySize == 0) - { - EditorGUILayout.HelpBox("未定义状态。点击 '+ 添加状态' 开始。", MessageType.Info); - return; - } - - for (int i = 0; i < _stateEntriesProp.arraySize; i++) - { - DrawStateEntry(recorder, i); - EditorGUILayout.Space(3); - } - } - - private void DrawStateEntry(UXControllerStateRecorder recorder, int index) - { - if (index < 0 || index >= _stateEntriesProp.arraySize) - { - return; - } - - var entryProp = _stateEntriesProp.GetArrayElementAtIndex(index); - if (entryProp == null) return; - - var stateProp = entryProp.FindPropertyRelative("State"); - var controllerNameProp = entryProp.FindPropertyRelative("ControllerName"); - var controllerIndexProp = entryProp.FindPropertyRelative("ControllerIndex"); - - // Validate all properties - if (controllerNameProp == null || controllerIndexProp == null) - { - return; - } - - if (stateProp == null || stateProp.managedReferenceValue == null) - { - DrawInvalidStateEntry(index); - return; - } - - var stateInstance = stateProp.managedReferenceValue as ControllerStateBase; - var stateType = stateProp.managedReferenceValue.GetType(); - var attr = stateType.GetCustomAttribute(); - var stateName = attr != null ? attr.StateName : stateType.Name; - - bool isValid = ValidateState(recorder, stateInstance); - bool isFolded = _stateFoldouts.ContainsKey(index) && _stateFoldouts[index]; - - // State box with validation color - Color boxColor = GUI.backgroundColor; - if (!isValid) GUI.backgroundColor = new Color(1f, 0.5f, 0.5f); - - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - GUI.backgroundColor = boxColor; - - // Header - DrawStateHeader(index, stateName, ref isFolded); - _stateFoldouts[index] = isFolded; - - // Compact mode: show binding info - if (_compactMode && !isFolded) - { - EditorGUI.indentLevel++; - try - { - string bindingText = string.IsNullOrEmpty(controllerNameProp.stringValue) - ? "未绑定" - : $"绑定到: {controllerNameProp.stringValue}[{controllerIndexProp.intValue}]"; - EditorGUILayout.LabelField(bindingText, EditorStyles.miniLabel); - - // 在紧凑模式下也允许快速修改索引 - if (!string.IsNullOrEmpty(controllerNameProp.stringValue)) - { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("索引", GUILayout.Width(50)); - - int ctrlLen = GetSelectedControllerLength(recorder, controllerNameProp.stringValue); - if (ctrlLen <= 0) ctrlLen = 1; - - string[] idxOptions = new string[ctrlLen]; - for (int j = 0; j < ctrlLen; j++) idxOptions[j] = j.ToString(); - - EditorGUI.BeginChangeCheck(); - int newIdx = EditorGUILayout.Popup(controllerIndexProp.intValue, idxOptions, GUILayout.Width(60)); - if (EditorGUI.EndChangeCheck()) - { - controllerIndexProp.intValue = newIdx; - } - EditorGUILayout.EndHorizontal(); - } - } - catch (ObjectDisposedException) - { - EditorGUILayout.LabelField("绑定: (刷新中...)", EditorStyles.miniLabel); - } - EditorGUI.indentLevel--; - } - - // Expanded content - if (isFolded && !_compactMode) - { - EditorGUI.indentLevel++; - - // Binding - DrawStateBinding(recorder, controllerNameProp, controllerIndexProp); - - EditorGUILayout.Space(3); - - // Properties - if (stateInstance != null) - { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("属性", EditorStyles.boldLabel); - - // Set to Current Value button - if (GUILayout.Button("设为当前值", GUILayout.Width(110))) - { - SetStateToCurrentValue(recorder, stateInstance, stateProp); - } - - EditorGUILayout.EndHorizontal(); - - SerializedClassDrawer.DrawSerializableProperty(stateProp); - - // Description - EditorGUILayout.Space(2); - var description = stateInstance.GetDescription(); - if (!string.IsNullOrEmpty(description)) - { - EditorGUILayout.LabelField($"ℹ {description}", EditorStyles.miniLabel); - } - } - - // Validation warning - if (!isValid) - { - EditorGUILayout.HelpBox("状态验证失败。请检查所需组件。", MessageType.Error); - } - - EditorGUI.indentLevel--; - } - - EditorGUILayout.EndVertical(); - } - - private void DrawStateHeader(int index, string stateName, ref bool isFolded) - { - EditorGUILayout.BeginHorizontal(); - - // Foldout with name - isFolded = EditorGUILayout.Foldout(isFolded, $"[{index}] {stateName}", true, _stateHeaderStyle); - - GUILayout.FlexibleSpace(); - - // Buttons - if (GUILayout.Button("▲", EditorStyles.miniButtonLeft, GUILayout.Width(25)) && index > 0) - { - MoveStateUp(index); - } - - if (GUILayout.Button("▼", EditorStyles.miniButtonMid, GUILayout.Width(25)) && index < _stateEntriesProp.arraySize - 1) - { - MoveStateDown(index); - } - - if (GUILayout.Button("⎘", EditorStyles.miniButtonMid, GUILayout.Width(25))) - { - DuplicateState(index); - } - - if (GUILayout.Button("×", EditorStyles.miniButtonRight, GUILayout.Width(25))) - { - DeleteStateAtIndex(index); - } - - EditorGUILayout.EndHorizontal(); - } - - private void DrawStateBinding(UXControllerStateRecorder recorder, - SerializedProperty controllerNameProp, SerializedProperty controllerIndexProp) - { - // Safety check: ensure properties are valid - if (controllerNameProp == null || controllerIndexProp == null) - { - EditorGUILayout.HelpBox("绑定属性无效。请尝试刷新。", MessageType.Warning); - return; - } - - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - EditorGUILayout.LabelField("绑定", EditorStyles.boldLabel); - - var controllerNames = GetControllerNamesForRecorder(recorder); - - // Controller dropdown - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("控制器", GUILayout.Width(70)); - - if (controllerNames.Count > 0) - { - string currentName = ""; - try - { - currentName = controllerNameProp.stringValue; - } - catch (ObjectDisposedException) - { - // Property has been disposed, exit gracefully - EditorGUILayout.EndHorizontal(); - EditorGUILayout.EndVertical(); - return; - } - - int selIdx = controllerNames.IndexOf(currentName); - if (selIdx < 0) selIdx = 0; - - EditorGUI.BeginChangeCheck(); - int newSel = EditorGUILayout.Popup(selIdx, controllerNames.ToArray()); - if (EditorGUI.EndChangeCheck() && newSel >= 0 && newSel < controllerNames.Count) - { - controllerNameProp.stringValue = controllerNames[newSel]; - } - } - else - { - EditorGUILayout.LabelField("(无可用控制器)", EditorStyles.miniLabel); - } - EditorGUILayout.EndHorizontal(); - - // Index dropdown - if (controllerNames.Count > 0 && !string.IsNullOrEmpty(controllerNameProp.stringValue)) - { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField("索引", GUILayout.Width(70)); - - int ctrlLen = GetSelectedControllerLength(recorder, controllerNameProp.stringValue); - if (ctrlLen <= 0) ctrlLen = 1; - - controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, ctrlLen - 1); - - string[] idxOptions = new string[ctrlLen]; - for (int j = 0; j < ctrlLen; j++) idxOptions[j] = j.ToString(); - - int curIdx = controllerIndexProp.intValue; - EditorGUI.BeginChangeCheck(); - int newIdx = EditorGUILayout.Popup(curIdx, idxOptions, GUILayout.Width(100)); - if (EditorGUI.EndChangeCheck()) - { - controllerIndexProp.intValue = newIdx; - } - - // Preview button - if (_controller.objectReferenceValue != null && !Application.isPlaying) - { - if (GUILayout.Button("预览", GUILayout.Width(60))) - { - PreviewState(controllerNameProp.stringValue, controllerIndexProp.intValue); - } - } - - EditorGUILayout.EndHorizontal(); - } - - EditorGUILayout.EndVertical(); - } - - private void DrawInvalidStateEntry(int index) - { - Color oldColor = GUI.backgroundColor; - GUI.backgroundColor = new Color(1f, 0.5f, 0.5f); - - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - GUI.backgroundColor = oldColor; - - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.LabelField($"[{index}] 无效状态", EditorStyles.boldLabel); - - if (GUILayout.Button("移除", GUILayout.Width(60))) - { - DeleteStateAtIndex(index); - } - EditorGUILayout.EndHorizontal(); - - EditorGUILayout.HelpBox("状态数据已损坏。请移除并重新添加。", MessageType.Error); - - EditorGUILayout.EndVertical(); - } - - private void DeleteStateAtIndex(int index) - { - if (index < 0 || index >= _stateEntriesProp.arraySize) - { - Debug.LogWarning($"Invalid state index: {index}"); - return; - } - - Undo.RecordObject(serializedObject.targetObject, "删除状态"); - - // Get the entry before deleting - var entryProp = _stateEntriesProp.GetArrayElementAtIndex(index); - var stateProp = entryProp?.FindPropertyRelative("State"); - - // If the state reference is not null, we need to delete twice - // First deletion clears the reference, second removes the array element - if (stateProp != null && stateProp.managedReferenceValue != null) - { - stateProp.managedReferenceValue = null; - } - - // Now delete the array element - _stateEntriesProp.DeleteArrayElementAtIndex(index); - - // Clean up foldout dictionary - _stateFoldouts.Remove(index); - - // Rebuild foldout dictionary with corrected indices - var newFoldouts = new Dictionary(); - foreach (var kvp in _stateFoldouts) - { - if (kvp.Key < index) - { - newFoldouts[kvp.Key] = kvp.Value; - } - else if (kvp.Key > index) - { - newFoldouts[kvp.Key - 1] = kvp.Value; - } - } - _stateFoldouts = newFoldouts; - - serializedObject.ApplyModifiedProperties(); - EditorUtility.SetDirty(serializedObject.targetObject); - - // Exit GUI to prevent using disposed SerializedProperty - GUIUtility.ExitGUI(); - } - - private void ClearAllStates() - { - Undo.RecordObject(serializedObject.targetObject, "清空所有状态"); - - // Clear all managed references first - for (int i = _stateEntriesProp.arraySize - 1; i >= 0; i--) - { - var entryProp = _stateEntriesProp.GetArrayElementAtIndex(i); - var stateProp = entryProp?.FindPropertyRelative("State"); - if (stateProp != null && stateProp.managedReferenceValue != null) - { - stateProp.managedReferenceValue = null; - } - } - - // Clear the array - _stateEntriesProp.ClearArray(); - _stateFoldouts.Clear(); - - serializedObject.ApplyModifiedProperties(); - EditorUtility.SetDirty(serializedObject.targetObject); - - // Exit GUI to prevent using disposed SerializedProperty - GUIUtility.ExitGUI(); - } - - private void PreviewState(string controllerName, int index) - { - if (_controller.objectReferenceValue != null) - { - var ctl = _controller.objectReferenceValue as UXController; - ctl.SetControllerIndex(controllerName, index); - EditorUtility.SetDirty(ctl); - SceneView.RepaintAll(); - } - } - - private void DuplicateState(int index) - { - if (index < 0 || index >= _stateEntriesProp.arraySize) - { - Debug.LogWarning($"Invalid state index: {index}"); - return; - } - - Undo.RecordObject(serializedObject.targetObject, "复制状态"); - - _stateEntriesProp.InsertArrayElementAtIndex(index); - - // Update foldout dictionary - var newFoldouts = new Dictionary(); - foreach (var kvp in _stateFoldouts) - { - if (kvp.Key <= index) - { - newFoldouts[kvp.Key] = kvp.Value; - } - else - { - newFoldouts[kvp.Key + 1] = kvp.Value; - } - } - _stateFoldouts = newFoldouts; - _stateFoldouts[index + 1] = true; - - serializedObject.ApplyModifiedProperties(); - EditorUtility.SetDirty(serializedObject.targetObject); - - // Exit GUI to prevent using disposed SerializedProperty - GUIUtility.ExitGUI(); - } - - private void MoveStateUp(int index) - { - if (index <= 0 || index >= _stateEntriesProp.arraySize) - { - return; - } - - Undo.RecordObject(serializedObject.targetObject, "上移状态"); - - _stateEntriesProp.MoveArrayElement(index, index - 1); - - // Update foldout dictionary - if (_stateFoldouts.ContainsKey(index)) - { - bool wasFolded = _stateFoldouts[index]; - _stateFoldouts.Remove(index); - _stateFoldouts[index - 1] = wasFolded; - } - - serializedObject.ApplyModifiedProperties(); - EditorUtility.SetDirty(serializedObject.targetObject); - - // Exit GUI to prevent using disposed SerializedProperty - GUIUtility.ExitGUI(); - } - - private void MoveStateDown(int index) - { - if (index < 0 || index >= _stateEntriesProp.arraySize - 1) - { - return; - } - - Undo.RecordObject(serializedObject.targetObject, "下移状态"); - - _stateEntriesProp.MoveArrayElement(index, index + 1); - - // Update foldout dictionary - if (_stateFoldouts.ContainsKey(index)) - { - bool wasFolded = _stateFoldouts[index]; - _stateFoldouts.Remove(index); - _stateFoldouts[index + 1] = wasFolded; - } - - serializedObject.ApplyModifiedProperties(); - EditorUtility.SetDirty(serializedObject.targetObject); - - // Exit GUI to prevent using disposed SerializedProperty - GUIUtility.ExitGUI(); - } - - private bool ValidateState(UXControllerStateRecorder recorder, ControllerStateBase stateInstance) - { - if (stateInstance == null) return false; - try - { - return stateInstance.Valid(recorder); - } - catch - { - return false; - } - } - - private void ShowAddStateMenu() - { - var provider = ScriptableObject.CreateInstance(); - provider.Init(OnTypeSelected); - SearchWindow.Open(new SearchWindowContext(GUIUtility.GUIToScreenPoint(Event.current.mousePosition)), provider); - } - - private void OnTypeSelected(Type type) - { - if (type == null) return; - - Undo.RecordObject(serializedObject.targetObject, "添加控制器状态"); - serializedObject.Update(); - - int idx = _stateEntriesProp.arraySize; - _stateEntriesProp.InsertArrayElementAtIndex(idx); - var entryProp = _stateEntriesProp.GetArrayElementAtIndex(idx); - var stateProp = entryProp.FindPropertyRelative("State"); - var controllerNameProp = entryProp.FindPropertyRelative("ControllerName"); - var controllerIndexProp = entryProp.FindPropertyRelative("ControllerIndex"); - - ControllerStateBase instance = null; - try - { - instance = (ControllerStateBase)Activator.CreateInstance(type); - } - catch (Exception ex) - { - Debug.LogException(ex); - return; - } - var recorder = serializedObject.targetObject as UXControllerStateRecorder; - if (instance != null) - { - instance.Init(recorder); - } - - if (stateProp != null) - { - stateProp.managedReferenceValue = instance; - stateProp.isExpanded = true; - } - - var controllerNames = GetControllerNamesForRecorder(recorder); - if (controllerNames.Count > 0) - { - controllerNameProp.stringValue = controllerNames[0]; - controllerIndexProp.intValue = 0; - } - else - { - controllerNameProp.stringValue = ""; - controllerIndexProp.intValue = 0; - } - - serializedObject.ApplyModifiedProperties(); - EditorUtility.SetDirty(serializedObject.targetObject); - - _stateFoldouts[idx] = true; - } - - private void UnRegisterSelf() - { - UXController controller = (_cacheController != null) ? _cacheController : GetPrefabStageController(); - if (controller == null) return; - - var so = new SerializedObject(controller); - var recorders = so.FindProperty("_recorders"); - for (int i = recorders.arraySize - 1; i >= 0; i--) - { - var el = recorders.GetArrayElementAtIndex(i); - if (el.objectReferenceValue == null) - { - recorders.DeleteArrayElementAtIndex(i); - } - } - - so.ApplyModifiedProperties(); - EditorUtility.SetDirty(controller); - } - - private void RegisterSelfToController(UXController controller) - { - var so = new SerializedObject(controller); - var recorders = so.FindProperty("_recorders"); - int newIndex = recorders.arraySize; - recorders.InsertArrayElementAtIndex(newIndex); - var el = recorders.GetArrayElementAtIndex(newIndex); - el.objectReferenceValue = target; - so.ApplyModifiedProperties(); - EditorUtility.SetDirty(controller); - } - - private UXController GetPrefabStageController() - { - var stage = PrefabStageUtility.GetCurrentPrefabStage(); - if (stage == null) return null; - var root = stage.prefabContentsRoot; - if (root == null) return null; - return root.GetComponentInChildren(); - } - - private List GetControllerNamesForRecorder(UXControllerStateRecorder recorder) - { - var names = new List(); - var prefabCtrl = GetPrefabStageController(); - if (prefabCtrl != null) - { - foreach (var cd in prefabCtrl.Controllers) names.Add(cd.Name); - return names; - } - - var assigned = recorder.Controller; - if (assigned != null) - { - foreach (var cd in assigned.Controllers) names.Add(cd.Name); - return names; - } - - var inScene = recorder.gameObject.GetComponentInParent(); - if (inScene != null) - { - foreach (var cd in inScene.Controllers) names.Add(cd.Name); - return names; - } - - return names; - } - - private int GetSelectedControllerLength(UXControllerStateRecorder recorder, string controllerName) - { - var prefabCtrl = GetPrefabStageController(); - if (prefabCtrl != null) - { - var ch = prefabCtrl.Controllers.ToList().Find(x => x.Name == controllerName); - if (ch != null) return ch.Length; - } - - var assigned = recorder.Controller; - if (assigned != null) - { - var ch = assigned.Controllers.ToList().Find(x => x.Name == controllerName); - if (ch != null) return ch.Length; - } - - var inScene = recorder.gameObject.GetComponentInParent(); - if (inScene != null) - { - var ch = inScene.Controllers.ToList().Find(x => x.Name == controllerName); - if (ch != null) return ch.Length; - } - - return 1; - } - - private List GetAvailableControllers(UXControllerStateRecorder recorder) - { - var controllers = new List(); - - // Search upwards through all parent transforms - Transform current = recorder.transform.parent; - while (current != null) - { - var ctrl = current.GetComponent(); - if (ctrl != null && !controllers.Contains(ctrl)) - { - controllers.Add(ctrl); - } - current = current.parent; - } - - return controllers; - } - - private string GetGameObjectPath(GameObject target, GameObject from) - { - if (target == null || from == null) return ""; - - List path = new List(); - Transform current = from.transform.parent; - - while (current != null) - { - if (current.gameObject == target) - { - return path.Count == 0 ? "Parent" : string.Join("/", path); - } - path.Insert(0, ".."); - current = current.parent; - } - - return "Unknown"; - } - - private void SetStateToCurrentValue(UXControllerStateRecorder recorder, ControllerStateBase state, SerializedProperty stateProp) - { - if (recorder == null || state == null) return; - - Undo.RecordObject(recorder, "设置状态为当前值"); - - // Use reflection to set current values based on state type - var stateType = state.GetType(); - - if (state is TransformPositionState) - { - if (recorder.TryGetComponent(out var rect)) - { - var posField = stateType.GetField("_position", BindingFlags.NonPublic | BindingFlags.Instance); - if (posField != null) - { - posField.SetValue(state, rect.anchoredPosition); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - } - else if (state is TransformScaleState) - { - if (recorder.transform != null) - { - var scaleField = stateType.GetField("_scale", BindingFlags.NonPublic | BindingFlags.Instance); - if (scaleField != null) - { - scaleField.SetValue(state, recorder.transform.localScale); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - } - else if (state is TransformRotationState) - { - if (recorder.transform != null) - { - var rotField = stateType.GetField("_rotation", BindingFlags.NonPublic | BindingFlags.Instance); - if (rotField != null) - { - rotField.SetValue(state, recorder.transform.localEulerAngles); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - } - else if (state is GraphicColorState) - { - if (recorder.TryGetComponent(out var graphic)) - { - var colorField = stateType.GetField("_color", BindingFlags.NonPublic | BindingFlags.Instance); - if (colorField != null) - { - colorField.SetValue(state, graphic.color); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - } - else if (state is GraphicMaterialState) - { - if (recorder.TryGetComponent(out var graphic)) - { - var matField = stateType.GetField("_material", BindingFlags.NonPublic | BindingFlags.Instance); - if (matField != null) - { - matField.SetValue(state, graphic.material); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - } - else if (state is ImageSpriteState) - { - if (recorder.TryGetComponent(out var image)) - { - var spriteField = stateType.GetField("_sprite", BindingFlags.NonPublic | BindingFlags.Instance); - if (spriteField != null) - { - spriteField.SetValue(state, image.sprite); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - } - else if (state is TextContentState) - { - string currentText = ""; - if (recorder.TryGetComponent(out var text)) - { - currentText = text.text; - } - else if (recorder.TryGetComponent(out var tmp)) - { - currentText = tmp.text; - } - - var textField = stateType.GetField("_text", BindingFlags.NonPublic | BindingFlags.Instance); - if (textField != null) - { - textField.SetValue(state, currentText); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - else if (state is TextColorState) - { - Color currentColor = Color.white; - if (recorder.TryGetComponent(out var text)) - { - currentColor = text.color; - } - else if (recorder.TryGetComponent(out var tmp)) - { - currentColor = tmp.color; - } - - var colorField = stateType.GetField("_color", BindingFlags.NonPublic | BindingFlags.Instance); - if (colorField != null) - { - colorField.SetValue(state, currentColor); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - else if (state is CanvasGroupAlphaState) - { - if (recorder.TryGetComponent(out var canvasGroup)) - { - var alphaField = stateType.GetField("_alpha", BindingFlags.NonPublic | BindingFlags.Instance); - if (alphaField != null) - { - alphaField.SetValue(state, canvasGroup.alpha); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - } - else if (state is CanvasGroupInteractableState) - { - if (recorder.TryGetComponent(out var canvasGroup)) - { - var interField = stateType.GetField("_interactable", BindingFlags.NonPublic | BindingFlags.Instance); - var blockField = stateType.GetField("_blocksRaycasts", BindingFlags.NonPublic | BindingFlags.Instance); - if (interField != null && blockField != null) - { - interField.SetValue(state, canvasGroup.interactable); - blockField.SetValue(state, canvasGroup.blocksRaycasts); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - } - else if (state is GameObjectActiveState) - { - var activeField = stateType.GetField("_active", BindingFlags.NonPublic | BindingFlags.Instance); - if (activeField != null) - { - activeField.SetValue(state, recorder.gameObject.activeSelf); - serializedObject.ApplyModifiedProperties(); - serializedObject.Update(); - EditorUtility.SetDirty(recorder); - } - } - - Debug.Log($"Set {stateType.Name} to current value"); - } - } -} -#endif diff --git a/Editor/UX/Controller/UXControllerStateRecorderEditor.cs.meta b/Editor/UX/Controller/UXControllerStateRecorderEditor.cs.meta deleted file mode 100644 index 3fc698d..0000000 --- a/Editor/UX/Controller/UXControllerStateRecorderEditor.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: a754ad6b8cb341ada13bb87279b12b78 -timeCreated: 1764134852 \ No newline at end of file diff --git a/Runtime/UXComponent/Controller/ControllerStateBase.cs b/Runtime/UXComponent/Controller/ControllerStateBase.cs deleted file mode 100644 index 7715422..0000000 --- a/Runtime/UXComponent/Controller/ControllerStateBase.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using UnityEngine; - -namespace AlicizaX.UI -{ - /// - /// Base class for all controller states - /// - public abstract class ControllerStateBase - { - /// - /// Initialize the state when added to a recorder - /// - public abstract void Init(UXControllerStateRecorder recorder); - - /// - /// Execute the state when controller index changes - /// - /// The state recorder - /// The index this state entry is bound to - /// The currently selected controller index - public abstract void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectedIndex); - - /// - /// Validate if the state can be properly executed - /// - public abstract bool Valid(UXControllerStateRecorder recorder); - - /// - /// Get a human-readable description of this state - /// - public virtual string GetDescription() - { - return GetType().Name; - } - } - - /// - /// Attribute to define display name for a controller state type - /// - [AttributeUsage(AttributeTargets.Class)] - public class ControlerStateNameAttribute : Attribute - { - public string StateName { get; } - - public ControlerStateNameAttribute(string stateName) - { - StateName = stateName; - } - } - - /// - /// Attribute to define if a state can be attached multiple times to the same recorder - /// - [AttributeUsage(AttributeTargets.Class)] - public class ControlerStateAttachTypeAttribute : Attribute - { - public bool Repeat { get; } - - public ControlerStateAttachTypeAttribute(bool repeat) - { - Repeat = repeat; - } - } -} diff --git a/Runtime/UXComponent/Controller/ControllerStateBase.cs.meta b/Runtime/UXComponent/Controller/ControllerStateBase.cs.meta deleted file mode 100644 index fd3b016..0000000 --- a/Runtime/UXComponent/Controller/ControllerStateBase.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 774b49ad28de40fda18249bd9a24e38b -timeCreated: 1763705196 \ No newline at end of file diff --git a/Runtime/UXComponent/Controller/Property/CanvasGroupAlphaState.cs b/Runtime/UXComponent/Controller/Property/CanvasGroupAlphaState.cs deleted file mode 100644 index 729908f..0000000 --- a/Runtime/UXComponent/Controller/Property/CanvasGroupAlphaState.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using UnityEngine; - -namespace AlicizaX.UI -{ - /// - /// CanvasGroup 透明度状态控制 - /// 控制 CanvasGroup 的 Alpha 值 - /// - [Serializable] - [ControlerStateName("CanvasGroup/Alpha")] - [ControlerStateAttachType(true)] - public class CanvasGroupAlphaState : ControllerStateBase - { - [SerializeField] [Range(0f, 1f)] private float _alpha = 1f; - [HideInInspector] [SerializeField] private float _defaultAlpha; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.TryGetComponent(out var canvasGroup)) - { - _defaultAlpha = canvasGroup.alpha; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.TryGetComponent(out var canvasGroup)) - { - canvasGroup.alpha = (entryIndex == selectionIndex) ? _alpha : _defaultAlpha; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.GetComponent() != null; - } - - public override string GetDescription() - { - return $"匹配时: 透明度={_alpha:F2}, 默认={_defaultAlpha:F2}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/CanvasGroupInteractableState.cs b/Runtime/UXComponent/Controller/Property/CanvasGroupInteractableState.cs deleted file mode 100644 index 3b3b42c..0000000 --- a/Runtime/UXComponent/Controller/Property/CanvasGroupInteractableState.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using UnityEngine; - -namespace AlicizaX.UI -{ - /// - /// CanvasGroup 交互状态控制 - /// 控制 CanvasGroup 的可交互性和射线检测 - /// - [Serializable] - [ControlerStateName("CanvasGroup/Interactable")] - [ControlerStateAttachType(true)] - public class CanvasGroupInteractableState : ControllerStateBase - { - [SerializeField] private bool _interactable = true; - [SerializeField] private bool _blocksRaycasts = true; - [HideInInspector] [SerializeField] private bool _defaultInteractable = true; - [HideInInspector] [SerializeField] private bool _defaultBlocksRaycasts = true; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.TryGetComponent(out var canvasGroup)) - { - _defaultInteractable = canvasGroup.interactable; - _defaultBlocksRaycasts = canvasGroup.blocksRaycasts; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.TryGetComponent(out var canvasGroup)) - { - bool isActive = (entryIndex == selectionIndex); - canvasGroup.interactable = isActive ? _interactable : _defaultInteractable; - canvasGroup.blocksRaycasts = isActive ? _blocksRaycasts : _defaultBlocksRaycasts; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.GetComponent() != null; - } - - public override string GetDescription() - { - return $"匹配时: 可交互={_interactable}, 射线检测={_blocksRaycasts}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/GameObjectActiveState.cs b/Runtime/UXComponent/Controller/Property/GameObjectActiveState.cs deleted file mode 100644 index 202da84..0000000 --- a/Runtime/UXComponent/Controller/Property/GameObjectActiveState.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using UnityEngine; - -namespace AlicizaX.UI -{ - /// - /// GameObject 激活状态控制 - /// 控制对象的显示/隐藏 - /// - [Serializable] - [ControlerStateName("GameObject/Active")] - [ControlerStateAttachType(true)] - public class GameObjectActiveState : ControllerStateBase - { - [SerializeField] private bool _active = true; - [HideInInspector] [SerializeField] private bool _defaultActive = true; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.gameObject != null) - { - _defaultActive = recorder.gameObject.activeSelf; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.gameObject != null) - { - bool shouldBeActive = (entryIndex == selectionIndex) ? _active : _defaultActive; - recorder.gameObject.SetActive(shouldBeActive); - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.gameObject != null; - } - - public override string GetDescription() - { - return $"匹配时: {(_active ? "显示" : "隐藏")}, 默认: {(_defaultActive ? "显示" : "隐藏")}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/GraphicColorState.cs b/Runtime/UXComponent/Controller/Property/GraphicColorState.cs deleted file mode 100644 index 920b6de..0000000 --- a/Runtime/UXComponent/Controller/Property/GraphicColorState.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.UI; - -namespace AlicizaX.UI -{ - /// - /// Graphic 颜色状态控制 - /// 控制 UI 图形组件的颜色 - /// - [Serializable] - [ControlerStateName("Graphic/Color")] - [ControlerStateAttachType(true)] - public class GraphicColorState : ControllerStateBase - { - [SerializeField] private Color _color = Color.white; - [HideInInspector] [SerializeField] private Color _defaultColor; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.TryGetComponent(out var graphic)) - { - _defaultColor = graphic.color; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.TryGetComponent(out var graphic)) - { - graphic.color = (entryIndex == selectionIndex) ? _color : _defaultColor; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.GetComponent() != null; - } - - public override string GetDescription() - { - return $"匹配时: 颜色={_color}, 默认={_defaultColor}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/GraphicColorState.cs.meta b/Runtime/UXComponent/Controller/Property/GraphicColorState.cs.meta deleted file mode 100644 index 85e36f4..0000000 --- a/Runtime/UXComponent/Controller/Property/GraphicColorState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 75807e24962b62547b1444c2078b77b2 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UXComponent/Controller/Property/GraphicMaterialState.cs b/Runtime/UXComponent/Controller/Property/GraphicMaterialState.cs deleted file mode 100644 index 5080296..0000000 --- a/Runtime/UXComponent/Controller/Property/GraphicMaterialState.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.UI; - -namespace AlicizaX.UI -{ - /// - /// Graphic 材质状态控制 - /// 控制 UI 图形组件的材质 - /// - [Serializable] - [ControlerStateName("Graphic/Material")] - [ControlerStateAttachType(true)] - public class GraphicMaterialState : ControllerStateBase - { - [SerializeField] private Material _material; - [HideInInspector] [SerializeField] private Material _defaultMaterial; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.TryGetComponent(out var graphic)) - { - _defaultMaterial = graphic.material; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.TryGetComponent(out var graphic)) - { - graphic.material = (entryIndex == selectionIndex) ? _material : _defaultMaterial; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.GetComponent() != null; - } - - public override string GetDescription() - { - string matched = _material != null ? _material.name : "无"; - string defaultVal = _defaultMaterial != null ? _defaultMaterial.name : "无"; - return $"匹配时: {matched}, 默认: {defaultVal}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/GraphicMaterialState.cs.meta b/Runtime/UXComponent/Controller/Property/GraphicMaterialState.cs.meta deleted file mode 100644 index dec40c7..0000000 --- a/Runtime/UXComponent/Controller/Property/GraphicMaterialState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 1920e338ff84a074997b6d495fdf8641 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UXComponent/Controller/Property/ImageSpriteState.cs b/Runtime/UXComponent/Controller/Property/ImageSpriteState.cs deleted file mode 100644 index e413b95..0000000 --- a/Runtime/UXComponent/Controller/Property/ImageSpriteState.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.UI; - -namespace AlicizaX.UI -{ - /// - /// Image 精灵图状态控制 - /// 控制 Image 组件的 Sprite - /// - [Serializable] - [ControlerStateName("Image/Sprite")] - [ControlerStateAttachType(true)] - public class ImageSpriteState : ControllerStateBase - { - [SerializeField] private Sprite _sprite; - [HideInInspector] [SerializeField] private Sprite _defaultSprite; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.TryGetComponent(out var image)) - { - _defaultSprite = image.sprite; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.TryGetComponent(out var image)) - { - image.sprite = (entryIndex == selectionIndex) ? _sprite : _defaultSprite; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.GetComponent() != null; - } - - public override string GetDescription() - { - string matched = _sprite != null ? _sprite.name : "无"; - string defaultVal = _defaultSprite != null ? _defaultSprite.name : "无"; - return $"匹配时: {matched}, 默认: {defaultVal}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/ImageSpriteState.cs.meta b/Runtime/UXComponent/Controller/Property/ImageSpriteState.cs.meta deleted file mode 100644 index d614581..0000000 --- a/Runtime/UXComponent/Controller/Property/ImageSpriteState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 628055cb200855e4caccc19755692f8c -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UXComponent/Controller/Property/TextColorState.cs b/Runtime/UXComponent/Controller/Property/TextColorState.cs deleted file mode 100644 index 88e72c6..0000000 --- a/Runtime/UXComponent/Controller/Property/TextColorState.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.UI; -using TMPro; - -namespace AlicizaX.UI -{ - /// - /// Text 颜色状态控制 - /// 控制 Text 或 TextMeshProUGUI 的文本颜色 - /// - [Serializable] - [ControlerStateName("Text/Color")] - [ControlerStateAttachType(true)] - public class TextColorState : ControllerStateBase - { - [SerializeField] private Color _color = Color.white; - [HideInInspector] [SerializeField] private Color _defaultColor; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder == null) return; - - if (recorder.TryGetComponent(out var text)) - { - _defaultColor = text.color; - } - else if (recorder.TryGetComponent(out var tmp)) - { - _defaultColor = tmp.color; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder == null) return; - - Color targetColor = (entryIndex == selectionIndex) ? _color : _defaultColor; - - if (recorder.TryGetComponent(out var text)) - { - text.color = targetColor; - } - else if (recorder.TryGetComponent(out var tmp)) - { - tmp.color = targetColor; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && - (recorder.GetComponent() != null || recorder.GetComponent() != null); - } - - public override string GetDescription() - { - return $"匹配时: 颜色={_color}, 默认={_defaultColor}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/TextColorState.cs.meta b/Runtime/UXComponent/Controller/Property/TextColorState.cs.meta deleted file mode 100644 index bf95e8b..0000000 --- a/Runtime/UXComponent/Controller/Property/TextColorState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c6ac5efb857bb65439be28f7d81256c4 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UXComponent/Controller/Property/TextContentState.cs b/Runtime/UXComponent/Controller/Property/TextContentState.cs deleted file mode 100644 index 24dc195..0000000 --- a/Runtime/UXComponent/Controller/Property/TextContentState.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.UI; -using TMPro; - -namespace AlicizaX.UI -{ - /// - /// Text 内容状态控制 - /// 控制 Text 或 TextMeshProUGUI 的文本内容 - /// - [Serializable] - [ControlerStateName("Text/Content")] - [ControlerStateAttachType(true)] - public class TextContentState : ControllerStateBase - { - [SerializeField] [TextArea(3, 10)] private string _text = ""; - - public override void Init(UXControllerStateRecorder recorder) - { - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder == null || entryIndex != selectionIndex) return; - - if (recorder.TryGetComponent(out var text)) - { - text.text = _text; - } - else if (recorder.TryGetComponent(out var tmp)) - { - tmp.text = _text; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && - (recorder.GetComponent() != null || recorder.GetComponent() != null); - } - - public override string GetDescription() - { - string preview = _text.Length > 30 ? _text.Substring(0, 30) + "..." : _text; - return $"匹配时: \"{preview}\""; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/TextContentState.cs.meta b/Runtime/UXComponent/Controller/Property/TextContentState.cs.meta deleted file mode 100644 index 30cbf9c..0000000 --- a/Runtime/UXComponent/Controller/Property/TextContentState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 2601e2ab0d45e3d42b77ab4d5b808794 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UXComponent/Controller/Property/TransformPositionState.cs b/Runtime/UXComponent/Controller/Property/TransformPositionState.cs deleted file mode 100644 index ac29775..0000000 --- a/Runtime/UXComponent/Controller/Property/TransformPositionState.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using UnityEngine; - -namespace AlicizaX.UI -{ - /// - /// Transform 位置状态控制 - /// 控制 RectTransform 的锚点位置 - /// - [Serializable] - [ControlerStateName("Transform/Position")] - [ControlerStateAttachType(true)] - public class TransformPositionState : ControllerStateBase - { - [SerializeField] private Vector2 _position; - [HideInInspector] [SerializeField] private Vector2 _defaultPosition; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.TryGetComponent(out var rect)) - { - _defaultPosition = rect.anchoredPosition; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.TryGetComponent(out var rect)) - { - rect.anchoredPosition = (entryIndex == selectionIndex) ? _position : _defaultPosition; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.GetComponent() != null; - } - - public override string GetDescription() - { - return $"匹配时: 位置={_position}, 默认={_defaultPosition}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/TransformPositionState.cs.meta b/Runtime/UXComponent/Controller/Property/TransformPositionState.cs.meta deleted file mode 100644 index 892ee62..0000000 --- a/Runtime/UXComponent/Controller/Property/TransformPositionState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: fd5741ffd27fc8f489c01426672b1553 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UXComponent/Controller/Property/TransformRotationState.cs b/Runtime/UXComponent/Controller/Property/TransformRotationState.cs deleted file mode 100644 index a8ba251..0000000 --- a/Runtime/UXComponent/Controller/Property/TransformRotationState.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using UnityEngine; - -namespace AlicizaX.UI -{ - /// - /// Transform 旋转状态控制 - /// 控制对象的本地欧拉角旋转 - /// - [Serializable] - [ControlerStateName("Transform/Rotation")] - [ControlerStateAttachType(true)] - public class TransformRotationState : ControllerStateBase - { - [SerializeField] private Vector3 _rotation; - [HideInInspector] [SerializeField] private Vector3 _defaultRotation; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.transform != null) - { - _defaultRotation = recorder.transform.localEulerAngles; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.transform != null) - { - recorder.transform.localEulerAngles = (entryIndex == selectionIndex) ? _rotation : _defaultRotation; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.transform != null; - } - - public override string GetDescription() - { - return $"匹配时: 旋转={_rotation}, 默认={_defaultRotation}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/TransformRotationState.cs.meta b/Runtime/UXComponent/Controller/Property/TransformRotationState.cs.meta deleted file mode 100644 index 4f980c3..0000000 --- a/Runtime/UXComponent/Controller/Property/TransformRotationState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 6928446dc2b05ae48b3a2b5547d086cd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UXComponent/Controller/Property/TransformScaleState.cs b/Runtime/UXComponent/Controller/Property/TransformScaleState.cs deleted file mode 100644 index 0e2f324..0000000 --- a/Runtime/UXComponent/Controller/Property/TransformScaleState.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using UnityEngine; - -namespace AlicizaX.UI -{ - /// - /// Transform 缩放状态控制 - /// 控制对象的本地缩放 - /// - [Serializable] - [ControlerStateName("Transform/Scale")] - [ControlerStateAttachType(true)] - public class TransformScaleState : ControllerStateBase - { - [SerializeField] private Vector3 _scale = Vector3.one; - [HideInInspector] [SerializeField] private Vector3 _defaultScale; - - public override void Init(UXControllerStateRecorder recorder) - { - if (recorder != null && recorder.transform != null) - { - _defaultScale = recorder.transform.localScale; - } - } - - public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex) - { - if (recorder != null && recorder.transform != null) - { - recorder.transform.localScale = (entryIndex == selectionIndex) ? _scale : _defaultScale; - } - } - - public override bool Valid(UXControllerStateRecorder recorder) - { - return recorder != null && recorder.transform != null; - } - - public override string GetDescription() - { - return $"匹配时: 缩放={_scale}, 默认={_defaultScale}"; - } - } -} diff --git a/Runtime/UXComponent/Controller/Property/TransformScaleState.cs.meta b/Runtime/UXComponent/Controller/Property/TransformScaleState.cs.meta deleted file mode 100644 index 8a7cab6..0000000 --- a/Runtime/UXComponent/Controller/Property/TransformScaleState.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8232f9df6a0c18649b35fc85e9935659 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/UXComponent/Controller/UXBinding.cs b/Runtime/UXComponent/Controller/UXBinding.cs new file mode 100644 index 0000000..cb71de4 --- /dev/null +++ b/Runtime/UXComponent/Controller/UXBinding.cs @@ -0,0 +1,419 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace AlicizaX.UI +{ + public enum UXBindingFallbackMode + { + KeepCurrent = 0, + RestoreCapturedDefault = 1, + UseCustomValue = 2 + } + + public enum UXBindingValueKind + { + Boolean = 0, + Float = 1, + String = 2, + Color = 3, + Vector2 = 4, + Vector3 = 5, + ObjectReference = 6 + } + + public enum UXBindingProperty + { + GameObjectActive = 0, + CanvasGroupAlpha = 1, + CanvasGroupInteractable = 2, + CanvasGroupBlocksRaycasts = 3, + GraphicColor = 4, + GraphicMaterial = 5, + ImageSprite = 6, + TextContent = 7, + TextColor = 8, + RectTransformAnchoredPosition = 9, + TransformLocalScale = 10, + TransformLocalEulerAngles = 11 + } + + [Serializable] + public sealed class UXBindingValue + { + [SerializeField] private bool _boolValue; + [SerializeField] private float _floatValue; + [SerializeField] private string _stringValue = string.Empty; + [SerializeField] private Color _colorValue = Color.white; + [SerializeField] private Vector2 _vector2Value; + [SerializeField] private Vector3 _vector3Value; + [SerializeField] private UnityEngine.Object _objectValue; + + public bool BoolValue + { + get => _boolValue; + set => _boolValue = value; + } + + public float FloatValue + { + get => _floatValue; + set => _floatValue = value; + } + + public string StringValue + { + get => _stringValue; + set => _stringValue = value ?? string.Empty; + } + + public Color ColorValue + { + get => _colorValue; + set => _colorValue = value; + } + + public Vector2 Vector2Value + { + get => _vector2Value; + set => _vector2Value = value; + } + + public Vector3 Vector3Value + { + get => _vector3Value; + set => _vector3Value = value; + } + + public UnityEngine.Object ObjectValue + { + get => _objectValue; + set => _objectValue = value; + } + + public void CopyFrom(UXBindingValue other) + { + if (other == null) + { + return; + } + + _boolValue = other._boolValue; + _floatValue = other._floatValue; + _stringValue = other._stringValue; + _colorValue = other._colorValue; + _vector2Value = other._vector2Value; + _vector3Value = other._vector3Value; + _objectValue = other._objectValue; + } + } + + [DisallowMultipleComponent] + [AddComponentMenu("UX/UX Binding")] + public sealed class UXBinding : MonoBehaviour + { + [Serializable] + public sealed class BindingEntry + { + [SerializeField] private string _controllerId = string.Empty; + [SerializeField] private int _controllerIndex; + [SerializeField] private UXBindingProperty _property = UXBindingProperty.GameObjectActive; + [SerializeField] private UXBindingValue _value = new UXBindingValue(); + [SerializeField] private UXBindingFallbackMode _fallbackMode = UXBindingFallbackMode.RestoreCapturedDefault; + [SerializeField] private UXBindingValue _fallbackValue = new UXBindingValue(); + [HideInInspector] [SerializeField] private UXBindingValue _capturedDefault = new UXBindingValue(); + [HideInInspector] [SerializeField] private bool _hasCapturedDefault; + [HideInInspector] [SerializeField] private UXBindingProperty _capturedProperty = UXBindingProperty.GameObjectActive; + + public string ControllerId + { + get => _controllerId; + set => _controllerId = value ?? string.Empty; + } + + public int ControllerIndex + { + get => Mathf.Max(0, _controllerIndex); + set => _controllerIndex = Mathf.Max(0, value); + } + + public UXBindingProperty Property + { + get => _property; + set => _property = value; + } + + public UXBindingValue Value => _value; + + public UXBindingFallbackMode FallbackMode + { + get => _fallbackMode; + set => _fallbackMode = value; + } + + public UXBindingValue FallbackValue => _fallbackValue; + public bool HasCapturedDefault => _hasCapturedDefault; + + internal void Normalize() + { + if (_property != UXBindingProperty.GameObjectActive) + { + return; + } + + if (_fallbackMode == UXBindingFallbackMode.RestoreCapturedDefault) + { + _fallbackMode = UXBindingFallbackMode.UseCustomValue; + _fallbackValue.BoolValue = false; + } + } + + internal void CaptureDefault(GameObject target) + { + if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property)) + { + return; + } + + UXBindingPropertyUtility.CaptureValue(target, _property, _capturedDefault); + _capturedProperty = _property; + _hasCapturedDefault = true; + } + + internal void CaptureCurrentAsValue(GameObject target) + { + if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property)) + { + return; + } + + UXBindingPropertyUtility.CaptureValue(target, _property, _value); + } + + internal void CaptureCurrentAsFallback(GameObject target) + { + if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property)) + { + return; + } + + UXBindingPropertyUtility.CaptureValue(target, _property, _fallbackValue); + } + + internal void ResetToCapturedDefault(GameObject target) + { + if (target == null) + { + return; + } + + if (!_hasCapturedDefault || _capturedProperty != _property) + { + CaptureDefault(target); + } + + UXBindingPropertyUtility.ApplyValue(target, _property, _capturedDefault); + } + + internal void Apply(GameObject target, string controllerId, int selectedIndex) + { + if (target == null || !string.Equals(_controllerId, controllerId, StringComparison.Ordinal)) + { + return; + } + + if (!_hasCapturedDefault || _capturedProperty != _property) + { + CaptureDefault(target); + } + + if (!UXBindingPropertyUtility.IsSupported(target, _property)) + { + return; + } + + if (selectedIndex == _controllerIndex) + { + UXBindingPropertyUtility.ApplyValue(target, _property, _value); + return; + } + + switch (_fallbackMode) + { + case UXBindingFallbackMode.KeepCurrent: + return; + case UXBindingFallbackMode.RestoreCapturedDefault: + if (_hasCapturedDefault) + { + UXBindingPropertyUtility.ApplyValue(target, _property, _capturedDefault); + } + return; + case UXBindingFallbackMode.UseCustomValue: + UXBindingPropertyUtility.ApplyValue(target, _property, _fallbackValue); + return; + } + } + } + + [SerializeField] private UXController _controller; + [SerializeField] private List _entries = new List(); + + private bool _initialized; + + public UXController Controller => _controller; + public List Entries => _entries; + + public void Initialize() + { + if (_initialized) + { + return; + } + + _initialized = true; + NormalizeEntries(); + EnsureControllerReference(); + RegisterToController(); + CaptureDefaults(); + } + + public void SetController(UXController controller) + { + if (_controller == controller) + { + return; + } + + if (_controller != null) + { + _controller.UnregisterBinding(this); + } + + _controller = controller; + RegisterToController(); + } + + public void CaptureDefaults() + { + for (int i = 0; i < _entries.Count; i++) + { + if (_entries[i] != null) + { + _entries[i].CaptureDefault(gameObject); + } + } + } + + public void ResetToDefaults() + { + for (int i = 0; i < _entries.Count; i++) + { + if (_entries[i] != null) + { + _entries[i].ResetToCapturedDefault(gameObject); + } + } + } + + public void PreviewEntry(int entryIndex) + { + if (_controller == null || entryIndex < 0 || entryIndex >= _entries.Count) + { + return; + } + + BindingEntry entry = _entries[entryIndex]; + if (entry == null || string.IsNullOrWhiteSpace(entry.ControllerId)) + { + return; + } + + _controller.SetControllerIndex(entry.ControllerId, entry.ControllerIndex); + } + + public void CaptureEntryValue(int entryIndex) + { + if (entryIndex < 0 || entryIndex >= _entries.Count || _entries[entryIndex] == null) + { + return; + } + + _entries[entryIndex].CaptureCurrentAsValue(gameObject); + } + + public void CaptureEntryFallbackValue(int entryIndex) + { + if (entryIndex < 0 || entryIndex >= _entries.Count || _entries[entryIndex] == null) + { + return; + } + + _entries[entryIndex].CaptureCurrentAsFallback(gameObject); + } + + internal void OnControllerChanged(string controllerId, int selectedIndex) + { + for (int i = 0; i < _entries.Count; i++) + { + BindingEntry entry = _entries[i]; + if (entry != null) + { + entry.Apply(gameObject, controllerId, selectedIndex); + } + } + } + + private void Reset() + { + EnsureControllerReference(); + RegisterToController(); + } + + private void Awake() + { + Initialize(); + } + + private void OnValidate() + { + NormalizeEntries(); + EnsureControllerReference(); + RegisterToController(); + } + + private void OnDestroy() + { + if (_controller != null) + { + _controller.UnregisterBinding(this); + } + } + + private void EnsureControllerReference() + { + if (_controller == null) + { + _controller = GetComponentInParent(); + } + } + + private void RegisterToController() + { + if (_controller != null) + { + _controller.RegisterBinding(this); + } + } + + private void NormalizeEntries() + { + for (int i = 0; i < _entries.Count; i++) + { + if (_entries[i] != null) + { + _entries[i].Normalize(); + } + } + } + } +} diff --git a/Runtime/UXComponent/Controller/Property/GameObjectActiveState.cs.meta b/Runtime/UXComponent/Controller/UXBinding.cs.meta similarity index 83% rename from Runtime/UXComponent/Controller/Property/GameObjectActiveState.cs.meta rename to Runtime/UXComponent/Controller/UXBinding.cs.meta index b4dfc74..d7c8351 100644 --- a/Runtime/UXComponent/Controller/Property/GameObjectActiveState.cs.meta +++ b/Runtime/UXComponent/Controller/UXBinding.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b7bbaef07861f9249bca4035e2422181 +guid: aacb6b471b8bfde4c9d3650f1b9b2b22 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs b/Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs new file mode 100644 index 0000000..2a981fe --- /dev/null +++ b/Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using TMPro; +using UnityEngine; +using UnityEngine.UI; + +namespace AlicizaX.UI +{ + public sealed class UXBindingPropertyMetadata + { + public UXBindingPropertyMetadata( + UXBindingProperty property, + string displayName, + UXBindingValueKind valueKind, + Type objectReferenceType) + { + Property = property; + DisplayName = displayName; + ValueKind = valueKind; + ObjectReferenceType = objectReferenceType; + } + + public UXBindingProperty Property { get; } + public string DisplayName { get; } + public UXBindingValueKind ValueKind { get; } + public Type ObjectReferenceType { get; } + } + + public static class UXBindingPropertyUtility + { + private static readonly UXBindingPropertyMetadata[] Metadata = + { + new UXBindingPropertyMetadata(UXBindingProperty.GameObjectActive, "GameObject/Active", UXBindingValueKind.Boolean, null), + new UXBindingPropertyMetadata(UXBindingProperty.CanvasGroupAlpha, "CanvasGroup/Alpha", UXBindingValueKind.Float, null), + new UXBindingPropertyMetadata(UXBindingProperty.CanvasGroupInteractable, "CanvasGroup/Interactable", UXBindingValueKind.Boolean, null), + new UXBindingPropertyMetadata(UXBindingProperty.CanvasGroupBlocksRaycasts, "CanvasGroup/Blocks Raycasts", UXBindingValueKind.Boolean, null), + new UXBindingPropertyMetadata(UXBindingProperty.GraphicColor, "Graphic/Color", UXBindingValueKind.Color, null), + new UXBindingPropertyMetadata(UXBindingProperty.GraphicMaterial, "Graphic/Material", UXBindingValueKind.ObjectReference, typeof(Material)), + new UXBindingPropertyMetadata(UXBindingProperty.ImageSprite, "Image/Sprite", UXBindingValueKind.ObjectReference, typeof(Sprite)), + new UXBindingPropertyMetadata(UXBindingProperty.TextContent, "Text/Content", UXBindingValueKind.String, null), + new UXBindingPropertyMetadata(UXBindingProperty.TextColor, "Text/Color", UXBindingValueKind.Color, null), + new UXBindingPropertyMetadata(UXBindingProperty.RectTransformAnchoredPosition, "RectTransform/Anchored Position", UXBindingValueKind.Vector2, null), + new UXBindingPropertyMetadata(UXBindingProperty.TransformLocalScale, "Transform/Local Scale", UXBindingValueKind.Vector3, null), + new UXBindingPropertyMetadata(UXBindingProperty.TransformLocalEulerAngles, "Transform/Local Rotation", UXBindingValueKind.Vector3, null) + }; + + public static IReadOnlyList AllMetadata => Metadata; + + public static UXBindingPropertyMetadata GetMetadata(UXBindingProperty property) + { + for (int i = 0; i < Metadata.Length; i++) + { + if (Metadata[i].Property == property) + { + return Metadata[i]; + } + } + + return Metadata[0]; + } + + public static bool IsSupported(GameObject target, UXBindingProperty property) + { + if (target == null) + { + return false; + } + + switch (property) + { + case UXBindingProperty.GameObjectActive: + case UXBindingProperty.TransformLocalScale: + case UXBindingProperty.TransformLocalEulerAngles: + return true; + case UXBindingProperty.CanvasGroupAlpha: + case UXBindingProperty.CanvasGroupInteractable: + case UXBindingProperty.CanvasGroupBlocksRaycasts: + return target.GetComponent() != null; + case UXBindingProperty.GraphicColor: + case UXBindingProperty.GraphicMaterial: + return target.GetComponent() != null; + case UXBindingProperty.ImageSprite: + return target.GetComponent() != null; + case UXBindingProperty.TextContent: + case UXBindingProperty.TextColor: + return target.GetComponent() != null || target.GetComponent() != null; + case UXBindingProperty.RectTransformAnchoredPosition: + return target.GetComponent() != null; + default: + return false; + } + } + + public static void GetSupportedProperties(GameObject target, List output) + { + output.Clear(); + if (target == null) + { + return; + } + + for (int i = 0; i < Metadata.Length; i++) + { + UXBindingProperty property = Metadata[i].Property; + if (IsSupported(target, property)) + { + output.Add(property); + } + } + } + + public static void CaptureValue(GameObject target, UXBindingProperty property, UXBindingValue destination) + { + if (target == null || destination == null) + { + return; + } + + switch (property) + { + case UXBindingProperty.GameObjectActive: + destination.BoolValue = target.activeSelf; + return; + case UXBindingProperty.CanvasGroupAlpha: + destination.FloatValue = target.GetComponent().alpha; + return; + case UXBindingProperty.CanvasGroupInteractable: + destination.BoolValue = target.GetComponent().interactable; + return; + case UXBindingProperty.CanvasGroupBlocksRaycasts: + destination.BoolValue = target.GetComponent().blocksRaycasts; + return; + case UXBindingProperty.GraphicColor: + destination.ColorValue = target.GetComponent().color; + return; + case UXBindingProperty.GraphicMaterial: + destination.ObjectValue = target.GetComponent().material; + return; + case UXBindingProperty.ImageSprite: + destination.ObjectValue = target.GetComponent().sprite; + return; + case UXBindingProperty.TextContent: + if (target.TryGetComponent(out Text text)) + { + destination.StringValue = text.text; + } + else if (target.TryGetComponent(out TextMeshProUGUI tmp)) + { + destination.StringValue = tmp.text; + } + return; + case UXBindingProperty.TextColor: + if (target.TryGetComponent(out Text legacyText)) + { + destination.ColorValue = legacyText.color; + } + else if (target.TryGetComponent(out TextMeshProUGUI tmpText)) + { + destination.ColorValue = tmpText.color; + } + return; + case UXBindingProperty.RectTransformAnchoredPosition: + destination.Vector2Value = target.GetComponent().anchoredPosition; + return; + case UXBindingProperty.TransformLocalScale: + destination.Vector3Value = target.transform.localScale; + return; + case UXBindingProperty.TransformLocalEulerAngles: + destination.Vector3Value = target.transform.localEulerAngles; + return; + } + } + + public static void ApplyValue(GameObject target, UXBindingProperty property, UXBindingValue value) + { + if (target == null || value == null) + { + return; + } + + switch (property) + { + case UXBindingProperty.GameObjectActive: + target.SetActive(value.BoolValue); + return; + case UXBindingProperty.CanvasGroupAlpha: + target.GetComponent().alpha = value.FloatValue; + return; + case UXBindingProperty.CanvasGroupInteractable: + target.GetComponent().interactable = value.BoolValue; + return; + case UXBindingProperty.CanvasGroupBlocksRaycasts: + target.GetComponent().blocksRaycasts = value.BoolValue; + return; + case UXBindingProperty.GraphicColor: + target.GetComponent().color = value.ColorValue; + return; + case UXBindingProperty.GraphicMaterial: + target.GetComponent().material = value.ObjectValue as Material; + return; + case UXBindingProperty.ImageSprite: + target.GetComponent().sprite = value.ObjectValue as Sprite; + return; + case UXBindingProperty.TextContent: + if (target.TryGetComponent(out Text text)) + { + text.text = value.StringValue; + } + else if (target.TryGetComponent(out TextMeshProUGUI tmp)) + { + tmp.text = value.StringValue; + } + return; + case UXBindingProperty.TextColor: + if (target.TryGetComponent(out Text legacyText)) + { + legacyText.color = value.ColorValue; + } + else if (target.TryGetComponent(out TextMeshProUGUI tmpText)) + { + tmpText.color = value.ColorValue; + } + return; + case UXBindingProperty.RectTransformAnchoredPosition: + target.GetComponent().anchoredPosition = value.Vector2Value; + return; + case UXBindingProperty.TransformLocalScale: + target.transform.localScale = value.Vector3Value; + return; + case UXBindingProperty.TransformLocalEulerAngles: + target.transform.localEulerAngles = value.Vector3Value; + return; + } + } + } +} diff --git a/Editor/UX/Controller/UXControllerSceneView.cs.meta b/Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs.meta similarity index 83% rename from Editor/UX/Controller/UXControllerSceneView.cs.meta rename to Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs.meta index 9251c1f..8bd1a23 100644 --- a/Editor/UX/Controller/UXControllerSceneView.cs.meta +++ b/Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: b698383c9842af646b8985aa1fc11a77 +guid: cd767ac91b0e6d44fa13eee8daebc60f MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/UXComponent/Controller/UXController.cs b/Runtime/UXComponent/Controller/UXController.cs index 9f89ac2..43b0097 100644 --- a/Runtime/UXComponent/Controller/UXController.cs +++ b/Runtime/UXComponent/Controller/UXController.cs @@ -1,6 +1,9 @@ using System; using System.Collections.Generic; using UnityEngine; +#if UNITY_EDITOR +using UnityEditor; +#endif namespace AlicizaX.UI { @@ -9,12 +12,15 @@ namespace AlicizaX.UI public sealed class UXController : MonoBehaviour { [Serializable] - public class ControllerData + public sealed class ControllerDefinition { + [SerializeField] private string _id = string.Empty; [SerializeField] private string _name = "Controller"; [SerializeField] private int _length = 2; - [SerializeField] private string _description = ""; - [NonSerialized] private int _currentIndex = 0; + [SerializeField] private string _description = string.Empty; + [NonSerialized] private int _selectedIndex = -1; + + public string Id => _id; public string Name { @@ -24,7 +30,7 @@ namespace AlicizaX.UI public int Length { - get => _length; + get => Mathf.Max(1, _length); set => _length = Mathf.Max(1, value); } @@ -34,240 +40,266 @@ namespace AlicizaX.UI set => _description = value; } - public int CurrentIndex + public int SelectedIndex { - get => _currentIndex; - set => _currentIndex = value; + get => _selectedIndex; + internal set => _selectedIndex = value; + } + + internal void EnsureId() + { + if (string.IsNullOrWhiteSpace(_id)) + { + _id = Guid.NewGuid().ToString("N"); + } } } - [SerializeField] private List _controllers = new List(); - [SerializeField] private List _recorders = new List(); + [SerializeField] private List _controllers = new List(); + [SerializeField] private List _bindings = new List(); - private Dictionary _controllerIndexMap; - private bool _initialized = false; - - #region Public API - - public IReadOnlyList Controllers => _controllers; + private readonly Dictionary _controllerIdMap = new Dictionary(); + private readonly Dictionary _controllerNameMap = new Dictionary(); + public IReadOnlyList Controllers => _controllers; + public IReadOnlyList Bindings => _bindings; public int ControllerCount => _controllers.Count; - public ControllerHandle GetController(string name) + public bool TryGetControllerById(string controllerId, out ControllerDefinition controller) + { + controller = null; + if (string.IsNullOrWhiteSpace(controllerId)) + { + return false; + } + + EnsureInitialized(); + if (_controllerIdMap.TryGetValue(controllerId, out int index)) + { + controller = _controllers[index]; + return true; + } + + return false; + } + + public bool TryGetControllerByName(string controllerName, out ControllerDefinition controller) + { + controller = null; + if (string.IsNullOrWhiteSpace(controllerName)) + { + return false; + } + + EnsureInitialized(); + if (_controllerNameMap.TryGetValue(controllerName, out int index)) + { + controller = _controllers[index]; + return true; + } + + return false; + } + + public ControllerDefinition GetControllerAt(int index) + { + if (index < 0 || index >= _controllers.Count) + { + return null; + } + + return _controllers[index]; + } + + public int GetControllerIndex(string controllerId) + { + return TryGetControllerById(controllerId, out ControllerDefinition controller) + ? controller.SelectedIndex + : 0; + } + + public bool SetControllerIndex(string controllerId, int selectedIndex) + { + if (!TryGetControllerById(controllerId, out ControllerDefinition controller)) + { + return false; + } + + return SetControllerIndexInternal(controller, selectedIndex); + } + + public bool SetControllerIndexByName(string controllerName, int selectedIndex) + { + if (!TryGetControllerByName(controllerName, out ControllerDefinition controller)) + { + return false; + } + + return SetControllerIndexInternal(controller, selectedIndex); + } + + public void ResetAllControllers() { EnsureInitialized(); - if (_controllerIndexMap.TryGetValue(name, out int index)) - { - return new ControllerHandle(this, index); - } - return null; - } - public ControllerHandle GetControllerAt(int index) - { - if (index < 0 || index >= _controllers.Count) return null; - return new ControllerHandle(this, index); - } - - public void SetControllerIndex(string name, int selectedIndex) - { - EnsureInitialized(); - if (_controllerIndexMap.TryGetValue(name, out int controllerIndex)) + for (int i = 0; i < _controllers.Count; i++) { - SetControllerIndexInternal(controllerIndex, selectedIndex); + SetControllerIndexInternal(_controllers[i], 0, true); } } - public int GetControllerIndex(string name) + internal bool HasBinding(UXBinding binding) { - EnsureInitialized(); - if (_controllerIndexMap.TryGetValue(name, out int controllerIndex)) + return binding != null && _bindings.Contains(binding); + } + + internal void RegisterBinding(UXBinding binding) + { + if (binding == null) { - return _controllers[controllerIndex].CurrentIndex; + return; } - return 0; - } - #endregion - - #region Internal API - - internal IReadOnlyList Recorders => _recorders; - - internal void SetControllerIndexInternal(int controllerIndex, int selectedIndex) - { - if (controllerIndex < 0 || controllerIndex >= _controllers.Count) return; - - var cd = _controllers[controllerIndex]; - selectedIndex = Mathf.Clamp(selectedIndex, 0, cd.Length - 1); - - if (cd.CurrentIndex == selectedIndex) return; - cd.CurrentIndex = selectedIndex; - - NotifyRecorders(cd.Name, selectedIndex); - } - - internal bool HasRecorder(UXControllerStateRecorder recorder) - { - if (recorder == null) return false; - return _recorders.Exists(r => r != null && r.ID == recorder.ID); - } - - internal void RegisterRecorder(UXControllerStateRecorder recorder) - { - if (recorder != null && !HasRecorder(recorder)) + if (!_bindings.Contains(binding)) { - _recorders.Add(recorder); + _bindings.Add(binding); +#if UNITY_EDITOR + if (!Application.isPlaying) + { + EditorUtility.SetDirty(this); + } +#endif } } - internal void UnregisterRecorder(UXControllerStateRecorder recorder) + internal void UnregisterBinding(UXBinding binding) { - if (recorder != null) + if (binding == null) { - _recorders.RemoveAll(r => r == null || r.ID == recorder.ID); + return; } + + _bindings.Remove(binding); +#if UNITY_EDITOR + if (!Application.isPlaying) + { + EditorUtility.SetDirty(this); + } +#endif } - #endregion + private void Reset() + { + if (_controllers.Count == 0) + { + _controllers.Add(new ControllerDefinition()); + } - #region Unity Lifecycle + RebuildMaps(); + } private void Awake() { - InitializeRuntime(); + EnsureInitialized(); + + for (int i = 0; i < _bindings.Count; i++) + { + if (_bindings[i] != null) + { + _bindings[i].Initialize(); + } + } + + ResetAllControllers(); } private void OnValidate() { - BuildControllerIndexMap(); + RebuildMaps(); + CleanupBindings(); } - #endregion - - #region Private Methods - private void EnsureInitialized() { - if (!_initialized) + if (_controllerIdMap.Count == 0 && _controllerNameMap.Count == 0) { - BuildControllerIndexMap(); + RebuildMaps(); } } - private void BuildControllerIndexMap() + private void RebuildMaps() { - if (_controllerIndexMap == null) - { - _controllerIndexMap = new Dictionary(); - } - else - { - _controllerIndexMap.Clear(); - } + _controllerIdMap.Clear(); + _controllerNameMap.Clear(); + + var usedNames = new HashSet(StringComparer.Ordinal); for (int i = 0; i < _controllers.Count; i++) { - var controller = _controllers[i]; - if (!string.IsNullOrEmpty(controller.Name)) + ControllerDefinition controller = _controllers[i]; + if (controller == null) { - _controllerIndexMap[controller.Name] = i; + continue; } - } - _initialized = true; + controller.EnsureId(); + controller.SelectedIndex = Mathf.Clamp(controller.SelectedIndex, -1, controller.Length - 1); + + if (string.IsNullOrWhiteSpace(controller.Name)) + { + controller.Name = $"Controller{i + 1}"; + } + + if (!usedNames.Add(controller.Name)) + { + controller.Name = $"{controller.Name}_{i + 1}"; + usedNames.Add(controller.Name); + } + + _controllerIdMap[controller.Id] = i; + _controllerNameMap[controller.Name] = i; + } } - private void InitializeRuntime() + private void CleanupBindings() { - EnsureInitialized(); - - for (int i = 0; i < _recorders.Count; i++) + for (int i = _bindings.Count - 1; i >= 0; i--) { - if (_recorders[i] != null) + if (_bindings[i] == null) { - _recorders[i].Initialize(); + _bindings.RemoveAt(i); } } - - for (int i = 0; i < _controllers.Count; i++) - { - SetControllerIndexInternal(i, 0); - } } - private void NotifyRecorders(string controllerName, int selectedIndex) + private bool SetControllerIndexInternal(ControllerDefinition controller, int selectedIndex, bool force = false) { - for (int i = 0; i < _recorders.Count; i++) + if (controller == null) { - if (_recorders[i] != null) - { - _recorders[i].OnControllerIndexChanged(controllerName, selectedIndex); - } + return false; } + + selectedIndex = Mathf.Clamp(selectedIndex, 0, controller.Length - 1); + if (!force && controller.SelectedIndex == selectedIndex) + { + return false; + } + + controller.SelectedIndex = selectedIndex; + NotifyBindings(controller.Id, selectedIndex); + return true; } - #endregion - - #region Controller Handle - - public class ControllerHandle + private void NotifyBindings(string controllerId, int selectedIndex) { - private readonly UXController _owner; - private readonly int _controllerIndex; - - public ControllerHandle(UXController owner, int controllerIndex) + for (int i = 0; i < _bindings.Count; i++) { - _owner = owner; - _controllerIndex = controllerIndex; - } - - public bool IsValid => _owner != null && _controllerIndex >= 0 && _controllerIndex < _owner._controllers.Count; - - public int SelectedIndex - { - get + UXBinding binding = _bindings[i]; + if (binding != null) { - if (!IsValid) return 0; - return _owner._controllers[_controllerIndex].CurrentIndex; - } - set - { - if (IsValid) - { - _owner.SetControllerIndexInternal(_controllerIndex, value); - } - } - } - - public int Length - { - get - { - if (!IsValid) return 0; - return _owner._controllers[_controllerIndex].Length; - } - } - - public string Name - { - get - { - if (!IsValid) return null; - return _owner._controllers[_controllerIndex].Name; - } - } - - public string Description - { - get - { - if (!IsValid) return null; - return _owner._controllers[_controllerIndex].Description; + binding.OnControllerChanged(controllerId, selectedIndex); } } } - - #endregion } } diff --git a/Runtime/UXComponent/Controller/UXControllerStateRecorder.cs b/Runtime/UXComponent/Controller/UXControllerStateRecorder.cs deleted file mode 100644 index 5fd14cb..0000000 --- a/Runtime/UXComponent/Controller/UXControllerStateRecorder.cs +++ /dev/null @@ -1,112 +0,0 @@ -using System; -using System.Collections.Generic; -using UnityEngine; - -namespace AlicizaX.UI -{ - [DisallowMultipleComponent] - [AddComponentMenu("UX/UX Controller State Recorder")] - public class UXControllerStateRecorder : MonoBehaviour - { - [SerializeField] private int _id; - [SerializeField] private UXController _controller; - - internal int ID => _id; - internal UXController Controller => _controller; - - [Serializable] - public class StateEntry - { - [SerializeReference] public ControllerStateBase State = null; - public string ControllerName = string.Empty; - public int ControllerIndex = 0; - } - - [SerializeField] private List _stateEntries = new List(); - - internal List StateEntries => _stateEntries; - - private bool _initialized = false; - - #region Unity Lifecycle - - private void Awake() - { - Initialize(); - } - - private void OnDestroy() - { - if (_controller != null) - { - _controller.UnregisterRecorder(this); - } - } - - #endregion - - #region Public API - - public void Initialize() - { - if (_initialized) return; - _initialized = true; - - foreach (var entry in _stateEntries) - { - if (entry?.State != null) - { - entry.State.Init(this); - } - } - } - - public void SetController(UXController controller) - { - if (_controller != null) - { - _controller.UnregisterRecorder(this); - } - - _controller = controller; - - if (_controller != null) - { - _controller.RegisterRecorder(this); - } - } - - #endregion - - #region Internal API - - internal void OnControllerIndexChanged(string controllerName, int selectedIndex) - { - for (int i = 0; i < _stateEntries.Count; i++) - { - var entry = _stateEntries[i]; - if (entry != null && entry.ControllerName == controllerName && entry.State != null) - { - try - { - entry.State.Execute(this, entry.ControllerIndex, selectedIndex); - } - catch (Exception ex) - { - Debug.LogError($"Error executing state {entry.State.GetType().Name}: {ex.Message}", this); - } - } - } - } - - internal void GenerateID() - { - if (_id <= 0) - { - _id = UnityEngine.Random.Range(10000000, 99999999); - } - } - - #endregion - } -} diff --git a/Runtime/UXComponent/Controller/UXControllerStateRecorder.cs.meta b/Runtime/UXComponent/Controller/UXControllerStateRecorder.cs.meta deleted file mode 100644 index 9560517..0000000 --- a/Runtime/UXComponent/Controller/UXControllerStateRecorder.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 35829408994740a08c32ac2f519442f0 -timeCreated: 1763705324 \ No newline at end of file