重构UXController
1.重新优化UXBinding设计 2.优化编辑器预览 更贴合FGUI效果。
This commit is contained in:
parent
f6cb7dde18
commit
2752fe9e1c
@ -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<Type> _onSelect;
|
|
||||||
private List<SearchTreeEntry> _entries;
|
|
||||||
|
|
||||||
public void Init(Action<Type> onSelect)
|
|
||||||
{
|
|
||||||
_onSelect = onSelect;
|
|
||||||
BuildEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void BuildEntries()
|
|
||||||
{
|
|
||||||
_entries = new List<SearchTreeEntry>();
|
|
||||||
|
|
||||||
_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<ControlerStateNameAttribute>();
|
|
||||||
var tag = attr != null ? attr.StateName : t.Name;
|
|
||||||
return (type: t, tag);
|
|
||||||
})
|
|
||||||
.OrderBy(x => x.tag)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
|
|
||||||
var groups = new Dictionary<string, List<(Type type, string fullTag, string display)>>();
|
|
||||||
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<SearchTreeEntry> 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
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 37183e7bcfe8411fba14fb9eb6bdfe9d
|
|
||||||
timeCreated: 1764136414
|
|
||||||
416
Editor/UX/Controller/UXBindingEditor.cs
Normal file
416
Editor/UX/Controller/UXBindingEditor.cs
Normal file
@ -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<int, bool> _foldouts = new Dictionary<int, bool>();
|
||||||
|
private readonly List<UXBindingProperty> _supportedProperties = new List<UXBindingProperty>();
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_controllerProp = serializedObject.FindProperty("_controller");
|
||||||
|
_entriesProp = serializedObject.FindProperty("_entries");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
|
||||||
|
var binding = (UXBinding)target;
|
||||||
|
|
||||||
|
DrawHeader(binding);
|
||||||
|
EditorGUILayout.Space(6f);
|
||||||
|
DrawControllerField(binding);
|
||||||
|
EditorGUILayout.Space(6f);
|
||||||
|
DrawEntries(binding);
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawHeader(UXBinding binding)
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
|
EditorGUILayout.LabelField("UX Binding", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.LabelField($"Target: {binding.gameObject.name}", EditorStyles.miniLabel);
|
||||||
|
EditorGUILayout.LabelField($"Rules: {_entriesProp.arraySize}", EditorStyles.miniLabel);
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
if (GUILayout.Button("Add Rule"))
|
||||||
|
{
|
||||||
|
AddEntry(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Capture Defaults"))
|
||||||
|
{
|
||||||
|
binding.CaptureDefaults();
|
||||||
|
EditorUtility.SetDirty(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Reset To Defaults"))
|
||||||
|
{
|
||||||
|
binding.ResetToDefaults();
|
||||||
|
EditorUtility.SetDirty(binding);
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawControllerField(UXBinding binding)
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
|
EditorGUILayout.LabelField("Controller", EditorStyles.boldLabel);
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
UXController newController = (UXController)EditorGUILayout.ObjectField("Reference", binding.Controller, typeof(UXController), true);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
Undo.RecordObject(binding, "Change UX Binding Controller");
|
||||||
|
binding.SetController(newController);
|
||||||
|
_controllerProp.objectReferenceValue = newController;
|
||||||
|
EditorUtility.SetDirty(binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.Controller == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Assign a UXController on this object or one of its parents.", MessageType.Warning);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
EditorGUILayout.LabelField($"Bound To: {binding.Controller.name}", EditorStyles.miniLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEntries(UXBinding binding)
|
||||||
|
{
|
||||||
|
if (_entriesProp.arraySize == 0)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("No rules yet. Add one and choose a controller + property pair.", MessageType.Info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UXBindingPropertyUtility.GetSupportedProperties(binding.gameObject, _supportedProperties);
|
||||||
|
|
||||||
|
for (int i = 0; i < _entriesProp.arraySize; i++)
|
||||||
|
{
|
||||||
|
DrawEntry(binding, i);
|
||||||
|
EditorGUILayout.Space(4f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawEntry(UXBinding binding, int index)
|
||||||
|
{
|
||||||
|
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
|
||||||
|
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
|
||||||
|
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
|
||||||
|
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
|
||||||
|
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
|
||||||
|
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
|
||||||
|
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
|
||||||
|
|
||||||
|
UXBindingProperty property = (UXBindingProperty)propertyProp.enumValueIndex;
|
||||||
|
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
|
||||||
|
string label = UXBindingPropertyUtility.GetMetadata(property).DisplayName;
|
||||||
|
|
||||||
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {label}", true);
|
||||||
|
GUILayout.FlexibleSpace();
|
||||||
|
|
||||||
|
if (GUILayout.Button("Preview", EditorStyles.miniButtonLeft, GUILayout.Width(60f)))
|
||||||
|
{
|
||||||
|
binding.PreviewEntry(index);
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("X", EditorStyles.miniButtonRight, GUILayout.Width(24f)))
|
||||||
|
{
|
||||||
|
DeleteEntry(binding, index);
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
_foldouts[index] = expanded;
|
||||||
|
|
||||||
|
if (expanded)
|
||||||
|
{
|
||||||
|
DrawControllerSelector(binding, controllerIdProp, controllerIndexProp);
|
||||||
|
DrawPropertySelector(entryProp, binding.gameObject, propertyProp);
|
||||||
|
|
||||||
|
property = (UXBindingProperty)propertyProp.enumValueIndex;
|
||||||
|
UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(property);
|
||||||
|
|
||||||
|
EditorGUILayout.Space(2f);
|
||||||
|
EditorGUILayout.LabelField("Value", EditorStyles.boldLabel);
|
||||||
|
DrawValueField(valueProp, metadata, "Matched Value");
|
||||||
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
if (GUILayout.Button("Use Current"))
|
||||||
|
{
|
||||||
|
binding.CaptureEntryValue(index);
|
||||||
|
EditorUtility.SetDirty(binding);
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
EditorGUILayout.Space(2f);
|
||||||
|
EditorGUILayout.PropertyField(fallbackModeProp, new GUIContent("Fallback"));
|
||||||
|
UXBindingFallbackMode fallbackMode = (UXBindingFallbackMode)fallbackModeProp.enumValueIndex;
|
||||||
|
if (fallbackMode == UXBindingFallbackMode.UseCustomValue)
|
||||||
|
{
|
||||||
|
DrawValueField(fallbackValueProp, metadata, "Fallback Value");
|
||||||
|
|
||||||
|
if (GUILayout.Button("Use Current As Fallback"))
|
||||||
|
{
|
||||||
|
binding.CaptureEntryFallbackValue(index);
|
||||||
|
EditorUtility.SetDirty(binding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_supportedProperties.Contains(property))
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("This property is not supported by the components on the current GameObject.", MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawControllerSelector(UXBinding binding, SerializedProperty controllerIdProp, SerializedProperty controllerIndexProp)
|
||||||
|
{
|
||||||
|
UXController controller = binding.Controller;
|
||||||
|
if (controller == null || controller.Controllers.Count == 0)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Create a controller definition first.", MessageType.Info);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] names = new string[controller.Controllers.Count];
|
||||||
|
int selectedController = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < controller.Controllers.Count; i++)
|
||||||
|
{
|
||||||
|
UXController.ControllerDefinition definition = controller.Controllers[i];
|
||||||
|
names[i] = definition.Name;
|
||||||
|
if (definition.Id == controllerIdProp.stringValue)
|
||||||
|
{
|
||||||
|
selectedController = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
selectedController = EditorGUILayout.Popup("Controller", selectedController, names);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
controllerIdProp.stringValue = controller.Controllers[selectedController].Id;
|
||||||
|
controllerIndexProp.intValue = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UXController.ControllerDefinition selectedDefinition = controller.Controllers[selectedController];
|
||||||
|
int maxIndex = Mathf.Max(1, selectedDefinition.Length);
|
||||||
|
controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, maxIndex - 1);
|
||||||
|
|
||||||
|
string[] indexNames = new string[maxIndex];
|
||||||
|
for (int i = 0; i < maxIndex; i++)
|
||||||
|
{
|
||||||
|
indexNames[i] = i.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
controllerIndexProp.intValue = EditorGUILayout.Popup("Index", controllerIndexProp.intValue, indexNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPropertySelector(SerializedProperty entryProp, GameObject targetObject, SerializedProperty propertyProp)
|
||||||
|
{
|
||||||
|
var options = new List<UXBindingProperty>(_supportedProperties);
|
||||||
|
UXBindingProperty current = (UXBindingProperty)propertyProp.enumValueIndex;
|
||||||
|
if (!options.Contains(current))
|
||||||
|
{
|
||||||
|
options.Add(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
string[] displayNames = new string[options.Count];
|
||||||
|
int selectedIndex = 0;
|
||||||
|
for (int i = 0; i < options.Count; i++)
|
||||||
|
{
|
||||||
|
UXBindingProperty option = options[i];
|
||||||
|
UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(option);
|
||||||
|
bool supported = UXBindingPropertyUtility.IsSupported(targetObject, option);
|
||||||
|
displayNames[i] = supported ? metadata.DisplayName : $"{metadata.DisplayName} (Unsupported)";
|
||||||
|
if (option == current)
|
||||||
|
{
|
||||||
|
selectedIndex = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
selectedIndex = EditorGUILayout.Popup("Property", selectedIndex, displayNames);
|
||||||
|
if (EditorGUI.EndChangeCheck())
|
||||||
|
{
|
||||||
|
propertyProp.enumValueIndex = (int)options[selectedIndex];
|
||||||
|
entryProp.FindPropertyRelative("_hasCapturedDefault").boolValue = false;
|
||||||
|
entryProp.FindPropertyRelative("_capturedProperty").enumValueIndex = propertyProp.enumValueIndex;
|
||||||
|
ResetValue(entryProp.FindPropertyRelative("_capturedDefault"));
|
||||||
|
ApplyDefaultFallbackForProperty(entryProp, (UXBindingProperty)propertyProp.enumValueIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawValueField(SerializedProperty valueProp, UXBindingPropertyMetadata metadata, string label)
|
||||||
|
{
|
||||||
|
switch (metadata.ValueKind)
|
||||||
|
{
|
||||||
|
case UXBindingValueKind.Boolean:
|
||||||
|
{
|
||||||
|
SerializedProperty boolProp = valueProp.FindPropertyRelative("_boolValue");
|
||||||
|
boolProp.boolValue = EditorGUILayout.Toggle(label, boolProp.boolValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UXBindingValueKind.Float:
|
||||||
|
{
|
||||||
|
SerializedProperty floatProp = valueProp.FindPropertyRelative("_floatValue");
|
||||||
|
floatProp.floatValue = EditorGUILayout.FloatField(label, floatProp.floatValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UXBindingValueKind.String:
|
||||||
|
{
|
||||||
|
SerializedProperty stringProp = valueProp.FindPropertyRelative("_stringValue");
|
||||||
|
EditorGUILayout.LabelField(label);
|
||||||
|
stringProp.stringValue = EditorGUILayout.TextArea(stringProp.stringValue, GUILayout.MinHeight(54f));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UXBindingValueKind.Color:
|
||||||
|
{
|
||||||
|
SerializedProperty colorProp = valueProp.FindPropertyRelative("_colorValue");
|
||||||
|
colorProp.colorValue = EditorGUILayout.ColorField(label, colorProp.colorValue);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UXBindingValueKind.Vector2:
|
||||||
|
{
|
||||||
|
SerializedProperty vector2Prop = valueProp.FindPropertyRelative("_vector2Value");
|
||||||
|
vector2Prop.vector2Value = EditorGUILayout.Vector2Field(label, vector2Prop.vector2Value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UXBindingValueKind.Vector3:
|
||||||
|
{
|
||||||
|
SerializedProperty vector3Prop = valueProp.FindPropertyRelative("_vector3Value");
|
||||||
|
vector3Prop.vector3Value = EditorGUILayout.Vector3Field(label, vector3Prop.vector3Value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UXBindingValueKind.ObjectReference:
|
||||||
|
{
|
||||||
|
SerializedProperty objectProp = valueProp.FindPropertyRelative("_objectValue");
|
||||||
|
objectProp.objectReferenceValue = EditorGUILayout.ObjectField(
|
||||||
|
label,
|
||||||
|
objectProp.objectReferenceValue,
|
||||||
|
metadata.ObjectReferenceType,
|
||||||
|
false);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddEntry(UXBinding binding)
|
||||||
|
{
|
||||||
|
int index = _entriesProp.arraySize;
|
||||||
|
_entriesProp.InsertArrayElementAtIndex(index);
|
||||||
|
|
||||||
|
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
|
||||||
|
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
|
||||||
|
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
|
||||||
|
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
|
||||||
|
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
|
||||||
|
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
|
||||||
|
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
|
||||||
|
SerializedProperty capturedDefaultProp = entryProp.FindPropertyRelative("_capturedDefault");
|
||||||
|
SerializedProperty hasCapturedDefaultProp = entryProp.FindPropertyRelative("_hasCapturedDefault");
|
||||||
|
SerializedProperty capturedPropertyProp = entryProp.FindPropertyRelative("_capturedProperty");
|
||||||
|
|
||||||
|
controllerIdProp.stringValue = string.Empty;
|
||||||
|
controllerIndexProp.intValue = 0;
|
||||||
|
propertyProp.enumValueIndex = (int)UXBindingProperty.GameObjectActive;
|
||||||
|
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault;
|
||||||
|
ResetValue(valueProp);
|
||||||
|
ResetValue(fallbackValueProp);
|
||||||
|
ResetValue(capturedDefaultProp);
|
||||||
|
hasCapturedDefaultProp.boolValue = false;
|
||||||
|
capturedPropertyProp.enumValueIndex = (int)UXBindingProperty.GameObjectActive;
|
||||||
|
ApplyDefaultFallbackForProperty(entryProp, UXBindingProperty.GameObjectActive);
|
||||||
|
|
||||||
|
if (binding.Controller != null && binding.Controller.Controllers.Count > 0)
|
||||||
|
{
|
||||||
|
controllerIdProp.stringValue = binding.Controller.Controllers[0].Id;
|
||||||
|
}
|
||||||
|
|
||||||
|
_foldouts[index] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ResetValue(SerializedProperty valueProp)
|
||||||
|
{
|
||||||
|
valueProp.FindPropertyRelative("_boolValue").boolValue = false;
|
||||||
|
valueProp.FindPropertyRelative("_floatValue").floatValue = 0f;
|
||||||
|
valueProp.FindPropertyRelative("_stringValue").stringValue = string.Empty;
|
||||||
|
valueProp.FindPropertyRelative("_colorValue").colorValue = Color.white;
|
||||||
|
valueProp.FindPropertyRelative("_vector2Value").vector2Value = Vector2.zero;
|
||||||
|
valueProp.FindPropertyRelative("_vector3Value").vector3Value = Vector3.zero;
|
||||||
|
valueProp.FindPropertyRelative("_objectValue").objectReferenceValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DeleteEntry(UXBinding binding, int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= _entriesProp.arraySize)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Undo.RecordObject(binding, "Delete UX Binding Rule");
|
||||||
|
_entriesProp.DeleteArrayElementAtIndex(index);
|
||||||
|
CleanupFoldouts(index);
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
EditorUtility.SetDirty(binding);
|
||||||
|
GUIUtility.ExitGUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanupFoldouts(int removedIndex)
|
||||||
|
{
|
||||||
|
_foldouts.Remove(removedIndex);
|
||||||
|
|
||||||
|
var remapped = new Dictionary<int, bool>();
|
||||||
|
foreach (var pair in _foldouts)
|
||||||
|
{
|
||||||
|
int nextIndex = pair.Key > removedIndex ? pair.Key - 1 : pair.Key;
|
||||||
|
remapped[nextIndex] = pair.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
_foldouts.Clear();
|
||||||
|
foreach (var pair in remapped)
|
||||||
|
{
|
||||||
|
_foldouts[pair.Key] = pair.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyDefaultFallbackForProperty(SerializedProperty entryProp, UXBindingProperty property)
|
||||||
|
{
|
||||||
|
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
|
||||||
|
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
|
||||||
|
|
||||||
|
if (property == UXBindingProperty.GameObjectActive)
|
||||||
|
{
|
||||||
|
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.UseCustomValue;
|
||||||
|
fallbackValueProp.FindPropertyRelative("_boolValue").boolValue = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((UXBindingFallbackMode)fallbackModeProp.enumValueIndex == UXBindingFallbackMode.UseCustomValue)
|
||||||
|
{
|
||||||
|
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault;
|
||||||
|
ResetValue(fallbackValueProp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 39cce3f21841b1f4aa459d3909001607
|
guid: 034c6da8785dcdb47a6f4350865dec51
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@ -1,288 +1,138 @@
|
|||||||
|
#if UNITY_EDITOR
|
||||||
|
using System.Collections.Generic;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using AlicizaX.UI;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
namespace AlicizaX.UI
|
||||||
{
|
{
|
||||||
[CustomEditor(typeof(UXController))]
|
[CustomEditor(typeof(UXController))]
|
||||||
public class UXControllerEditor : UnityEditor.Editor
|
public sealed class UXControllerEditor : UnityEditor.Editor
|
||||||
{
|
{
|
||||||
private SerializedProperty _controllersProp;
|
private SerializedProperty _controllersProp;
|
||||||
private bool _showRecorders = true;
|
private SerializedProperty _bindingsProp;
|
||||||
private bool _previewMode = false;
|
private readonly Dictionary<int, bool> _foldouts = new Dictionary<int, bool>();
|
||||||
private Dictionary<int, bool> _controllerFoldouts = new Dictionary<int, bool>();
|
|
||||||
|
|
||||||
private GUIStyle _headerStyle;
|
|
||||||
private GUIStyle _toolbarStyle;
|
|
||||||
private GUIStyle _controllerHeaderStyle;
|
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
_controllersProp = serializedObject.FindProperty("_controllers");
|
_controllersProp = serializedObject.FindProperty("_controllers");
|
||||||
}
|
_bindingsProp = serializedObject.FindProperty("_bindings");
|
||||||
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnInspectorGUI()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
InitStyles();
|
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
|
||||||
var controller = target as UXController;
|
var controller = (UXController)target;
|
||||||
|
|
||||||
// Header
|
DrawHeader(controller);
|
||||||
DrawHeader();
|
EditorGUILayout.Space(6f);
|
||||||
EditorGUILayout.Space(5);
|
DrawControllers(controller);
|
||||||
|
EditorGUILayout.Space(6f);
|
||||||
// Preview Mode
|
DrawBindings();
|
||||||
DrawPreviewMode(controller);
|
|
||||||
EditorGUILayout.Space(5);
|
|
||||||
|
|
||||||
// Toolbar
|
|
||||||
DrawToolbar();
|
|
||||||
EditorGUILayout.Space(5);
|
|
||||||
|
|
||||||
// Controllers List
|
|
||||||
DrawControllersList(controller);
|
|
||||||
EditorGUILayout.Space(5);
|
|
||||||
|
|
||||||
// Recorders Section
|
|
||||||
DrawRecorders(controller);
|
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawHeader()
|
private void DrawHeader(UXController controller)
|
||||||
{
|
{
|
||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
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.BeginHorizontal();
|
||||||
|
if (GUILayout.Button("Add Controller"))
|
||||||
EditorGUILayout.LabelField("UX 控制器", _headerStyle, GUILayout.Width(130));
|
|
||||||
EditorGUILayout.LabelField($"控制器数量: {_controllersProp.arraySize}", EditorStyles.miniLabel);
|
|
||||||
|
|
||||||
GUILayout.FlexibleSpace();
|
|
||||||
|
|
||||||
if (GUILayout.Button("+ 添加控制器", GUILayout.Width(110)))
|
|
||||||
{
|
{
|
||||||
AddNewController();
|
AddController();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Reset Preview"))
|
||||||
|
{
|
||||||
|
controller.ResetAllControllers();
|
||||||
|
EditorUtility.SetDirty(controller);
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawPreviewMode(UXController controller)
|
private void DrawControllers(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)
|
|
||||||
{
|
{
|
||||||
if (_controllersProp.arraySize == 0)
|
if (_controllersProp.arraySize == 0)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("未定义控制器。点击 '+ 添加控制器' 创建一个。", MessageType.Info);
|
EditorGUILayout.HelpBox("No controllers yet. Add one to start driving bindings.", MessageType.Info);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < _controllersProp.arraySize; i++)
|
for (int i = 0; i < _controllersProp.arraySize; i++)
|
||||||
{
|
{
|
||||||
DrawControllerEntry(controller, i);
|
DrawController(controller, i);
|
||||||
EditorGUILayout.Space(3);
|
EditorGUILayout.Space(4f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawControllerEntry(UXController controller, int index)
|
private void DrawController(UXController controller, int index)
|
||||||
{
|
{
|
||||||
var element = _controllersProp.GetArrayElementAtIndex(index);
|
SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index);
|
||||||
var nameProp = element.FindPropertyRelative("_name");
|
SerializedProperty idProp = entryProp.FindPropertyRelative("_id");
|
||||||
var lengthProp = element.FindPropertyRelative("_length");
|
SerializedProperty nameProp = entryProp.FindPropertyRelative("_name");
|
||||||
var descProp = element.FindPropertyRelative("_description");
|
SerializedProperty lengthProp = entryProp.FindPropertyRelative("_length");
|
||||||
|
SerializedProperty descriptionProp = entryProp.FindPropertyRelative("_description");
|
||||||
|
|
||||||
if (!_controllerFoldouts.ContainsKey(index))
|
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
|
||||||
{
|
|
||||||
_controllerFoldouts[index] = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isFolded = _controllerFoldouts[index];
|
|
||||||
|
|
||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
|
|
||||||
// Header
|
|
||||||
DrawControllerHeader(index, nameProp, ref isFolded);
|
|
||||||
_controllerFoldouts[index] = isFolded;
|
|
||||||
|
|
||||||
// Expanded Content
|
|
||||||
if (isFolded)
|
|
||||||
{
|
|
||||||
EditorGUI.indentLevel++;
|
|
||||||
|
|
||||||
// Name
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
EditorGUILayout.LabelField("名称", GUILayout.Width(70));
|
expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {nameProp.stringValue}", true);
|
||||||
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);
|
|
||||||
|
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
|
|
||||||
// Buttons
|
if (GUILayout.Button("Up", EditorStyles.miniButtonLeft, GUILayout.Width(34f)) && index > 0)
|
||||||
if (GUILayout.Button("▲", EditorStyles.miniButtonLeft, GUILayout.Width(25)) && index > 0)
|
|
||||||
{
|
{
|
||||||
_controllersProp.MoveArrayElement(index, index - 1);
|
_controllersProp.MoveArrayElement(index, index - 1);
|
||||||
GUIUtility.ExitGUI();
|
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);
|
_controllersProp.MoveArrayElement(index, index + 1);
|
||||||
GUIUtility.ExitGUI();
|
GUIUtility.ExitGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("⎘", EditorStyles.miniButtonMid, GUILayout.Width(25)))
|
if (GUILayout.Button("X", EditorStyles.miniButtonRight, GUILayout.Width(24f)))
|
||||||
{
|
{
|
||||||
DuplicateController(index);
|
DeleteController(controller, index);
|
||||||
GUIUtility.ExitGUI();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("×", EditorStyles.miniButtonRight, GUILayout.Width(25)))
|
|
||||||
{
|
|
||||||
if (EditorUtility.DisplayDialog("删除控制器",
|
|
||||||
$"删除控制器 '{nameProp.stringValue}'?", "删除", "取消"))
|
|
||||||
{
|
|
||||||
DeleteController(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
|
_foldouts[index] = expanded;
|
||||||
|
|
||||||
|
if (expanded)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(nameProp, new GUIContent("Name"));
|
||||||
|
lengthProp.intValue = Mathf.Max(1, EditorGUILayout.IntField("Length", Mathf.Max(1, lengthProp.intValue)));
|
||||||
|
EditorGUILayout.PropertyField(descriptionProp, new GUIContent("Description"));
|
||||||
|
|
||||||
|
EditorGUI.BeginDisabledGroup(true);
|
||||||
|
EditorGUILayout.TextField("Id", idProp.stringValue);
|
||||||
|
EditorGUI.EndDisabledGroup();
|
||||||
|
|
||||||
|
UXController.ControllerDefinition definition = controller.GetControllerAt(index);
|
||||||
|
if (definition != null)
|
||||||
|
{
|
||||||
|
DrawPreview(controller, definition);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawControllerPreview(UXController controller, int controllerIndex, int length)
|
EditorGUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawPreview(UXController controller, UXController.ControllerDefinition definition)
|
||||||
{
|
{
|
||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
EditorGUILayout.Space(4f);
|
||||||
EditorGUILayout.LabelField("预览", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel);
|
||||||
|
|
||||||
var handle = controller.GetControllerAt(controllerIndex);
|
|
||||||
if (handle != null)
|
|
||||||
{
|
|
||||||
int currentIndex = handle.SelectedIndex;
|
|
||||||
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
|
||||||
EditorGUILayout.LabelField($"状态: {currentIndex}", GUILayout.Width(60));
|
|
||||||
|
|
||||||
|
int currentIndex = Mathf.Max(0, definition.SelectedIndex);
|
||||||
|
int length = Mathf.Max(1, definition.Length);
|
||||||
string[] options = new string[length];
|
string[] options = new string[length];
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
@ -293,132 +143,124 @@ namespace AlicizaX.UI
|
|||||||
int newIndex = GUILayout.SelectionGrid(currentIndex, options, Mathf.Min(length, 6));
|
int newIndex = GUILayout.SelectionGrid(currentIndex, options, Mathf.Min(length, 6));
|
||||||
if (EditorGUI.EndChangeCheck())
|
if (EditorGUI.EndChangeCheck())
|
||||||
{
|
{
|
||||||
controller.SetControllerIndexInternal(controllerIndex, newIndex);
|
controller.SetControllerIndex(definition.Id, newIndex);
|
||||||
EditorUtility.SetDirty(controller);
|
EditorUtility.SetDirty(controller);
|
||||||
SceneView.RepaintAll();
|
SceneView.RepaintAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndVertical();
|
private void DrawBindings()
|
||||||
}
|
|
||||||
|
|
||||||
private void DrawRecorders(UXController controller)
|
|
||||||
{
|
{
|
||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
|
EditorGUILayout.LabelField("Registered Bindings", EditorStyles.boldLabel);
|
||||||
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
if (_bindingsProp.arraySize == 0)
|
||||||
_showRecorders = EditorGUILayout.Foldout(_showRecorders, $"状态记录器 ({controller.Recorders.Count})", true);
|
|
||||||
EditorGUILayout.EndHorizontal();
|
|
||||||
|
|
||||||
if (_showRecorders)
|
|
||||||
{
|
{
|
||||||
if (controller.Recorders.Count == 0)
|
EditorGUILayout.HelpBox("No UXBinding components are registered under this controller.", MessageType.Info);
|
||||||
{
|
|
||||||
EditorGUILayout.HelpBox("未附加状态记录器。", MessageType.Info);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
EditorGUI.indentLevel++;
|
for (int i = 0; i < _bindingsProp.arraySize; i++)
|
||||||
|
|
||||||
for (int i = 0; i < controller.Recorders.Count; i++)
|
|
||||||
{
|
{
|
||||||
DrawRecorderEntry(controller.Recorders[i], i);
|
SerializedProperty bindingProp = _bindingsProp.GetArrayElementAtIndex(i);
|
||||||
}
|
EditorGUI.BeginDisabledGroup(true);
|
||||||
|
EditorGUILayout.ObjectField($"Binding {i}", bindingProp.objectReferenceValue, typeof(UXBinding), true);
|
||||||
EditorGUI.indentLevel--;
|
EditorGUI.EndDisabledGroup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRecorderEntry(UXControllerStateRecorder recorder, int index)
|
private void AddController()
|
||||||
{
|
|
||||||
if (recorder == null) return;
|
|
||||||
|
|
||||||
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)))
|
|
||||||
{
|
|
||||||
Selection.activeGameObject = recorder.gameObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.EndHorizontal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddNewController()
|
|
||||||
{
|
|
||||||
_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DuplicateController(int index)
|
|
||||||
{
|
{
|
||||||
|
int index = _controllersProp.arraySize;
|
||||||
_controllersProp.InsertArrayElementAtIndex(index);
|
_controllersProp.InsertArrayElementAtIndex(index);
|
||||||
|
|
||||||
var newProp = _controllersProp.GetArrayElementAtIndex(index + 1);
|
SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index);
|
||||||
var nameProp = newProp.FindPropertyRelative("_name");
|
entryProp.FindPropertyRelative("_id").stringValue = string.Empty;
|
||||||
nameProp.stringValue += " 副本";
|
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)
|
if (index < 0 || index >= _controllersProp.arraySize)
|
||||||
{
|
{
|
||||||
return;
|
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);
|
_controllersProp.DeleteArrayElementAtIndex(index);
|
||||||
_controllerFoldouts.Remove(index);
|
CleanupFoldouts(index);
|
||||||
|
|
||||||
// Rebuild foldout dictionary with corrected indices
|
|
||||||
var newFoldouts = new Dictionary<int, bool>();
|
|
||||||
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;
|
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
EditorUtility.SetDirty(serializedObject.targetObject);
|
ClearBindingReferences(controller, deletedControllerId);
|
||||||
|
EditorUtility.SetDirty(controller);
|
||||||
GUIUtility.ExitGUI();
|
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<int, bool>();
|
||||||
|
foreach (var pair in _foldouts)
|
||||||
{
|
{
|
||||||
controller.SetControllerIndexInternal(i, 0);
|
int nextIndex = pair.Key > removedIndex ? pair.Key - 1 : pair.Key;
|
||||||
|
remapped[nextIndex] = pair.Value;
|
||||||
}
|
}
|
||||||
EditorUtility.SetDirty(controller);
|
|
||||||
SceneView.RepaintAll();
|
_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<UXBinding> bindings = controller.Bindings;
|
||||||
|
for (int i = 0; i < bindings.Count; i++)
|
||||||
|
{
|
||||||
|
UXBinding binding = bindings[i];
|
||||||
|
if (binding == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var so = new SerializedObject(binding);
|
||||||
|
SerializedProperty entriesProp = so.FindProperty("_entries");
|
||||||
|
bool changed = false;
|
||||||
|
|
||||||
|
for (int entryIndex = 0; entryIndex < entriesProp.arraySize; entryIndex++)
|
||||||
|
{
|
||||||
|
SerializedProperty bindingEntry = entriesProp.GetArrayElementAtIndex(entryIndex);
|
||||||
|
SerializedProperty controllerIdProp = bindingEntry.FindPropertyRelative("_controllerId");
|
||||||
|
if (controllerIdProp.stringValue == deletedControllerId)
|
||||||
|
{
|
||||||
|
controllerIdProp.stringValue = string.Empty;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed)
|
||||||
|
{
|
||||||
|
so.ApplyModifiedProperties();
|
||||||
|
EditorUtility.SetDirty(binding);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 627b7a51244f46ce8bcbd31e6cedf4f8
|
guid: 627b7a51244f46ce8bcbd31e6cedf4f8
|
||||||
timeCreated: 1764127335
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
268
Editor/UX/Controller/UXControllerSceneOverlayWindow.cs
Normal file
268
Editor/UX/Controller/UXControllerSceneOverlayWindow.cs
Normal file
@ -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<UXControllerSceneOverlayWindow>("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<SceneView>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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<UXController> 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<UXController> 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<UXController> GetSceneControllers()
|
||||||
|
{
|
||||||
|
var results = new List<UXController>();
|
||||||
|
UXController[] allControllers = Resources.FindObjectsOfTypeAll<UXController>();
|
||||||
|
|
||||||
|
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
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 671716e6e16c2da419a6a7a374e699e6
|
guid: 7c62b8151e77e4b428e4e79a17d89e3b
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@ -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<UXControllerSceneViewWindow>("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<UXController>();
|
|
||||||
|
|
||||||
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<UXController>();
|
|
||||||
|
|
||||||
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
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: a754ad6b8cb341ada13bb87279b12b78
|
|
||||||
timeCreated: 1764134852
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Base class for all controller states
|
|
||||||
/// </summary>
|
|
||||||
public abstract class ControllerStateBase
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initialize the state when added to a recorder
|
|
||||||
/// </summary>
|
|
||||||
public abstract void Init(UXControllerStateRecorder recorder);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Execute the state when controller index changes
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="recorder">The state recorder</param>
|
|
||||||
/// <param name="entryIndex">The index this state entry is bound to</param>
|
|
||||||
/// <param name="selectedIndex">The currently selected controller index</param>
|
|
||||||
public abstract void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectedIndex);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validate if the state can be properly executed
|
|
||||||
/// </summary>
|
|
||||||
public abstract bool Valid(UXControllerStateRecorder recorder);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Get a human-readable description of this state
|
|
||||||
/// </summary>
|
|
||||||
public virtual string GetDescription()
|
|
||||||
{
|
|
||||||
return GetType().Name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attribute to define display name for a controller state type
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public class ControlerStateNameAttribute : Attribute
|
|
||||||
{
|
|
||||||
public string StateName { get; }
|
|
||||||
|
|
||||||
public ControlerStateNameAttribute(string stateName)
|
|
||||||
{
|
|
||||||
StateName = stateName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Attribute to define if a state can be attached multiple times to the same recorder
|
|
||||||
/// </summary>
|
|
||||||
[AttributeUsage(AttributeTargets.Class)]
|
|
||||||
public class ControlerStateAttachTypeAttribute : Attribute
|
|
||||||
{
|
|
||||||
public bool Repeat { get; }
|
|
||||||
|
|
||||||
public ControlerStateAttachTypeAttribute(bool repeat)
|
|
||||||
{
|
|
||||||
Repeat = repeat;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 774b49ad28de40fda18249bd9a24e38b
|
|
||||||
timeCreated: 1763705196
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// CanvasGroup 透明度状态控制
|
|
||||||
/// 控制 CanvasGroup 的 Alpha 值
|
|
||||||
/// </summary>
|
|
||||||
[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<CanvasGroup>(out var canvasGroup))
|
|
||||||
{
|
|
||||||
_defaultAlpha = canvasGroup.alpha;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
|
|
||||||
{
|
|
||||||
if (recorder != null && recorder.TryGetComponent<CanvasGroup>(out var canvasGroup))
|
|
||||||
{
|
|
||||||
canvasGroup.alpha = (entryIndex == selectionIndex) ? _alpha : _defaultAlpha;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Valid(UXControllerStateRecorder recorder)
|
|
||||||
{
|
|
||||||
return recorder != null && recorder.GetComponent<CanvasGroup>() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDescription()
|
|
||||||
{
|
|
||||||
return $"匹配时: 透明度={_alpha:F2}, 默认={_defaultAlpha:F2}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// CanvasGroup 交互状态控制
|
|
||||||
/// 控制 CanvasGroup 的可交互性和射线检测
|
|
||||||
/// </summary>
|
|
||||||
[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<CanvasGroup>(out var canvasGroup))
|
|
||||||
{
|
|
||||||
_defaultInteractable = canvasGroup.interactable;
|
|
||||||
_defaultBlocksRaycasts = canvasGroup.blocksRaycasts;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
|
|
||||||
{
|
|
||||||
if (recorder != null && recorder.TryGetComponent<CanvasGroup>(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<CanvasGroup>() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDescription()
|
|
||||||
{
|
|
||||||
return $"匹配时: 可交互={_interactable}, 射线检测={_blocksRaycasts}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// GameObject 激活状态控制
|
|
||||||
/// 控制对象的显示/隐藏
|
|
||||||
/// </summary>
|
|
||||||
[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 ? "显示" : "隐藏")}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Graphic 颜色状态控制
|
|
||||||
/// 控制 UI 图形组件的颜色
|
|
||||||
/// </summary>
|
|
||||||
[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<Graphic>(out var graphic))
|
|
||||||
{
|
|
||||||
_defaultColor = graphic.color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
|
|
||||||
{
|
|
||||||
if (recorder != null && recorder.TryGetComponent<Graphic>(out var graphic))
|
|
||||||
{
|
|
||||||
graphic.color = (entryIndex == selectionIndex) ? _color : _defaultColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Valid(UXControllerStateRecorder recorder)
|
|
||||||
{
|
|
||||||
return recorder != null && recorder.GetComponent<Graphic>() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDescription()
|
|
||||||
{
|
|
||||||
return $"匹配时: 颜色={_color}, 默认={_defaultColor}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 75807e24962b62547b1444c2078b77b2
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Graphic 材质状态控制
|
|
||||||
/// 控制 UI 图形组件的材质
|
|
||||||
/// </summary>
|
|
||||||
[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<Graphic>(out var graphic))
|
|
||||||
{
|
|
||||||
_defaultMaterial = graphic.material;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
|
|
||||||
{
|
|
||||||
if (recorder != null && recorder.TryGetComponent<Graphic>(out var graphic))
|
|
||||||
{
|
|
||||||
graphic.material = (entryIndex == selectionIndex) ? _material : _defaultMaterial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Valid(UXControllerStateRecorder recorder)
|
|
||||||
{
|
|
||||||
return recorder != null && recorder.GetComponent<Graphic>() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDescription()
|
|
||||||
{
|
|
||||||
string matched = _material != null ? _material.name : "无";
|
|
||||||
string defaultVal = _defaultMaterial != null ? _defaultMaterial.name : "无";
|
|
||||||
return $"匹配时: {matched}, 默认: {defaultVal}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 1920e338ff84a074997b6d495fdf8641
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Image 精灵图状态控制
|
|
||||||
/// 控制 Image 组件的 Sprite
|
|
||||||
/// </summary>
|
|
||||||
[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<Image>(out var image))
|
|
||||||
{
|
|
||||||
_defaultSprite = image.sprite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
|
|
||||||
{
|
|
||||||
if (recorder != null && recorder.TryGetComponent<Image>(out var image))
|
|
||||||
{
|
|
||||||
image.sprite = (entryIndex == selectionIndex) ? _sprite : _defaultSprite;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Valid(UXControllerStateRecorder recorder)
|
|
||||||
{
|
|
||||||
return recorder != null && recorder.GetComponent<Image>() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDescription()
|
|
||||||
{
|
|
||||||
string matched = _sprite != null ? _sprite.name : "无";
|
|
||||||
string defaultVal = _defaultSprite != null ? _defaultSprite.name : "无";
|
|
||||||
return $"匹配时: {matched}, 默认: {defaultVal}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 628055cb200855e4caccc19755692f8c
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
using TMPro;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Text 颜色状态控制
|
|
||||||
/// 控制 Text 或 TextMeshProUGUI 的文本颜色
|
|
||||||
/// </summary>
|
|
||||||
[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<Text>(out var text))
|
|
||||||
{
|
|
||||||
_defaultColor = text.color;
|
|
||||||
}
|
|
||||||
else if (recorder.TryGetComponent<TextMeshProUGUI>(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<Text>(out var text))
|
|
||||||
{
|
|
||||||
text.color = targetColor;
|
|
||||||
}
|
|
||||||
else if (recorder.TryGetComponent<TextMeshProUGUI>(out var tmp))
|
|
||||||
{
|
|
||||||
tmp.color = targetColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Valid(UXControllerStateRecorder recorder)
|
|
||||||
{
|
|
||||||
return recorder != null &&
|
|
||||||
(recorder.GetComponent<Text>() != null || recorder.GetComponent<TextMeshProUGUI>() != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDescription()
|
|
||||||
{
|
|
||||||
return $"匹配时: 颜色={_color}, 默认={_defaultColor}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: c6ac5efb857bb65439be28f7d81256c4
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.UI;
|
|
||||||
using TMPro;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Text 内容状态控制
|
|
||||||
/// 控制 Text 或 TextMeshProUGUI 的文本内容
|
|
||||||
/// </summary>
|
|
||||||
[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<Text>(out var text))
|
|
||||||
{
|
|
||||||
text.text = _text;
|
|
||||||
}
|
|
||||||
else if (recorder.TryGetComponent<TextMeshProUGUI>(out var tmp))
|
|
||||||
{
|
|
||||||
tmp.text = _text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Valid(UXControllerStateRecorder recorder)
|
|
||||||
{
|
|
||||||
return recorder != null &&
|
|
||||||
(recorder.GetComponent<Text>() != null || recorder.GetComponent<TextMeshProUGUI>() != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDescription()
|
|
||||||
{
|
|
||||||
string preview = _text.Length > 30 ? _text.Substring(0, 30) + "..." : _text;
|
|
||||||
return $"匹配时: \"{preview}\"";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 2601e2ab0d45e3d42b77ab4d5b808794
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Transform 位置状态控制
|
|
||||||
/// 控制 RectTransform 的锚点位置
|
|
||||||
/// </summary>
|
|
||||||
[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<RectTransform>(out var rect))
|
|
||||||
{
|
|
||||||
_defaultPosition = rect.anchoredPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Execute(UXControllerStateRecorder recorder, int entryIndex, int selectionIndex)
|
|
||||||
{
|
|
||||||
if (recorder != null && recorder.TryGetComponent<RectTransform>(out var rect))
|
|
||||||
{
|
|
||||||
rect.anchoredPosition = (entryIndex == selectionIndex) ? _position : _defaultPosition;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Valid(UXControllerStateRecorder recorder)
|
|
||||||
{
|
|
||||||
return recorder != null && recorder.GetComponent<RectTransform>() != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string GetDescription()
|
|
||||||
{
|
|
||||||
return $"匹配时: 位置={_position}, 默认={_defaultPosition}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: fd5741ffd27fc8f489c01426672b1553
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Transform 旋转状态控制
|
|
||||||
/// 控制对象的本地欧拉角旋转
|
|
||||||
/// </summary>
|
|
||||||
[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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 6928446dc2b05ae48b3a2b5547d086cd
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@ -1,44 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Transform 缩放状态控制
|
|
||||||
/// 控制对象的本地缩放
|
|
||||||
/// </summary>
|
|
||||||
[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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 8232f9df6a0c18649b35fc85e9935659
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
419
Runtime/UXComponent/Controller/UXBinding.cs
Normal file
419
Runtime/UXComponent/Controller/UXBinding.cs
Normal file
@ -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<BindingEntry> _entries = new List<BindingEntry>();
|
||||||
|
|
||||||
|
private bool _initialized;
|
||||||
|
|
||||||
|
public UXController Controller => _controller;
|
||||||
|
public List<BindingEntry> 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<UXController>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: b7bbaef07861f9249bca4035e2422181
|
guid: aacb6b471b8bfde4c9d3650f1b9b2b22
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
236
Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs
Normal file
236
Runtime/UXComponent/Controller/UXBindingPropertyUtility.cs
Normal file
@ -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<UXBindingPropertyMetadata> 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<CanvasGroup>() != null;
|
||||||
|
case UXBindingProperty.GraphicColor:
|
||||||
|
case UXBindingProperty.GraphicMaterial:
|
||||||
|
return target.GetComponent<Graphic>() != null;
|
||||||
|
case UXBindingProperty.ImageSprite:
|
||||||
|
return target.GetComponent<Image>() != null;
|
||||||
|
case UXBindingProperty.TextContent:
|
||||||
|
case UXBindingProperty.TextColor:
|
||||||
|
return target.GetComponent<Text>() != null || target.GetComponent<TextMeshProUGUI>() != null;
|
||||||
|
case UXBindingProperty.RectTransformAnchoredPosition:
|
||||||
|
return target.GetComponent<RectTransform>() != null;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void GetSupportedProperties(GameObject target, List<UXBindingProperty> 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<CanvasGroup>().alpha;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.CanvasGroupInteractable:
|
||||||
|
destination.BoolValue = target.GetComponent<CanvasGroup>().interactable;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.CanvasGroupBlocksRaycasts:
|
||||||
|
destination.BoolValue = target.GetComponent<CanvasGroup>().blocksRaycasts;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.GraphicColor:
|
||||||
|
destination.ColorValue = target.GetComponent<Graphic>().color;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.GraphicMaterial:
|
||||||
|
destination.ObjectValue = target.GetComponent<Graphic>().material;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.ImageSprite:
|
||||||
|
destination.ObjectValue = target.GetComponent<Image>().sprite;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.TextContent:
|
||||||
|
if (target.TryGetComponent<Text>(out Text text))
|
||||||
|
{
|
||||||
|
destination.StringValue = text.text;
|
||||||
|
}
|
||||||
|
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmp))
|
||||||
|
{
|
||||||
|
destination.StringValue = tmp.text;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.TextColor:
|
||||||
|
if (target.TryGetComponent<Text>(out Text legacyText))
|
||||||
|
{
|
||||||
|
destination.ColorValue = legacyText.color;
|
||||||
|
}
|
||||||
|
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmpText))
|
||||||
|
{
|
||||||
|
destination.ColorValue = tmpText.color;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.RectTransformAnchoredPosition:
|
||||||
|
destination.Vector2Value = target.GetComponent<RectTransform>().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<CanvasGroup>().alpha = value.FloatValue;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.CanvasGroupInteractable:
|
||||||
|
target.GetComponent<CanvasGroup>().interactable = value.BoolValue;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.CanvasGroupBlocksRaycasts:
|
||||||
|
target.GetComponent<CanvasGroup>().blocksRaycasts = value.BoolValue;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.GraphicColor:
|
||||||
|
target.GetComponent<Graphic>().color = value.ColorValue;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.GraphicMaterial:
|
||||||
|
target.GetComponent<Graphic>().material = value.ObjectValue as Material;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.ImageSprite:
|
||||||
|
target.GetComponent<Image>().sprite = value.ObjectValue as Sprite;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.TextContent:
|
||||||
|
if (target.TryGetComponent<Text>(out Text text))
|
||||||
|
{
|
||||||
|
text.text = value.StringValue;
|
||||||
|
}
|
||||||
|
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmp))
|
||||||
|
{
|
||||||
|
tmp.text = value.StringValue;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.TextColor:
|
||||||
|
if (target.TryGetComponent<Text>(out Text legacyText))
|
||||||
|
{
|
||||||
|
legacyText.color = value.ColorValue;
|
||||||
|
}
|
||||||
|
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmpText))
|
||||||
|
{
|
||||||
|
tmpText.color = value.ColorValue;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.RectTransformAnchoredPosition:
|
||||||
|
target.GetComponent<RectTransform>().anchoredPosition = value.Vector2Value;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.TransformLocalScale:
|
||||||
|
target.transform.localScale = value.Vector3Value;
|
||||||
|
return;
|
||||||
|
case UXBindingProperty.TransformLocalEulerAngles:
|
||||||
|
target.transform.localEulerAngles = value.Vector3Value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: b698383c9842af646b8985aa1fc11a77
|
guid: cd767ac91b0e6d44fa13eee8daebc60f
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@ -1,6 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
using UnityEditor;
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace AlicizaX.UI
|
namespace AlicizaX.UI
|
||||||
{
|
{
|
||||||
@ -9,12 +12,15 @@ namespace AlicizaX.UI
|
|||||||
public sealed class UXController : MonoBehaviour
|
public sealed class UXController : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
public class ControllerData
|
public sealed class ControllerDefinition
|
||||||
{
|
{
|
||||||
|
[SerializeField] private string _id = string.Empty;
|
||||||
[SerializeField] private string _name = "Controller";
|
[SerializeField] private string _name = "Controller";
|
||||||
[SerializeField] private int _length = 2;
|
[SerializeField] private int _length = 2;
|
||||||
[SerializeField] private string _description = "";
|
[SerializeField] private string _description = string.Empty;
|
||||||
[NonSerialized] private int _currentIndex = 0;
|
[NonSerialized] private int _selectedIndex = -1;
|
||||||
|
|
||||||
|
public string Id => _id;
|
||||||
|
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
@ -24,7 +30,7 @@ namespace AlicizaX.UI
|
|||||||
|
|
||||||
public int Length
|
public int Length
|
||||||
{
|
{
|
||||||
get => _length;
|
get => Mathf.Max(1, _length);
|
||||||
set => _length = Mathf.Max(1, value);
|
set => _length = Mathf.Max(1, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -34,240 +40,266 @@ namespace AlicizaX.UI
|
|||||||
set => _description = value;
|
set => _description = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CurrentIndex
|
public int SelectedIndex
|
||||||
{
|
{
|
||||||
get => _currentIndex;
|
get => _selectedIndex;
|
||||||
set => _currentIndex = value;
|
internal set => _selectedIndex = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EnsureId()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_id))
|
||||||
|
{
|
||||||
|
_id = Guid.NewGuid().ToString("N");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SerializeField] private List<ControllerData> _controllers = new List<ControllerData>();
|
[SerializeField] private List<ControllerDefinition> _controllers = new List<ControllerDefinition>();
|
||||||
[SerializeField] private List<UXControllerStateRecorder> _recorders = new List<UXControllerStateRecorder>();
|
[SerializeField] private List<UXBinding> _bindings = new List<UXBinding>();
|
||||||
|
|
||||||
private Dictionary<string, int> _controllerIndexMap;
|
private readonly Dictionary<string, int> _controllerIdMap = new Dictionary<string, int>();
|
||||||
private bool _initialized = false;
|
private readonly Dictionary<string, int> _controllerNameMap = new Dictionary<string, int>();
|
||||||
|
|
||||||
#region Public API
|
|
||||||
|
|
||||||
public IReadOnlyList<ControllerData> Controllers => _controllers;
|
|
||||||
|
|
||||||
|
public IReadOnlyList<ControllerDefinition> Controllers => _controllers;
|
||||||
|
public IReadOnlyList<UXBinding> Bindings => _bindings;
|
||||||
public int ControllerCount => _controllers.Count;
|
public int ControllerCount => _controllers.Count;
|
||||||
|
|
||||||
public ControllerHandle GetController(string name)
|
public bool TryGetControllerById(string controllerId, out ControllerDefinition controller)
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
controller = null;
|
||||||
if (_controllerIndexMap.TryGetValue(name, out int index))
|
if (string.IsNullOrWhiteSpace(controllerId))
|
||||||
{
|
{
|
||||||
return new ControllerHandle(this, index);
|
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 null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ControllerHandle GetControllerAt(int index)
|
return _controllers[index];
|
||||||
{
|
|
||||||
if (index < 0 || index >= _controllers.Count) return null;
|
|
||||||
return new ControllerHandle(this, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetControllerIndex(string name, int selectedIndex)
|
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();
|
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();
|
return binding != null && _bindings.Contains(binding);
|
||||||
if (_controllerIndexMap.TryGetValue(name, out int controllerIndex))
|
}
|
||||||
|
|
||||||
|
internal void RegisterBinding(UXBinding binding)
|
||||||
{
|
{
|
||||||
return _controllers[controllerIndex].CurrentIndex;
|
if (binding == null)
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Internal API
|
|
||||||
|
|
||||||
internal IReadOnlyList<UXControllerStateRecorder> Recorders => _recorders;
|
|
||||||
|
|
||||||
internal void SetControllerIndexInternal(int controllerIndex, int selectedIndex)
|
|
||||||
{
|
{
|
||||||
if (controllerIndex < 0 || controllerIndex >= _controllers.Count) return;
|
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 (!_bindings.Contains(binding))
|
||||||
{
|
{
|
||||||
if (recorder == null) return false;
|
_bindings.Add(binding);
|
||||||
return _recorders.Exists(r => r != null && r.ID == recorder.ID);
|
#if UNITY_EDITOR
|
||||||
|
if (!Application.isPlaying)
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(this);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
internal void RegisterRecorder(UXControllerStateRecorder recorder)
|
|
||||||
{
|
|
||||||
if (recorder != null && !HasRecorder(recorder))
|
|
||||||
{
|
|
||||||
_recorders.Add(recorder);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
_bindings.Remove(binding);
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
if (!Application.isPlaying)
|
||||||
|
{
|
||||||
|
EditorUtility.SetDirty(this);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#region Unity Lifecycle
|
private void Reset()
|
||||||
|
{
|
||||||
|
if (_controllers.Count == 0)
|
||||||
|
{
|
||||||
|
_controllers.Add(new ControllerDefinition());
|
||||||
|
}
|
||||||
|
|
||||||
|
RebuildMaps();
|
||||||
|
}
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
{
|
{
|
||||||
InitializeRuntime();
|
EnsureInitialized();
|
||||||
|
|
||||||
|
for (int i = 0; i < _bindings.Count; i++)
|
||||||
|
{
|
||||||
|
if (_bindings[i] != null)
|
||||||
|
{
|
||||||
|
_bindings[i].Initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetAllControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
BuildControllerIndexMap();
|
RebuildMaps();
|
||||||
|
CleanupBindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Private Methods
|
|
||||||
|
|
||||||
private void EnsureInitialized()
|
private void EnsureInitialized()
|
||||||
{
|
{
|
||||||
if (!_initialized)
|
if (_controllerIdMap.Count == 0 && _controllerNameMap.Count == 0)
|
||||||
{
|
{
|
||||||
BuildControllerIndexMap();
|
RebuildMaps();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildControllerIndexMap()
|
private void RebuildMaps()
|
||||||
{
|
{
|
||||||
if (_controllerIndexMap == null)
|
_controllerIdMap.Clear();
|
||||||
{
|
_controllerNameMap.Clear();
|
||||||
_controllerIndexMap = new Dictionary<string, int>();
|
|
||||||
}
|
var usedNames = new HashSet<string>(StringComparer.Ordinal);
|
||||||
else
|
|
||||||
{
|
|
||||||
_controllerIndexMap.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < _controllers.Count; i++)
|
for (int i = 0; i < _controllers.Count; i++)
|
||||||
{
|
{
|
||||||
var controller = _controllers[i];
|
ControllerDefinition controller = _controllers[i];
|
||||||
if (!string.IsNullOrEmpty(controller.Name))
|
if (controller == null)
|
||||||
{
|
{
|
||||||
_controllerIndexMap[controller.Name] = i;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_initialized = true;
|
private void CleanupBindings()
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeRuntime()
|
|
||||||
{
|
{
|
||||||
EnsureInitialized();
|
for (int i = _bindings.Count - 1; i >= 0; i--)
|
||||||
|
|
||||||
for (int i = 0; i < _recorders.Count; 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)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _recorders.Count; i++)
|
|
||||||
{
|
|
||||||
if (_recorders[i] != null)
|
|
||||||
{
|
|
||||||
_recorders[i].OnControllerIndexChanged(controllerName, selectedIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
private bool SetControllerIndexInternal(ControllerDefinition controller, int selectedIndex, bool force = false)
|
||||||
|
|
||||||
#region Controller Handle
|
|
||||||
|
|
||||||
public class ControllerHandle
|
|
||||||
{
|
{
|
||||||
private readonly UXController _owner;
|
if (controller == null)
|
||||||
private readonly int _controllerIndex;
|
|
||||||
|
|
||||||
public ControllerHandle(UXController owner, int controllerIndex)
|
|
||||||
{
|
{
|
||||||
_owner = owner;
|
return false;
|
||||||
_controllerIndex = controllerIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsValid => _owner != null && _controllerIndex >= 0 && _controllerIndex < _owner._controllers.Count;
|
selectedIndex = Mathf.Clamp(selectedIndex, 0, controller.Length - 1);
|
||||||
|
if (!force && controller.SelectedIndex == selectedIndex)
|
||||||
public int SelectedIndex
|
|
||||||
{
|
{
|
||||||
get
|
return false;
|
||||||
{
|
|
||||||
if (!IsValid) return 0;
|
|
||||||
return _owner._controllers[_controllerIndex].CurrentIndex;
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (IsValid)
|
|
||||||
{
|
|
||||||
_owner.SetControllerIndexInternal(_controllerIndex, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Length
|
controller.SelectedIndex = selectedIndex;
|
||||||
{
|
NotifyBindings(controller.Id, selectedIndex);
|
||||||
get
|
return true;
|
||||||
{
|
|
||||||
if (!IsValid) return 0;
|
|
||||||
return _owner._controllers[_controllerIndex].Length;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name
|
private void NotifyBindings(string controllerId, int selectedIndex)
|
||||||
{
|
{
|
||||||
get
|
for (int i = 0; i < _bindings.Count; i++)
|
||||||
{
|
{
|
||||||
if (!IsValid) return null;
|
UXBinding binding = _bindings[i];
|
||||||
return _owner._controllers[_controllerIndex].Name;
|
if (binding != null)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Description
|
|
||||||
{
|
{
|
||||||
get
|
binding.OnControllerChanged(controllerId, selectedIndex);
|
||||||
{
|
|
||||||
if (!IsValid) return null;
|
|
||||||
return _owner._controllers[_controllerIndex].Description;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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<StateEntry> _stateEntries = new List<StateEntry>();
|
|
||||||
|
|
||||||
internal List<StateEntry> 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 35829408994740a08c32ac2f519442f0
|
|
||||||
timeCreated: 1763705324
|
|
||||||
Loading…
Reference in New Issue
Block a user