#if UNITY_EDITOR using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace UnityEngine.UI { [CustomEditor(typeof(UXBinding))] public sealed class UXBindingEditor : UnityEditor.Editor { private readonly struct AddRuleOption { public readonly string ControllerId; public readonly string ControllerName; public readonly UXBindingProperty Property; public readonly string PropertyName; public AddRuleOption(string controllerId, string controllerName, UXBindingProperty property, string propertyName) { ControllerId = controllerId; ControllerName = controllerName; Property = property; PropertyName = propertyName; } } private sealed class AddRulePopup : PopupWindowContent { private readonly UXBindingEditor _editor; private readonly UXBinding _binding; private readonly List _options; private readonly bool _showControllerName; private string _search = string.Empty; private Vector2 _scroll; public AddRulePopup(UXBindingEditor editor, UXBinding binding, List options, bool showControllerName) { _editor = editor; _binding = binding; _options = new List(options); _showControllerName = showControllerName; } public override Vector2 GetWindowSize() { return new Vector2(360f, 320f); } public override void OnGUI(Rect rect) { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.LabelField("Add Rule", EditorStyles.boldLabel); EditorGUILayout.EndVertical(); EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); _search = GUILayout.TextField(_search, GUI.skin.FindStyle("ToolbarSearchTextField") ?? EditorStyles.toolbarSearchField, GUILayout.ExpandWidth(true)); if (GUILayout.Button(string.Empty, GUI.skin.FindStyle("ToolbarSearchCancelButton") ?? EditorStyles.toolbarButton, GUILayout.Width(18f))) { _search = string.Empty; GUI.FocusControl(null); } EditorGUILayout.EndHorizontal(); _scroll = EditorGUILayout.BeginScrollView(_scroll); for (int i = 0; i < _options.Count; i++) { AddRuleOption option = _options[i]; if (!IsMatch(option, _search)) { continue; } string label = _showControllerName ? $"{option.ControllerName} / {option.PropertyName}" : option.PropertyName; if (GUILayout.Button(label, EditorStyles.miniButton)) { _editor.AddEntry(_binding, option.ControllerId, option.Property); editorWindow.Close(); } } EditorGUILayout.EndScrollView(); } private static bool IsMatch(AddRuleOption option, string search) { if (string.IsNullOrEmpty(search)) { return true; } return option.ControllerName.IndexOf(search, System.StringComparison.OrdinalIgnoreCase) >= 0 || option.PropertyName.IndexOf(search, System.StringComparison.OrdinalIgnoreCase) >= 0; } } private SerializedProperty _controllerProp; private SerializedProperty _entriesProp; private readonly Dictionary _foldouts = new Dictionary(); private readonly List _supportedProperties = new List(); private string[] _controllerNames = System.Array.Empty(); private string[] _indexNames = System.Array.Empty(); private GUIStyle _pillOn; private GUIStyle _pillOff; private readonly List _addRuleOptions = new List(); private GUIContent _addRuleContent; private GUIContent _autoBindContent; private GUIContent _captureContent; private GUIContent _resetContent; private GUIContent _expandAllContent; private GUIContent _collapseAllContent; private GUIContent _upContent; private GUIContent _downContent; private GUIContent _deleteContent; private void OnEnable() { _controllerProp = serializedObject.FindProperty("_controller"); _entriesProp = serializedObject.FindProperty("_entries"); InitializeContents(); } private void InitializeContents() { _addRuleContent = EditorGUIUtility.IconContent("Toolbar Plus", "Add binding rule"); _autoBindContent = EditorGUIUtility.IconContent("d_Prefab Icon", "Auto bind parent UXController"); _captureContent = EditorGUIUtility.IconContent("d_SaveAs", "Capture defaults"); _resetContent = EditorGUIUtility.IconContent("d_Refresh", "Reset to defaults"); _expandAllContent = EditorGUIUtility.IconContent("d_scrollup", "Expand all rules"); _collapseAllContent = EditorGUIUtility.IconContent("d_scrolldown", "Collapse all rules"); _upContent = EditorGUIUtility.IconContent("d_scrollup", "Move up"); _downContent = EditorGUIUtility.IconContent("d_scrolldown", "Move down"); _deleteContent = EditorGUIUtility.IconContent("TreeEditor.Trash", "Delete rule"); } public override void OnInspectorGUI() { serializedObject.Update(); EnsureStyles(); var binding = (UXBinding)target; DrawHeader(binding); EditorGUILayout.Space(6f); DrawEntries(binding); serializedObject.ApplyModifiedProperties(); } private void DrawHeader(UXBinding binding) { EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("UX Binding", EditorStyles.boldLabel, GUILayout.Width(82f)); EditorGUI.BeginChangeCheck(); UXController newController = (UXController)EditorGUILayout.ObjectField(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.LabelField(binding.Controller.ControllerCount.ToString(), EditorStyles.miniLabel, GUILayout.Width(18f)); } GUILayout.FlexibleSpace(); if (GUILayout.Button(_addRuleContent, EditorStyles.miniButtonLeft, GUILayout.Width(28f))) { ShowAddRuleMenu(binding, GUILayoutUtility.GetLastRect()); } if (GUILayout.Button(_autoBindContent, EditorStyles.miniButtonMid, GUILayout.Width(28f))) { AutoBindController(binding); } if (GUILayout.Button(_captureContent, EditorStyles.miniButtonMid, GUILayout.Width(28f))) { binding.CaptureDefaults(); EditorUtility.SetDirty(binding); } if (GUILayout.Button(_resetContent, EditorStyles.miniButtonRight, GUILayout.Width(28f))) { binding.ResetToDefaults(); EditorUtility.SetDirty(binding); SceneView.RepaintAll(); } EditorGUILayout.EndHorizontal(); if (binding.Controller == null) { EditorGUILayout.HelpBox("Assign a UXController or use Auto Bind.", MessageType.Warning); } 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 controllerIndexMaskProp = entryProp.FindPropertyRelative("_controllerIndexMask"); SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property"); SerializedProperty valueProp = entryProp.FindPropertyRelative("_value"); SerializedProperty indexedValuesProp = entryProp.FindPropertyRelative("_indexedValues"); 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; bool propertySupported = _supportedProperties.Contains(property); bool controllerResolved = TryGetControllerName(binding.Controller, controllerIdProp.stringValue, out string controllerName); EditorGUILayout.BeginVertical(EditorStyles.helpBox); EditorGUILayout.BeginHorizontal(); expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {label}", true); GUILayout.FlexibleSpace(); EditorGUI.BeginDisabledGroup(index == 0); if (GUILayout.Button(_upContent, EditorStyles.miniButtonLeft, GUILayout.Width(24f))) { MoveEntry(index, index - 1); } EditorGUI.EndDisabledGroup(); EditorGUI.BeginDisabledGroup(index >= _entriesProp.arraySize - 1); if (GUILayout.Button(_downContent, EditorStyles.miniButtonMid, GUILayout.Width(24f))) { MoveEntry(index, index + 1); } EditorGUI.EndDisabledGroup(); if (GUILayout.Button(_deleteContent, EditorStyles.miniButtonRight, GUILayout.Width(24f))) { DeleteEntry(binding, index); } EditorGUILayout.EndHorizontal(); _foldouts[index] = expanded; if (expanded) { UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(property); bool indexChanged = DrawControllerSelector(binding, controllerIdProp, controllerIndexProp, controllerIndexMaskProp, property == UXBindingProperty.GameObjectActive); if (property == UXBindingProperty.GameObjectActive) { DrawGameObjectActiveHint(controllerIndexMaskProp.intValue); ForceGameObjectActiveValues(valueProp, fallbackModeProp, fallbackValueProp); if (indexChanged) { serializedObject.ApplyModifiedProperties(); binding.ApplyEntryValue(index, GetFirstSelectedIndex(controllerIndexMaskProp.intValue)); EditorUtility.SetDirty(binding); SceneView.RepaintAll(); } } else { int selectedIndex = GetFirstSelectedIndex(controllerIndexMaskProp.intValue); if (indexChanged) { serializedObject.ApplyModifiedProperties(); binding.ApplyEntryValue(index, selectedIndex); EditorUtility.SetDirty(binding); SceneView.RepaintAll(); } EditorGUILayout.Space(2f); EditorGUILayout.LabelField("Value", EditorStyles.boldLabel); SerializedProperty selectedValueProp = GetIndexedValueProperty(indexedValuesProp, valueProp, selectedIndex); EditorGUI.BeginChangeCheck(); DrawValueField(selectedValueProp, metadata, $"Index {selectedIndex} Value"); if (EditorGUI.EndChangeCheck()) { serializedObject.ApplyModifiedProperties(); binding.ApplyEntryValue(index, selectedIndex); EditorUtility.SetDirty(binding); SceneView.RepaintAll(); } EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Use Current")) { binding.CaptureEntryValue(index, selectedIndex); 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 (!controllerResolved) { EditorGUILayout.HelpBox("Controller reference is missing or points to a deleted controller definition.", MessageType.Error); } if (!propertySupported) { EditorGUILayout.HelpBox("This property is not supported by the components on the current GameObject.", MessageType.Error); } } EditorGUILayout.EndVertical(); } private bool DrawControllerSelector(UXBinding binding, SerializedProperty controllerIdProp, SerializedProperty controllerIndexProp, SerializedProperty controllerIndexMaskProp, bool multiSelect) { UXController controller = binding.Controller; if (controller == null || controller.Controllers.Count == 0) { EditorGUILayout.HelpBox("Create a controller definition first.", MessageType.Info); return false; } bool changed = false; EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Controller", EditorStyles.miniLabel, GUILayout.Width(64f)); EnsureStringArray(ref _controllerNames, controller.Controllers.Count); int selectedController = 0; for (int i = 0; i < controller.Controllers.Count; i++) { UXController.ControllerDefinition definition = controller.Controllers[i]; _controllerNames[i] = definition.Name; if (definition.Id == controllerIdProp.stringValue) { selectedController = i; } } EditorGUI.BeginChangeCheck(); selectedController = EditorGUILayout.Popup(selectedController, _controllerNames, GUILayout.MinWidth(90f)); if (EditorGUI.EndChangeCheck()) { controllerIdProp.stringValue = controller.Controllers[selectedController].Id; controllerIndexProp.intValue = 0; controllerIndexMaskProp.intValue = 1; changed = true; } UXController.ControllerDefinition selectedDefinition = controller.Controllers[selectedController]; int maxIndex = Mathf.Max(1, selectedDefinition.Length); controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, maxIndex - 1); controllerIndexMaskProp.intValue = ClampMask(controllerIndexMaskProp.intValue, maxIndex, controllerIndexProp.intValue); changed |= DrawIndexMask(controllerIndexMaskProp, controllerIndexProp, maxIndex, multiSelect); EditorGUILayout.EndHorizontal(); return changed; } 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, string controllerId, UXBindingProperty property) { int index = _entriesProp.arraySize; _entriesProp.InsertArrayElementAtIndex(index); SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index); SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId"); SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex"); SerializedProperty controllerIndexMaskProp = entryProp.FindPropertyRelative("_controllerIndexMask"); SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property"); SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode"); SerializedProperty valueProp = entryProp.FindPropertyRelative("_value"); SerializedProperty indexedValuesProp = entryProp.FindPropertyRelative("_indexedValues"); SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue"); SerializedProperty capturedDefaultProp = entryProp.FindPropertyRelative("_capturedDefault"); SerializedProperty hasCapturedDefaultProp = entryProp.FindPropertyRelative("_hasCapturedDefault"); SerializedProperty capturedPropertyProp = entryProp.FindPropertyRelative("_capturedProperty"); controllerIdProp.stringValue = controllerId; controllerIndexProp.intValue = 0; controllerIndexMaskProp.intValue = 1; propertyProp.enumValueIndex = (int)property; fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault; ResetValue(valueProp); indexedValuesProp.ClearArray(); ResetValue(fallbackValueProp); ResetValue(capturedDefaultProp); hasCapturedDefaultProp.boolValue = false; capturedPropertyProp.enumValueIndex = (int)property; ApplyDefaultFallbackForProperty(entryProp, property); _foldouts[index] = true; serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(binding); } private void ShowAddRuleMenu(UXBinding binding, Rect activatorRect) { if (binding.Controller == null || binding.Controller.Controllers.Count == 0) { EditorUtility.DisplayDialog("Add UX Binding Rule", "Assign a UXController before adding rules.", "OK"); return; } UXBindingPropertyUtility.GetSupportedProperties(binding.gameObject, _supportedProperties); _addRuleOptions.Clear(); for (int controllerIndex = 0; controllerIndex < binding.Controller.Controllers.Count; controllerIndex++) { UXController.ControllerDefinition controller = binding.Controller.Controllers[controllerIndex]; if (controller == null) { continue; } for (int propertyIndex = 0; propertyIndex < _supportedProperties.Count; propertyIndex++) { UXBindingProperty property = _supportedProperties[propertyIndex]; if (HasRule(controller.Id, property)) { continue; } UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(property); _addRuleOptions.Add(new AddRuleOption(controller.Id, controller.Name, property, metadata.DisplayName)); } } if (_addRuleOptions.Count == 0) { EditorUtility.DisplayDialog("Add UX Binding Rule", "All supported states already exist.", "OK"); return; } PopupWindow.Show(activatorRect, new AddRulePopup(this, binding, _addRuleOptions, binding.Controller.Controllers.Count > 1)); } 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; } if (!EditorUtility.DisplayDialog( "Delete UX Binding Rule", $"Delete binding rule {index}? This cannot be undone outside Unity Undo.", "Delete", "Cancel")) { return; } Undo.RecordObject(binding, "Delete UX Binding Rule"); _entriesProp.DeleteArrayElementAtIndex(index); CleanupFoldouts(index); serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(binding); GUIUtility.ExitGUI(); } private void MoveEntry(int fromIndex, int toIndex) { if (fromIndex < 0 || toIndex < 0 || fromIndex >= _entriesProp.arraySize || toIndex >= _entriesProp.arraySize) { return; } _entriesProp.MoveArrayElement(fromIndex, toIndex); bool fromExpanded = _foldouts.ContainsKey(fromIndex) && _foldouts[fromIndex]; bool toExpanded = _foldouts.ContainsKey(toIndex) && _foldouts[toIndex]; _foldouts[fromIndex] = toExpanded; _foldouts[toIndex] = fromExpanded; serializedObject.ApplyModifiedProperties(); GUIUtility.ExitGUI(); } private void CleanupFoldouts(int removedIndex) { _foldouts.Remove(removedIndex); var remapped = new Dictionary(); foreach (var pair in _foldouts) { int nextIndex = pair.Key > removedIndex ? pair.Key - 1 : pair.Key; remapped[nextIndex] = pair.Value; } _foldouts.Clear(); foreach (var pair in remapped) { _foldouts[pair.Key] = pair.Value; } } private static void ApplyDefaultFallbackForProperty(SerializedProperty entryProp, UXBindingProperty property) { SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode"); SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue"); if (property == UXBindingProperty.GameObjectActive) { fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.UseCustomValue; fallbackValueProp.FindPropertyRelative("_boolValue").boolValue = false; return; } if ((UXBindingFallbackMode)fallbackModeProp.enumValueIndex == UXBindingFallbackMode.UseCustomValue) { fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault; ResetValue(fallbackValueProp); } } private void AutoBindController(UXBinding binding) { UXController controller = binding.GetComponentInParent(); if (controller == null) { EditorUtility.DisplayDialog("Auto Bind UX Controller", "No UXController found in parents.", "OK"); return; } Undo.RecordObject(binding, "Auto Bind UX Controller"); binding.SetController(controller); _controllerProp.objectReferenceValue = controller; serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(binding); } private void EnsureStyles() { if (_pillOn != null) { return; } _pillOn = new GUIStyle(EditorStyles.miniButton) { fontStyle = FontStyle.Bold, fixedHeight = 18f, margin = new RectOffset(1, 1, 1, 1), padding = new RectOffset(2, 2, 1, 1) }; _pillOff = new GUIStyle(EditorStyles.miniButton) { fixedHeight = 18f, margin = new RectOffset(1, 1, 1, 1), padding = new RectOffset(2, 2, 1, 1) }; } private bool DrawIndexMask(SerializedProperty maskProp, SerializedProperty indexProp, int length, bool multiSelect) { EditorGUILayout.LabelField(multiSelect ? "Active" : "Index", EditorStyles.miniLabel, GUILayout.Width(38f)); int mask = maskProp.intValue; int originalMask = mask; for (int i = 0; i < length; i++) { int bit = UXBinding.BindingEntry.IndexToMask(i); bool selected = (mask & bit) != 0; bool nextSelected = GUILayout.Toggle(selected, i.ToString(), selected ? _pillOn : _pillOff, GUILayout.Width(26f)); if (nextSelected != selected) { if (multiSelect) { if (nextSelected) { mask |= bit; } else { mask &= ~bit; } } else { mask = bit; } } } if (mask == 0) { mask = UXBinding.BindingEntry.IndexToMask(Mathf.Clamp(indexProp.intValue, 0, length - 1)); } maskProp.intValue = ClampMask(mask, length, indexProp.intValue); indexProp.intValue = GetFirstSelectedIndex(maskProp.intValue); return maskProp.intValue != originalMask; } private static SerializedProperty GetIndexedValueProperty(SerializedProperty indexedValuesProp, SerializedProperty fallbackValueProp, int selectedIndex) { for (int i = 0; i < indexedValuesProp.arraySize; i++) { SerializedProperty indexedValueProp = indexedValuesProp.GetArrayElementAtIndex(i); if (indexedValueProp.FindPropertyRelative("_index").intValue == selectedIndex) { return indexedValueProp.FindPropertyRelative("_value"); } } int nextIndex = indexedValuesProp.arraySize; indexedValuesProp.InsertArrayElementAtIndex(nextIndex); SerializedProperty nextValueProp = indexedValuesProp.GetArrayElementAtIndex(nextIndex); nextValueProp.FindPropertyRelative("_index").intValue = selectedIndex; CopyValue(fallbackValueProp, nextValueProp.FindPropertyRelative("_value")); return nextValueProp.FindPropertyRelative("_value"); } private static void CopyValue(SerializedProperty source, SerializedProperty destination) { destination.FindPropertyRelative("_boolValue").boolValue = source.FindPropertyRelative("_boolValue").boolValue; destination.FindPropertyRelative("_floatValue").floatValue = source.FindPropertyRelative("_floatValue").floatValue; destination.FindPropertyRelative("_stringValue").stringValue = source.FindPropertyRelative("_stringValue").stringValue; destination.FindPropertyRelative("_colorValue").colorValue = source.FindPropertyRelative("_colorValue").colorValue; destination.FindPropertyRelative("_vector2Value").vector2Value = source.FindPropertyRelative("_vector2Value").vector2Value; destination.FindPropertyRelative("_vector3Value").vector3Value = source.FindPropertyRelative("_vector3Value").vector3Value; destination.FindPropertyRelative("_objectValue").objectReferenceValue = source.FindPropertyRelative("_objectValue").objectReferenceValue; } private static void DrawGameObjectActiveHint(int mask) { EditorGUILayout.LabelField($"Visible: {BuildIndexLabel(mask)} Hidden: others", EditorStyles.miniLabel); } private static void ForceGameObjectActiveValues(SerializedProperty valueProp, SerializedProperty fallbackModeProp, SerializedProperty fallbackValueProp) { valueProp.FindPropertyRelative("_boolValue").boolValue = true; fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.UseCustomValue; fallbackValueProp.FindPropertyRelative("_boolValue").boolValue = false; } private bool HasRule(string controllerId, UXBindingProperty property) { for (int i = 0; i < _entriesProp.arraySize; i++) { SerializedProperty entry = _entriesProp.GetArrayElementAtIndex(i); if (entry.FindPropertyRelative("_controllerId").stringValue == controllerId && entry.FindPropertyRelative("_property").enumValueIndex == (int)property) { return true; } } return false; } private void SetAllFoldouts(bool expanded) { for (int i = 0; i < _entriesProp.arraySize; i++) { _foldouts[i] = expanded; } } private static bool TryGetControllerName(UXController controller, string controllerId, out string controllerName) { controllerName = string.Empty; if (controller == null || string.IsNullOrEmpty(controllerId)) { return false; } for (int i = 0; i < controller.Controllers.Count; i++) { UXController.ControllerDefinition definition = controller.Controllers[i]; if (definition != null && definition.Id == controllerId) { controllerName = definition.Name; return true; } } return false; } private static string BuildIndexLabel(int mask) { if (mask == 0) { return "0"; } string label = string.Empty; for (int i = 0; i < 31; i++) { if ((mask & UXBinding.BindingEntry.IndexToMask(i)) == 0) { continue; } label = string.IsNullOrEmpty(label) ? i.ToString() : $"{label},{i}"; } return label; } private static int ClampMask(int mask, int length, int fallbackIndex) { int validMask = 0; int max = Mathf.Min(length, 31); for (int i = 0; i < max; i++) { validMask |= UXBinding.BindingEntry.IndexToMask(i); } mask &= validMask; if (mask == 0) { mask = UXBinding.BindingEntry.IndexToMask(Mathf.Clamp(fallbackIndex, 0, max - 1)); } return mask; } private static int GetFirstSelectedIndex(int mask) { for (int i = 0; i < 31; i++) { if ((mask & UXBinding.BindingEntry.IndexToMask(i)) != 0) { return i; } } return 0; } private static void EnsureStringArray(ref string[] array, int length) { if (array.Length != length) { array = new string[length]; } } } } #endif