优化UIExtension扩展系统
优化UXBinding编辑器 优化UXBinding性能 优化UXBinding Bug 优化Hotkey注册器性能 优化UXNavigation导航性能
This commit is contained in:
parent
8c3c13634d
commit
526341579a
@ -8,27 +8,138 @@ namespace UnityEngine.UI
|
|||||||
[CustomEditor(typeof(UXBinding))]
|
[CustomEditor(typeof(UXBinding))]
|
||||||
public sealed class UXBindingEditor : UnityEditor.Editor
|
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<AddRuleOption> _options;
|
||||||
|
private readonly bool _showControllerName;
|
||||||
|
private string _search = string.Empty;
|
||||||
|
private Vector2 _scroll;
|
||||||
|
|
||||||
|
public AddRulePopup(UXBindingEditor editor, UXBinding binding, List<AddRuleOption> options, bool showControllerName)
|
||||||
|
{
|
||||||
|
_editor = editor;
|
||||||
|
_binding = binding;
|
||||||
|
_options = new List<AddRuleOption>(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 _controllerProp;
|
||||||
private SerializedProperty _entriesProp;
|
private SerializedProperty _entriesProp;
|
||||||
private readonly Dictionary<int, bool> _foldouts = new Dictionary<int, bool>();
|
private readonly Dictionary<int, bool> _foldouts = new Dictionary<int, bool>();
|
||||||
private readonly List<UXBindingProperty> _supportedProperties = new List<UXBindingProperty>();
|
private readonly List<UXBindingProperty> _supportedProperties = new List<UXBindingProperty>();
|
||||||
|
private string[] _controllerNames = System.Array.Empty<string>();
|
||||||
|
private string[] _indexNames = System.Array.Empty<string>();
|
||||||
|
private GUIStyle _pillOn;
|
||||||
|
private GUIStyle _pillOff;
|
||||||
|
private readonly List<AddRuleOption> _addRuleOptions = new List<AddRuleOption>();
|
||||||
|
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()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
_controllerProp = serializedObject.FindProperty("_controller");
|
_controllerProp = serializedObject.FindProperty("_controller");
|
||||||
_entriesProp = serializedObject.FindProperty("_entries");
|
_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()
|
public override void OnInspectorGUI()
|
||||||
{
|
{
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
EnsureStyles();
|
||||||
|
|
||||||
var binding = (UXBinding)target;
|
var binding = (UXBinding)target;
|
||||||
|
|
||||||
DrawHeader(binding);
|
DrawHeader(binding);
|
||||||
EditorGUILayout.Space(6f);
|
EditorGUILayout.Space(6f);
|
||||||
DrawControllerField(binding);
|
|
||||||
EditorGUILayout.Space(6f);
|
|
||||||
DrawEntries(binding);
|
DrawEntries(binding);
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
@ -37,39 +148,11 @@ namespace UnityEngine.UI
|
|||||||
private void DrawHeader(UXBinding binding)
|
private void DrawHeader(UXBinding binding)
|
||||||
{
|
{
|
||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
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();
|
EditorGUILayout.BeginHorizontal();
|
||||||
if (GUILayout.Button("Add Rule"))
|
EditorGUILayout.LabelField("UX Binding", EditorStyles.boldLabel, GUILayout.Width(82f));
|
||||||
{
|
|
||||||
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();
|
EditorGUI.BeginChangeCheck();
|
||||||
UXController newController = (UXController)EditorGUILayout.ObjectField("Reference", binding.Controller, typeof(UXController), true);
|
UXController newController = (UXController)EditorGUILayout.ObjectField(binding.Controller, typeof(UXController), true);
|
||||||
if (EditorGUI.EndChangeCheck())
|
if (EditorGUI.EndChangeCheck())
|
||||||
{
|
{
|
||||||
Undo.RecordObject(binding, "Change UX Binding Controller");
|
Undo.RecordObject(binding, "Change UX Binding Controller");
|
||||||
@ -78,13 +161,40 @@ namespace UnityEngine.UI
|
|||||||
EditorUtility.SetDirty(binding);
|
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)
|
if (binding.Controller == null)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("Assign a UXController on this object or one of its parents.", MessageType.Warning);
|
EditorGUILayout.HelpBox("Assign a UXController or use Auto Bind.", MessageType.Warning);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
EditorGUILayout.LabelField($"Bound To: {binding.Controller.name}", EditorStyles.miniLabel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
@ -112,27 +222,39 @@ namespace UnityEngine.UI
|
|||||||
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
|
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
|
||||||
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
|
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
|
||||||
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
|
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
|
||||||
|
SerializedProperty controllerIndexMaskProp = entryProp.FindPropertyRelative("_controllerIndexMask");
|
||||||
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
|
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
|
||||||
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
|
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
|
||||||
|
SerializedProperty indexedValuesProp = entryProp.FindPropertyRelative("_indexedValues");
|
||||||
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
|
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
|
||||||
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
|
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
|
||||||
|
|
||||||
UXBindingProperty property = (UXBindingProperty)propertyProp.enumValueIndex;
|
UXBindingProperty property = (UXBindingProperty)propertyProp.enumValueIndex;
|
||||||
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
|
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
|
||||||
string label = UXBindingPropertyUtility.GetMetadata(property).DisplayName;
|
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.BeginVertical(EditorStyles.helpBox);
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {label}", true);
|
expanded = EditorGUILayout.Foldout(expanded, $"[{index}] {label}", true);
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
|
|
||||||
if (GUILayout.Button("Preview", EditorStyles.miniButtonLeft, GUILayout.Width(60f)))
|
EditorGUI.BeginDisabledGroup(index == 0);
|
||||||
|
if (GUILayout.Button(_upContent, EditorStyles.miniButtonLeft, GUILayout.Width(24f)))
|
||||||
{
|
{
|
||||||
binding.PreviewEntry(index);
|
MoveEntry(index, index - 1);
|
||||||
SceneView.RepaintAll();
|
|
||||||
}
|
}
|
||||||
|
EditorGUI.EndDisabledGroup();
|
||||||
|
|
||||||
if (GUILayout.Button("X", EditorStyles.miniButtonRight, GUILayout.Width(24f)))
|
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);
|
DeleteEntry(binding, index);
|
||||||
}
|
}
|
||||||
@ -141,39 +263,75 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
if (expanded)
|
if (expanded)
|
||||||
{
|
{
|
||||||
DrawControllerSelector(binding, controllerIdProp, controllerIndexProp);
|
|
||||||
DrawPropertySelector(entryProp, binding.gameObject, propertyProp);
|
|
||||||
|
|
||||||
property = (UXBindingProperty)propertyProp.enumValueIndex;
|
|
||||||
UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(property);
|
UXBindingPropertyMetadata metadata = UXBindingPropertyUtility.GetMetadata(property);
|
||||||
|
bool indexChanged = DrawControllerSelector(binding, controllerIdProp, controllerIndexProp, controllerIndexMaskProp, property == UXBindingProperty.GameObjectActive);
|
||||||
|
|
||||||
EditorGUILayout.Space(2f);
|
if (property == UXBindingProperty.GameObjectActive)
|
||||||
EditorGUILayout.LabelField("Value", EditorStyles.boldLabel);
|
|
||||||
DrawValueField(valueProp, metadata, "Matched Value");
|
|
||||||
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
|
||||||
if (GUILayout.Button("Use Current"))
|
|
||||||
{
|
{
|
||||||
binding.CaptureEntryValue(index);
|
DrawGameObjectActiveHint(controllerIndexMaskProp.intValue);
|
||||||
EditorUtility.SetDirty(binding);
|
ForceGameObjectActiveValues(valueProp, fallbackModeProp, fallbackValueProp);
|
||||||
}
|
if (indexChanged)
|
||||||
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);
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
binding.ApplyEntryValue(index, GetFirstSelectedIndex(controllerIndexMaskProp.intValue));
|
||||||
EditorUtility.SetDirty(binding);
|
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 (!_supportedProperties.Contains(property))
|
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.HelpBox("This property is not supported by the components on the current GameObject.", MessageType.Error);
|
||||||
}
|
}
|
||||||
@ -182,22 +340,25 @@ namespace UnityEngine.UI
|
|||||||
EditorGUILayout.EndVertical();
|
EditorGUILayout.EndVertical();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawControllerSelector(UXBinding binding, SerializedProperty controllerIdProp, SerializedProperty controllerIndexProp)
|
private bool DrawControllerSelector(UXBinding binding, SerializedProperty controllerIdProp, SerializedProperty controllerIndexProp, SerializedProperty controllerIndexMaskProp, bool multiSelect)
|
||||||
{
|
{
|
||||||
UXController controller = binding.Controller;
|
UXController controller = binding.Controller;
|
||||||
if (controller == null || controller.Controllers.Count == 0)
|
if (controller == null || controller.Controllers.Count == 0)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("Create a controller definition first.", MessageType.Info);
|
EditorGUILayout.HelpBox("Create a controller definition first.", MessageType.Info);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
string[] names = new string[controller.Controllers.Count];
|
bool changed = false;
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
EditorGUILayout.LabelField("Controller", EditorStyles.miniLabel, GUILayout.Width(64f));
|
||||||
|
EnsureStringArray(ref _controllerNames, controller.Controllers.Count);
|
||||||
int selectedController = 0;
|
int selectedController = 0;
|
||||||
|
|
||||||
for (int i = 0; i < controller.Controllers.Count; i++)
|
for (int i = 0; i < controller.Controllers.Count; i++)
|
||||||
{
|
{
|
||||||
UXController.ControllerDefinition definition = controller.Controllers[i];
|
UXController.ControllerDefinition definition = controller.Controllers[i];
|
||||||
names[i] = definition.Name;
|
_controllerNames[i] = definition.Name;
|
||||||
if (definition.Id == controllerIdProp.stringValue)
|
if (definition.Id == controllerIdProp.stringValue)
|
||||||
{
|
{
|
||||||
selectedController = i;
|
selectedController = i;
|
||||||
@ -205,59 +366,23 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
EditorGUI.BeginChangeCheck();
|
EditorGUI.BeginChangeCheck();
|
||||||
selectedController = EditorGUILayout.Popup("Controller", selectedController, names);
|
selectedController = EditorGUILayout.Popup(selectedController, _controllerNames, GUILayout.MinWidth(90f));
|
||||||
if (EditorGUI.EndChangeCheck())
|
if (EditorGUI.EndChangeCheck())
|
||||||
{
|
{
|
||||||
controllerIdProp.stringValue = controller.Controllers[selectedController].Id;
|
controllerIdProp.stringValue = controller.Controllers[selectedController].Id;
|
||||||
controllerIndexProp.intValue = 0;
|
controllerIndexProp.intValue = 0;
|
||||||
|
controllerIndexMaskProp.intValue = 1;
|
||||||
|
changed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
UXController.ControllerDefinition selectedDefinition = controller.Controllers[selectedController];
|
UXController.ControllerDefinition selectedDefinition = controller.Controllers[selectedController];
|
||||||
int maxIndex = Mathf.Max(1, selectedDefinition.Length);
|
int maxIndex = Mathf.Max(1, selectedDefinition.Length);
|
||||||
controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, maxIndex - 1);
|
controllerIndexProp.intValue = Mathf.Clamp(controllerIndexProp.intValue, 0, maxIndex - 1);
|
||||||
|
controllerIndexMaskProp.intValue = ClampMask(controllerIndexMaskProp.intValue, maxIndex, controllerIndexProp.intValue);
|
||||||
|
|
||||||
string[] indexNames = new string[maxIndex];
|
changed |= DrawIndexMask(controllerIndexMaskProp, controllerIndexProp, maxIndex, multiSelect);
|
||||||
for (int i = 0; i < maxIndex; i++)
|
EditorGUILayout.EndHorizontal();
|
||||||
{
|
return changed;
|
||||||
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)
|
private void DrawValueField(SerializedProperty valueProp, UXBindingPropertyMetadata metadata, string label)
|
||||||
@ -314,7 +439,7 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddEntry(UXBinding binding)
|
private void AddEntry(UXBinding binding, string controllerId, UXBindingProperty property)
|
||||||
{
|
{
|
||||||
int index = _entriesProp.arraySize;
|
int index = _entriesProp.arraySize;
|
||||||
_entriesProp.InsertArrayElementAtIndex(index);
|
_entriesProp.InsertArrayElementAtIndex(index);
|
||||||
@ -322,31 +447,73 @@ namespace UnityEngine.UI
|
|||||||
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
|
SerializedProperty entryProp = _entriesProp.GetArrayElementAtIndex(index);
|
||||||
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
|
SerializedProperty controllerIdProp = entryProp.FindPropertyRelative("_controllerId");
|
||||||
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
|
SerializedProperty controllerIndexProp = entryProp.FindPropertyRelative("_controllerIndex");
|
||||||
|
SerializedProperty controllerIndexMaskProp = entryProp.FindPropertyRelative("_controllerIndexMask");
|
||||||
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
|
SerializedProperty propertyProp = entryProp.FindPropertyRelative("_property");
|
||||||
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
|
SerializedProperty fallbackModeProp = entryProp.FindPropertyRelative("_fallbackMode");
|
||||||
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
|
SerializedProperty valueProp = entryProp.FindPropertyRelative("_value");
|
||||||
|
SerializedProperty indexedValuesProp = entryProp.FindPropertyRelative("_indexedValues");
|
||||||
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
|
SerializedProperty fallbackValueProp = entryProp.FindPropertyRelative("_fallbackValue");
|
||||||
SerializedProperty capturedDefaultProp = entryProp.FindPropertyRelative("_capturedDefault");
|
SerializedProperty capturedDefaultProp = entryProp.FindPropertyRelative("_capturedDefault");
|
||||||
SerializedProperty hasCapturedDefaultProp = entryProp.FindPropertyRelative("_hasCapturedDefault");
|
SerializedProperty hasCapturedDefaultProp = entryProp.FindPropertyRelative("_hasCapturedDefault");
|
||||||
SerializedProperty capturedPropertyProp = entryProp.FindPropertyRelative("_capturedProperty");
|
SerializedProperty capturedPropertyProp = entryProp.FindPropertyRelative("_capturedProperty");
|
||||||
|
|
||||||
controllerIdProp.stringValue = string.Empty;
|
controllerIdProp.stringValue = controllerId;
|
||||||
controllerIndexProp.intValue = 0;
|
controllerIndexProp.intValue = 0;
|
||||||
propertyProp.enumValueIndex = (int)UXBindingProperty.GameObjectActive;
|
controllerIndexMaskProp.intValue = 1;
|
||||||
|
propertyProp.enumValueIndex = (int)property;
|
||||||
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault;
|
fallbackModeProp.enumValueIndex = (int)UXBindingFallbackMode.RestoreCapturedDefault;
|
||||||
ResetValue(valueProp);
|
ResetValue(valueProp);
|
||||||
|
indexedValuesProp.ClearArray();
|
||||||
ResetValue(fallbackValueProp);
|
ResetValue(fallbackValueProp);
|
||||||
ResetValue(capturedDefaultProp);
|
ResetValue(capturedDefaultProp);
|
||||||
hasCapturedDefaultProp.boolValue = false;
|
hasCapturedDefaultProp.boolValue = false;
|
||||||
capturedPropertyProp.enumValueIndex = (int)UXBindingProperty.GameObjectActive;
|
capturedPropertyProp.enumValueIndex = (int)property;
|
||||||
ApplyDefaultFallbackForProperty(entryProp, UXBindingProperty.GameObjectActive);
|
ApplyDefaultFallbackForProperty(entryProp, property);
|
||||||
|
|
||||||
if (binding.Controller != null && binding.Controller.Controllers.Count > 0)
|
|
||||||
{
|
|
||||||
controllerIdProp.stringValue = binding.Controller.Controllers[0].Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
_foldouts[index] = true;
|
_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)
|
private static void ResetValue(SerializedProperty valueProp)
|
||||||
@ -367,6 +534,15 @@ namespace UnityEngine.UI
|
|||||||
return;
|
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");
|
Undo.RecordObject(binding, "Delete UX Binding Rule");
|
||||||
_entriesProp.DeleteArrayElementAtIndex(index);
|
_entriesProp.DeleteArrayElementAtIndex(index);
|
||||||
CleanupFoldouts(index);
|
CleanupFoldouts(index);
|
||||||
@ -375,6 +551,22 @@ namespace UnityEngine.UI
|
|||||||
GUIUtility.ExitGUI();
|
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)
|
private void CleanupFoldouts(int removedIndex)
|
||||||
{
|
{
|
||||||
_foldouts.Remove(removedIndex);
|
_foldouts.Remove(removedIndex);
|
||||||
@ -411,6 +603,230 @@ namespace UnityEngine.UI
|
|||||||
ResetValue(fallbackValueProp);
|
ResetValue(fallbackValueProp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AutoBindController(UXBinding binding)
|
||||||
|
{
|
||||||
|
UXController controller = binding.GetComponentInParent<UXController>();
|
||||||
|
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
|
#endif
|
||||||
|
|||||||
@ -77,6 +77,7 @@ namespace UnityEngine.UI
|
|||||||
SerializedProperty idProp = entryProp.FindPropertyRelative("_id");
|
SerializedProperty idProp = entryProp.FindPropertyRelative("_id");
|
||||||
SerializedProperty nameProp = entryProp.FindPropertyRelative("_name");
|
SerializedProperty nameProp = entryProp.FindPropertyRelative("_name");
|
||||||
SerializedProperty lengthProp = entryProp.FindPropertyRelative("_length");
|
SerializedProperty lengthProp = entryProp.FindPropertyRelative("_length");
|
||||||
|
SerializedProperty defaultIndexProp = entryProp.FindPropertyRelative("_defaultIndex");
|
||||||
SerializedProperty descriptionProp = entryProp.FindPropertyRelative("_description");
|
SerializedProperty descriptionProp = entryProp.FindPropertyRelative("_description");
|
||||||
|
|
||||||
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
|
bool expanded = _foldouts.ContainsKey(index) && _foldouts[index];
|
||||||
@ -110,6 +111,7 @@ namespace UnityEngine.UI
|
|||||||
{
|
{
|
||||||
EditorGUILayout.PropertyField(nameProp, new GUIContent("Name"));
|
EditorGUILayout.PropertyField(nameProp, new GUIContent("Name"));
|
||||||
lengthProp.intValue = Mathf.Max(1, EditorGUILayout.IntField("Length", Mathf.Max(1, lengthProp.intValue)));
|
lengthProp.intValue = Mathf.Max(1, EditorGUILayout.IntField("Length", Mathf.Max(1, lengthProp.intValue)));
|
||||||
|
defaultIndexProp.intValue = Mathf.Clamp(EditorGUILayout.IntField("Default Index", defaultIndexProp.intValue), 0, lengthProp.intValue - 1);
|
||||||
EditorGUILayout.PropertyField(descriptionProp, new GUIContent("Description"));
|
EditorGUILayout.PropertyField(descriptionProp, new GUIContent("Description"));
|
||||||
|
|
||||||
EditorGUI.BeginDisabledGroup(true);
|
EditorGUI.BeginDisabledGroup(true);
|
||||||
@ -181,6 +183,7 @@ namespace UnityEngine.UI
|
|||||||
entryProp.FindPropertyRelative("_id").stringValue = string.Empty;
|
entryProp.FindPropertyRelative("_id").stringValue = string.Empty;
|
||||||
entryProp.FindPropertyRelative("_name").stringValue = $"Controller {index + 1}";
|
entryProp.FindPropertyRelative("_name").stringValue = $"Controller {index + 1}";
|
||||||
entryProp.FindPropertyRelative("_length").intValue = 2;
|
entryProp.FindPropertyRelative("_length").intValue = 2;
|
||||||
|
entryProp.FindPropertyRelative("_defaultIndex").intValue = 0;
|
||||||
entryProp.FindPropertyRelative("_description").stringValue = string.Empty;
|
entryProp.FindPropertyRelative("_description").stringValue = string.Empty;
|
||||||
|
|
||||||
_foldouts[index] = true;
|
_foldouts[index] = true;
|
||||||
@ -195,6 +198,15 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index);
|
SerializedProperty entryProp = _controllersProp.GetArrayElementAtIndex(index);
|
||||||
string deletedControllerId = entryProp.FindPropertyRelative("_id").stringValue;
|
string deletedControllerId = entryProp.FindPropertyRelative("_id").stringValue;
|
||||||
|
int referenceCount = CountBindingReferences(controller, deletedControllerId);
|
||||||
|
if (referenceCount > 0 && !EditorUtility.DisplayDialog(
|
||||||
|
"Delete UX Controller",
|
||||||
|
$"This controller is referenced by {referenceCount} binding rule(s). Delete and clear those references?",
|
||||||
|
"Delete",
|
||||||
|
"Cancel"))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Undo.RecordObject(controller, "Delete UX Controller");
|
Undo.RecordObject(controller, "Delete UX Controller");
|
||||||
_controllersProp.DeleteArrayElementAtIndex(index);
|
_controllersProp.DeleteArrayElementAtIndex(index);
|
||||||
@ -261,6 +273,37 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int CountBindingReferences(UXController controller, string controllerId)
|
||||||
|
{
|
||||||
|
if (controller == null || string.IsNullOrEmpty(controllerId))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
IReadOnlyList<UXBinding> bindings = controller.Bindings;
|
||||||
|
for (int i = 0; i < bindings.Count; i++)
|
||||||
|
{
|
||||||
|
UXBinding binding = bindings[i];
|
||||||
|
if (binding == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IReadOnlyList<UXBinding.BindingEntry> entries = binding.Entries;
|
||||||
|
for (int entryIndex = 0; entryIndex < entries.Count; entryIndex++)
|
||||||
|
{
|
||||||
|
UXBinding.BindingEntry entry = entries[entryIndex];
|
||||||
|
if (entry != null && entry.ControllerId == controllerId)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -8,29 +8,39 @@ namespace UnityEngine.UI
|
|||||||
public sealed class UXControllerSceneOverlayWindow : EditorWindow
|
public sealed class UXControllerSceneOverlayWindow : EditorWindow
|
||||||
{
|
{
|
||||||
private const string OverlayEnabledKey = "AlicizaX.UI.UXControllerSceneOverlay.Enabled";
|
private const string OverlayEnabledKey = "AlicizaX.UI.UXControllerSceneOverlay.Enabled";
|
||||||
private const string OverlayAutoFocusKey = "AlicizaX.UI.UXControllerSceneOverlay.AutoFocus";
|
private const string MenuPath = "Window/UX/Controller Scene Overlay";
|
||||||
private const float OverlayWidth = 360f;
|
private const float OverlayWidth = 340f;
|
||||||
private const float OverlayMargin = 12f;
|
private const float OverlayMargin = 10f;
|
||||||
|
|
||||||
private static bool s_overlayEnabled;
|
private static bool s_overlayEnabled;
|
||||||
private static bool s_autoFocusSceneView;
|
|
||||||
private static Vector2 s_scrollPosition;
|
private static Vector2 s_scrollPosition;
|
||||||
private static bool s_registered;
|
private static bool s_registered;
|
||||||
|
private static readonly List<UXController> Controllers = new List<UXController>();
|
||||||
|
private static GUIStyle s_indexOnStyle;
|
||||||
|
private static GUIStyle s_indexOffStyle;
|
||||||
|
|
||||||
[MenuItem("Window/UX/Controller Scene Overlay")]
|
[MenuItem(MenuPath)]
|
||||||
public static void ShowWindow()
|
public static void ToggleOverlay()
|
||||||
{
|
{
|
||||||
var window = GetWindow<UXControllerSceneOverlayWindow>("UX Controller Overlay");
|
s_overlayEnabled = !s_overlayEnabled;
|
||||||
window.minSize = new Vector2(320f, 140f);
|
EditorPrefs.SetBool(OverlayEnabledKey, s_overlayEnabled);
|
||||||
window.Show();
|
Menu.SetChecked(MenuPath, s_overlayEnabled);
|
||||||
EnsureRegistered();
|
EnsureRegistered();
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
[MenuItem(MenuPath, true)]
|
||||||
|
private static bool ToggleOverlayValidate()
|
||||||
|
{
|
||||||
|
Menu.SetChecked(MenuPath, s_overlayEnabled);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[InitializeOnLoadMethod]
|
[InitializeOnLoadMethod]
|
||||||
private static void Initialize()
|
private static void Initialize()
|
||||||
{
|
{
|
||||||
s_overlayEnabled = EditorPrefs.GetBool(OverlayEnabledKey, false);
|
s_overlayEnabled = EditorPrefs.GetBool(OverlayEnabledKey, false);
|
||||||
s_autoFocusSceneView = EditorPrefs.GetBool(OverlayAutoFocusKey, false);
|
Menu.SetChecked(MenuPath, s_overlayEnabled);
|
||||||
EnsureRegistered();
|
EnsureRegistered();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,37 +55,6 @@ namespace UnityEngine.UI
|
|||||||
s_registered = true;
|
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)
|
private static void OnSceneGui(SceneView sceneView)
|
||||||
{
|
{
|
||||||
if (!s_overlayEnabled)
|
if (!s_overlayEnabled)
|
||||||
@ -83,51 +62,44 @@ namespace UnityEngine.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_autoFocusSceneView && sceneView != null)
|
GetSceneControllers(Controllers);
|
||||||
{
|
|
||||||
s_autoFocusSceneView = false;
|
|
||||||
EditorPrefs.SetBool(OverlayAutoFocusKey, false);
|
|
||||||
sceneView.Focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<UXController> controllers = GetSceneControllers();
|
|
||||||
|
|
||||||
Handles.BeginGUI();
|
Handles.BeginGUI();
|
||||||
float height = Mathf.Max(160f, sceneView.position.height - OverlayMargin * 2f);
|
float height = Mathf.Min(Mathf.Max(180f, sceneView.position.height - OverlayMargin * 2f), 520f);
|
||||||
Rect area = new Rect(
|
Rect area = new Rect(
|
||||||
sceneView.position.width - OverlayWidth - OverlayMargin,
|
sceneView.position.width - OverlayWidth - OverlayMargin,
|
||||||
OverlayMargin,
|
OverlayMargin,
|
||||||
OverlayWidth,
|
OverlayWidth,
|
||||||
height);
|
height);
|
||||||
|
|
||||||
GUILayout.BeginArea(area, EditorStyles.helpBox);
|
GUILayout.BeginArea(area, GUI.skin.window);
|
||||||
DrawOverlayContent(controllers);
|
DrawOverlayContent(Controllers);
|
||||||
GUILayout.EndArea();
|
GUILayout.EndArea();
|
||||||
Handles.EndGUI();
|
Handles.EndGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawOverlayContent(List<UXController> controllers)
|
private static void DrawOverlayContent(List<UXController> controllers)
|
||||||
{
|
{
|
||||||
|
EnsureStyles();
|
||||||
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
EditorGUILayout.LabelField("UX Controllers", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("UX Controller Preview", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.LabelField(controllers.Count.ToString(), EditorStyles.miniBoldLabel, GUILayout.Width(24f));
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
if (GUILayout.Button("Hide", GUILayout.Width(64f)))
|
if (GUILayout.Button("X", EditorStyles.miniButton, GUILayout.Width(22f)))
|
||||||
{
|
{
|
||||||
s_overlayEnabled = false;
|
s_overlayEnabled = false;
|
||||||
EditorPrefs.SetBool(OverlayEnabledKey, s_overlayEnabled);
|
EditorPrefs.SetBool(OverlayEnabledKey, s_overlayEnabled);
|
||||||
SceneView.RepaintAll();
|
Menu.SetChecked(MenuPath, false);
|
||||||
}
|
|
||||||
if (GUILayout.Button("Refresh", GUILayout.Width(64f)))
|
|
||||||
{
|
|
||||||
SceneView.RepaintAll();
|
SceneView.RepaintAll();
|
||||||
}
|
}
|
||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
EditorGUILayout.Space(4f);
|
EditorGUILayout.Space(2f);
|
||||||
|
|
||||||
if (controllers.Count == 0)
|
if (controllers.Count == 0)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("No UXController found in the current loaded scenes.", MessageType.Info);
|
EditorGUILayout.HelpBox("No UXController in loaded scenes.", MessageType.Info);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,16 +124,17 @@ namespace UnityEngine.UI
|
|||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
||||||
|
|
||||||
EditorGUILayout.BeginHorizontal();
|
EditorGUILayout.BeginHorizontal();
|
||||||
EditorGUILayout.LabelField($"{index + 1}. {controller.gameObject.name}", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField(controller.gameObject.name, EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.LabelField($"C:{controller.ControllerCount} B:{controller.Bindings.Count}", EditorStyles.miniLabel, GUILayout.Width(72f));
|
||||||
GUILayout.FlexibleSpace();
|
GUILayout.FlexibleSpace();
|
||||||
|
|
||||||
if (GUILayout.Button("Select", GUILayout.Width(52f)))
|
if (GUILayout.Button("Ping", EditorStyles.miniButtonLeft, GUILayout.Width(42f)))
|
||||||
{
|
{
|
||||||
Selection.activeGameObject = controller.gameObject;
|
Selection.activeGameObject = controller.gameObject;
|
||||||
EditorGUIUtility.PingObject(controller.gameObject);
|
EditorGUIUtility.PingObject(controller.gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUILayout.Button("Reset", GUILayout.Width(52f)))
|
if (GUILayout.Button("Reset", EditorStyles.miniButtonRight, GUILayout.Width(44f)))
|
||||||
{
|
{
|
||||||
controller.ResetAllControllers();
|
controller.ResetAllControllers();
|
||||||
EditorUtility.SetDirty(controller);
|
EditorUtility.SetDirty(controller);
|
||||||
@ -170,7 +143,6 @@ namespace UnityEngine.UI
|
|||||||
EditorGUILayout.EndHorizontal();
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
EditorGUILayout.LabelField(GetHierarchyPath(controller.transform), EditorStyles.miniLabel);
|
EditorGUILayout.LabelField(GetHierarchyPath(controller.transform), EditorStyles.miniLabel);
|
||||||
EditorGUILayout.LabelField($"Bindings: {controller.Bindings.Count}", EditorStyles.miniLabel);
|
|
||||||
|
|
||||||
for (int controllerIndex = 0; controllerIndex < controller.ControllerCount; controllerIndex++)
|
for (int controllerIndex = 0; controllerIndex < controller.ControllerCount; controllerIndex++)
|
||||||
{
|
{
|
||||||
@ -188,38 +160,33 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
private static void DrawDefinitionPreview(UXController controller, UXController.ControllerDefinition definition)
|
private static void DrawDefinitionPreview(UXController controller, UXController.ControllerDefinition definition)
|
||||||
{
|
{
|
||||||
EditorGUILayout.Space(3f);
|
int currentIndex = Mathf.Max(0, definition.SelectedIndex);
|
||||||
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
|
int length = Mathf.Max(1, definition.Length);
|
||||||
EditorGUILayout.LabelField(definition.Name, EditorStyles.boldLabel);
|
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
EditorGUILayout.LabelField(definition.Name, EditorStyles.miniBoldLabel, GUILayout.Width(96f));
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
bool selected = i == currentIndex;
|
||||||
|
GUIStyle style = selected ? s_indexOnStyle : s_indexOffStyle;
|
||||||
|
if (GUILayout.Toggle(selected, i.ToString(), style, GUILayout.Width(26f)) != selected)
|
||||||
|
{
|
||||||
|
controller.SetControllerIndex(definition.Id, i);
|
||||||
|
EditorUtility.SetDirty(controller);
|
||||||
|
SceneView.RepaintAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(definition.Description))
|
if (!string.IsNullOrWhiteSpace(definition.Description))
|
||||||
{
|
{
|
||||||
EditorGUILayout.LabelField(definition.Description, EditorStyles.miniLabel);
|
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()
|
private static void GetSceneControllers(List<UXController> results)
|
||||||
{
|
{
|
||||||
var results = new List<UXController>();
|
results.Clear();
|
||||||
UXController[] allControllers = Resources.FindObjectsOfTypeAll<UXController>();
|
UXController[] allControllers = Resources.FindObjectsOfTypeAll<UXController>();
|
||||||
|
|
||||||
for (int i = 0; i < allControllers.Length; i++)
|
for (int i = 0; i < allControllers.Length; i++)
|
||||||
@ -248,8 +215,35 @@ namespace UnityEngine.UI
|
|||||||
results.Add(controller);
|
results.Add(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
results.Sort((left, right) => string.CompareOrdinal(GetHierarchyPath(left.transform), GetHierarchyPath(right.transform)));
|
results.Sort(CompareControllers);
|
||||||
return results;
|
}
|
||||||
|
|
||||||
|
private static int CompareControllers(UXController left, UXController right)
|
||||||
|
{
|
||||||
|
return string.CompareOrdinal(GetHierarchyPath(left.transform), GetHierarchyPath(right.transform));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureStyles()
|
||||||
|
{
|
||||||
|
if (s_indexOnStyle != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s_indexOnStyle = new GUIStyle(EditorStyles.miniButton)
|
||||||
|
{
|
||||||
|
fontStyle = FontStyle.Bold,
|
||||||
|
fixedHeight = 18f,
|
||||||
|
margin = new RectOffset(1, 1, 1, 1),
|
||||||
|
padding = new RectOffset(2, 2, 1, 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
s_indexOffStyle = new GUIStyle(EditorStyles.miniButton)
|
||||||
|
{
|
||||||
|
fixedHeight = 18f,
|
||||||
|
margin = new RectOffset(1, 1, 1, 1),
|
||||||
|
padding = new RectOffset(2, 2, 1, 1)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetHierarchyPath(Transform target)
|
private static string GetHierarchyPath(Transform target)
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
using System.Collections.Generic;
|
using UnityEditor;
|
||||||
using UnityEditorInternal;
|
using UnityEditorInternal;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
using UnityEditor;
|
|
||||||
|
|
||||||
namespace UnityEditor.UI
|
namespace UnityEditor.UI
|
||||||
{
|
{
|
||||||
@ -12,23 +11,23 @@ namespace UnityEditor.UI
|
|||||||
private SerializedProperty m_Toggles;
|
private SerializedProperty m_Toggles;
|
||||||
private SerializedProperty m_AllowSwitchOff;
|
private SerializedProperty m_AllowSwitchOff;
|
||||||
private SerializedProperty m_DefaultToggle;
|
private SerializedProperty m_DefaultToggle;
|
||||||
private UXGroup _target;
|
private UXGroup m_Target;
|
||||||
private ReorderableList _reorderableList;
|
private ReorderableList m_ReorderableList;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
_target = (UXGroup)target;
|
m_Target = (UXGroup)target;
|
||||||
m_Toggles = serializedObject.FindProperty("m_Toggles");
|
m_Toggles = serializedObject.FindProperty("m_Toggles");
|
||||||
m_AllowSwitchOff = serializedObject.FindProperty("m_AllowSwitchOff");
|
m_AllowSwitchOff = serializedObject.FindProperty("m_AllowSwitchOff");
|
||||||
m_DefaultToggle = serializedObject.FindProperty("m_DefaultToggle");
|
m_DefaultToggle = serializedObject.FindProperty("m_DefaultToggle");
|
||||||
|
|
||||||
_reorderableList = new ReorderableList(serializedObject, m_Toggles, true, true, true, true)
|
m_ReorderableList = new ReorderableList(serializedObject, m_Toggles, true, true, true, true)
|
||||||
{
|
{
|
||||||
drawHeaderCallback = DrawHeader,
|
drawHeaderCallback = DrawHeader,
|
||||||
drawElementCallback = DrawElement,
|
drawElementCallback = DrawElement,
|
||||||
|
onAddCallback = OnAddList,
|
||||||
onRemoveCallback = OnRemoveList,
|
onRemoveCallback = OnRemoveList,
|
||||||
onChangedCallback = OnChanged,
|
onChangedCallback = OnChanged
|
||||||
displayAdd = false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,160 +36,166 @@ namespace UnityEditor.UI
|
|||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(m_AllowSwitchOff);
|
EditorGUILayout.PropertyField(m_AllowSwitchOff);
|
||||||
|
|
||||||
// Default selector: only show toggles that are currently in the group's m_Toggles list
|
|
||||||
DrawDefaultToggleSelector();
|
DrawDefaultToggleSelector();
|
||||||
|
DrawTools();
|
||||||
|
|
||||||
bool isPlaying = Application.isPlaying || EditorApplication.isPlaying;
|
bool isPlaying = Application.isPlaying || EditorApplication.isPlaying;
|
||||||
|
m_ReorderableList.draggable = !isPlaying;
|
||||||
|
m_ReorderableList.displayAdd = !isPlaying;
|
||||||
|
m_ReorderableList.displayRemove = !isPlaying;
|
||||||
|
|
||||||
_reorderableList.draggable = !isPlaying;
|
bool previousEnabled = GUI.enabled;
|
||||||
_reorderableList.displayAdd = !isPlaying;
|
GUI.enabled = !isPlaying;
|
||||||
_reorderableList.displayRemove = !isPlaying;
|
m_ReorderableList.DoLayoutList();
|
||||||
|
GUI.enabled = previousEnabled;
|
||||||
bool prevEnabled = GUI.enabled;
|
|
||||||
if (isPlaying) GUI.enabled = false;
|
|
||||||
|
|
||||||
_reorderableList.DoLayoutList();
|
|
||||||
|
|
||||||
GUI.enabled = prevEnabled;
|
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
// 在编辑器下尽量实时同步状态
|
|
||||||
if (!Application.isPlaying)
|
if (!Application.isPlaying)
|
||||||
{
|
m_Target.EnsureValidState();
|
||||||
// 保证序列化数据写入后同步 group 状态
|
|
||||||
_target.EnsureValidState();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawDefaultToggleSelector()
|
private void DrawDefaultToggleSelector()
|
||||||
{
|
{
|
||||||
// Build a list of current toggles (non-null)
|
int toggleCount = CountValidToggles();
|
||||||
List<UXToggle> toggles = new List<UXToggle>();
|
bool requireDefault = !m_AllowSwitchOff.boolValue && toggleCount > 0;
|
||||||
|
int optionOffset = requireDefault ? 0 : 1;
|
||||||
|
string[] options = new string[toggleCount + optionOffset];
|
||||||
|
UXToggle[] toggles = new UXToggle[toggleCount];
|
||||||
|
if (!requireDefault)
|
||||||
|
options[0] = "<None>";
|
||||||
|
|
||||||
|
int write = 0;
|
||||||
for (int i = 0; i < m_Toggles.arraySize; i++)
|
for (int i = 0; i < m_Toggles.arraySize; i++)
|
||||||
{
|
{
|
||||||
var elem = m_Toggles.GetArrayElementAtIndex(i);
|
UXToggle toggle = m_Toggles.GetArrayElementAtIndex(i).objectReferenceValue as UXToggle;
|
||||||
var t = elem.objectReferenceValue as UXToggle;
|
if (toggle == null)
|
||||||
if (t != null)
|
continue;
|
||||||
toggles.Add(t);
|
|
||||||
|
toggles[write] = toggle;
|
||||||
|
options[write + optionOffset] = "[" + i + "] " + toggle.name;
|
||||||
|
write++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare options array with a "None" entry
|
|
||||||
string[] options = new string[toggles.Count + 1];
|
|
||||||
options[0] = "<None>";
|
|
||||||
for (int i = 0; i < toggles.Count; i++)
|
|
||||||
{
|
|
||||||
options[i + 1] = string.Format("[{0}] {1}", i, toggles[i] != null ? toggles[i].name : "Null");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine current index
|
|
||||||
UXToggle currentDefault = m_DefaultToggle.objectReferenceValue as UXToggle;
|
UXToggle currentDefault = m_DefaultToggle.objectReferenceValue as UXToggle;
|
||||||
int currentIndex = 0;
|
int currentIndex = requireDefault ? -1 : 0;
|
||||||
if (currentDefault != null)
|
for (int i = 0; i < toggles.Length; i++)
|
||||||
{
|
{
|
||||||
int found = toggles.IndexOf(currentDefault);
|
if (toggles[i] == currentDefault)
|
||||||
if (found >= 0)
|
|
||||||
currentIndex = found + 1; // +1 because 0 is <None>
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// Current default is not in the list -> clear it
|
currentIndex = i + optionOffset;
|
||||||
m_DefaultToggle.objectReferenceValue = null;
|
break;
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
currentIndex = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (requireDefault && currentIndex < 0)
|
||||||
|
{
|
||||||
|
currentDefault = GetSelectedToggle(toggles);
|
||||||
|
if (currentDefault == null)
|
||||||
|
currentDefault = toggles[0];
|
||||||
|
|
||||||
|
m_DefaultToggle.objectReferenceValue = currentDefault;
|
||||||
|
for (int i = 0; i < toggles.Length; i++)
|
||||||
|
{
|
||||||
|
if (toggles[i] == currentDefault)
|
||||||
|
{
|
||||||
|
currentIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!requireDefault && currentDefault != null && currentIndex == 0)
|
||||||
|
{
|
||||||
|
m_DefaultToggle.objectReferenceValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
EditorGUI.BeginChangeCheck();
|
EditorGUI.BeginChangeCheck();
|
||||||
int newIndex = EditorGUILayout.Popup("Default Toggle", currentIndex, options);
|
int newIndex = EditorGUILayout.Popup("Default Toggle", currentIndex, options);
|
||||||
if (EditorGUI.EndChangeCheck())
|
if (EditorGUI.EndChangeCheck())
|
||||||
{
|
{
|
||||||
UXToggle newDefault = null;
|
UXToggle newDefault = newIndex >= optionOffset ? toggles[newIndex - optionOffset] : null;
|
||||||
if (newIndex > 0)
|
|
||||||
newDefault = toggles[newIndex - 1];
|
|
||||||
|
|
||||||
m_DefaultToggle.objectReferenceValue = newDefault;
|
m_DefaultToggle.objectReferenceValue = newDefault;
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
// 如果选择了一个非空默认项,则在 allowSwitchOff == false 时将其设为选中状态(并通知组)
|
|
||||||
if (newDefault != null)
|
if (newDefault != null)
|
||||||
{
|
{
|
||||||
// 确保该 toggle 在 group 中(理论上应该如此)
|
AssignGroup(newDefault, m_Target);
|
||||||
if (!_target.ContainsToggle(newDefault))
|
|
||||||
{
|
|
||||||
_target.RegisterToggle(newDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_target.allowSwitchOff)
|
|
||||||
{
|
|
||||||
// 通过 SerializedObject 修改 UXToggle 的 m_IsOn,避免触发不必要的回调
|
|
||||||
SerializedObject so = new SerializedObject(newDefault);
|
|
||||||
var isOnProp = so.FindProperty("m_IsOn");
|
|
||||||
isOnProp.boolValue = true;
|
|
||||||
so.ApplyModifiedProperties();
|
|
||||||
|
|
||||||
// 通知组同步其余项
|
|
||||||
_target.NotifyToggleOn(newDefault);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 选择 None:不自动切换状态。但如果组不允许 all-off,则会在 EnsureValidState 中被处理
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static UXToggle GetSelectedToggle(UXToggle[] toggles)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < toggles.Length; i++)
|
||||||
|
{
|
||||||
|
UXToggle toggle = toggles[i];
|
||||||
|
if (toggle != null && toggle.isOn)
|
||||||
|
return toggle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawTools()
|
||||||
|
{
|
||||||
|
EditorGUILayout.BeginHorizontal();
|
||||||
|
if (GUILayout.Button("Collect Children"))
|
||||||
|
{
|
||||||
|
CollectChildren();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Clean Nulls"))
|
||||||
|
{
|
||||||
|
CleanNulls();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("Sort By Hierarchy"))
|
||||||
|
{
|
||||||
|
SortByHierarchy();
|
||||||
|
}
|
||||||
|
|
||||||
|
EditorGUILayout.EndHorizontal();
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawHeader(Rect rect)
|
private void DrawHeader(Rect rect)
|
||||||
{
|
{
|
||||||
EditorGUI.LabelField(rect, "Toggles", EditorStyles.boldLabel);
|
EditorGUI.LabelField(rect, "Toggles", EditorStyles.boldLabel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录旧的引用用于侦测变化
|
|
||||||
private UXToggle previousRef;
|
|
||||||
|
|
||||||
private void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
|
private void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
|
||||||
{
|
{
|
||||||
SerializedProperty element = m_Toggles.GetArrayElementAtIndex(index);
|
SerializedProperty element = m_Toggles.GetArrayElementAtIndex(index);
|
||||||
|
UXToggle oldToggle = element.objectReferenceValue as UXToggle;
|
||||||
|
|
||||||
rect.y += 2;
|
rect.y += 2;
|
||||||
Rect fieldRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
|
Rect fieldRect = new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight);
|
||||||
|
string label = oldToggle != null ? "[" + index + "] " + oldToggle.name : "[" + index + "] Null";
|
||||||
|
|
||||||
UXToggle oldButton = element.objectReferenceValue as UXToggle;
|
bool duplicate = oldToggle != null && HasDuplicate(oldToggle, index);
|
||||||
|
bool wrongGroup = oldToggle != null && oldToggle.group != null && oldToggle.group != m_Target;
|
||||||
string label = $"[{index}] {(oldButton != null ? oldButton.name : "Null")}";
|
if (duplicate || wrongGroup)
|
||||||
|
EditorGUI.DrawRect(fieldRect, new Color(1f, 0.55f, 0f, 0.2f));
|
||||||
|
|
||||||
EditorGUI.BeginChangeCheck();
|
EditorGUI.BeginChangeCheck();
|
||||||
|
UXToggle newToggle = EditorGUI.ObjectField(fieldRect, label, oldToggle, typeof(UXToggle), true) as UXToggle;
|
||||||
var newRef = EditorGUI.ObjectField(fieldRect, label, oldButton, typeof(UXToggle), true) as UXToggle;
|
|
||||||
|
|
||||||
if (EditorGUI.EndChangeCheck())
|
if (EditorGUI.EndChangeCheck())
|
||||||
{
|
{
|
||||||
// 先处理 Remove(旧值存在且不同)
|
if (oldToggle != null && oldToggle != newToggle)
|
||||||
if (oldButton != null && oldButton != newRef)
|
AssignGroup(oldToggle, null);
|
||||||
{
|
|
||||||
OnRemove(oldButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 再处理 Add(新值非空)
|
if (newToggle != null && oldToggle != newToggle)
|
||||||
if (newRef != null && oldButton != newRef)
|
AssignGroup(newToggle, m_Target);
|
||||||
{
|
|
||||||
OnAdd(newRef);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最后把引用写回去
|
element.objectReferenceValue = newToggle;
|
||||||
element.objectReferenceValue = newRef;
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
m_Target.EnsureValidState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnAddList(ReorderableList list)
|
private void OnAddList(ReorderableList list)
|
||||||
{
|
{
|
||||||
int newIndex = m_Toggles.arraySize;
|
|
||||||
m_Toggles.arraySize++;
|
m_Toggles.arraySize++;
|
||||||
serializedObject.ApplyModifiedProperties();
|
m_Toggles.GetArrayElementAtIndex(m_Toggles.arraySize - 1).objectReferenceValue = null;
|
||||||
|
|
||||||
var newElem = m_Toggles.GetArrayElementAtIndex(newIndex);
|
|
||||||
newElem.objectReferenceValue = null;
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,72 +204,122 @@ namespace UnityEditor.UI
|
|||||||
if (list.index < 0 || list.index >= m_Toggles.arraySize)
|
if (list.index < 0 || list.index >= m_Toggles.arraySize)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var oldButton = m_Toggles.GetArrayElementAtIndex(list.index).objectReferenceValue as UXToggle;
|
UXToggle oldToggle = m_Toggles.GetArrayElementAtIndex(list.index).objectReferenceValue as UXToggle;
|
||||||
if (oldButton)
|
if (oldToggle != null)
|
||||||
{
|
AssignGroup(oldToggle, null);
|
||||||
OnRemove(oldButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_Toggles.DeleteArrayElementAtIndex(list.index);
|
m_Toggles.DeleteArrayElementAtIndex(list.index);
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
m_Target.EnsureValidState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnChanged(ReorderableList list)
|
private void OnChanged(ReorderableList list)
|
||||||
{
|
{
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
|
||||||
// 编辑器变动后同步 group 状态和默认项
|
|
||||||
if (!Application.isPlaying)
|
if (!Application.isPlaying)
|
||||||
_target.EnsureValidState();
|
m_Target.EnsureValidState();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================
|
private void CollectChildren()
|
||||||
// 自动调用的新增方法
|
|
||||||
// ========================
|
|
||||||
|
|
||||||
private void OnAdd(UXToggle toggle)
|
|
||||||
{
|
{
|
||||||
if (toggle == null)
|
UXToggle[] toggles = m_Target.GetComponentsInChildren<UXToggle>(true);
|
||||||
return;
|
m_Toggles.arraySize = toggles.Length;
|
||||||
|
for (int i = 0; i < toggles.Length; i++)
|
||||||
SerializedObject so = new SerializedObject(toggle);
|
|
||||||
var groupProp = so.FindProperty("m_Group");
|
|
||||||
groupProp.objectReferenceValue = target;
|
|
||||||
so.ApplyModifiedProperties();
|
|
||||||
|
|
||||||
UXGroup group = (UXGroup)target;
|
|
||||||
group.RegisterToggle(toggle);
|
|
||||||
|
|
||||||
// 添加后尽量同步组状态(处理默认项或冲突)
|
|
||||||
if (!Application.isPlaying)
|
|
||||||
group.EnsureValidState();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnRemove(UXToggle toggle)
|
|
||||||
{
|
|
||||||
if (toggle == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
SerializedObject so = new SerializedObject(toggle);
|
|
||||||
var groupProp = so.FindProperty("m_Group");
|
|
||||||
|
|
||||||
UXGroup group = groupProp.objectReferenceValue as UXGroup;
|
|
||||||
if (group != null)
|
|
||||||
group.UnregisterToggle(toggle);
|
|
||||||
|
|
||||||
groupProp.objectReferenceValue = null;
|
|
||||||
|
|
||||||
so.ApplyModifiedProperties();
|
|
||||||
|
|
||||||
// 如果移除的正好是默认项,则清空默认选项
|
|
||||||
if (m_DefaultToggle != null && m_DefaultToggle.objectReferenceValue == toggle)
|
|
||||||
{
|
{
|
||||||
m_DefaultToggle.objectReferenceValue = null;
|
m_Toggles.GetArrayElementAtIndex(i).objectReferenceValue = toggles[i];
|
||||||
serializedObject.ApplyModifiedProperties();
|
AssignGroup(toggles[i], m_Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Application.isPlaying && _target != null)
|
serializedObject.ApplyModifiedProperties();
|
||||||
_target.EnsureValidState();
|
m_Target.EnsureValidState();
|
||||||
|
EditorUtility.SetDirty(m_Target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CleanNulls()
|
||||||
|
{
|
||||||
|
for (int i = m_Toggles.arraySize - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (m_Toggles.GetArrayElementAtIndex(i).objectReferenceValue == null)
|
||||||
|
m_Toggles.DeleteArrayElementAtIndex(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
m_Target.EnsureValidState();
|
||||||
|
EditorUtility.SetDirty(m_Target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SortByHierarchy()
|
||||||
|
{
|
||||||
|
int count = CountValidToggles();
|
||||||
|
UXToggle[] toggles = new UXToggle[count];
|
||||||
|
int write = 0;
|
||||||
|
for (int i = 0; i < m_Toggles.arraySize; i++)
|
||||||
|
{
|
||||||
|
UXToggle toggle = m_Toggles.GetArrayElementAtIndex(i).objectReferenceValue as UXToggle;
|
||||||
|
if (toggle != null)
|
||||||
|
{
|
||||||
|
toggles[write] = toggle;
|
||||||
|
write++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
System.Array.Sort(toggles, CompareHierarchyIndex);
|
||||||
|
m_Toggles.arraySize = toggles.Length;
|
||||||
|
for (int i = 0; i < toggles.Length; i++)
|
||||||
|
m_Toggles.GetArrayElementAtIndex(i).objectReferenceValue = toggles[i];
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
m_Target.EnsureValidState();
|
||||||
|
EditorUtility.SetDirty(m_Target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int CountValidToggles()
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < m_Toggles.arraySize; i++)
|
||||||
|
{
|
||||||
|
if (m_Toggles.GetArrayElementAtIndex(i).objectReferenceValue != null)
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasDuplicate(UXToggle toggle, int selfIndex)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_Toggles.arraySize; i++)
|
||||||
|
{
|
||||||
|
if (i != selfIndex && m_Toggles.GetArrayElementAtIndex(i).objectReferenceValue == toggle)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CompareHierarchyIndex(UXToggle left, UXToggle right)
|
||||||
|
{
|
||||||
|
int leftIndex = left.transform.GetSiblingIndex();
|
||||||
|
int rightIndex = right.transform.GetSiblingIndex();
|
||||||
|
if (leftIndex < rightIndex)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (leftIndex > rightIndex)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AssignGroup(UXToggle toggle, UXGroup group)
|
||||||
|
{
|
||||||
|
if (toggle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SerializedObject serializedToggle = new SerializedObject(toggle);
|
||||||
|
SerializedProperty groupProperty = serializedToggle.FindProperty("m_Group");
|
||||||
|
groupProperty.objectReferenceValue = group;
|
||||||
|
serializedToggle.ApplyModifiedProperties();
|
||||||
|
toggle.group = group;
|
||||||
|
EditorUtility.SetDirty(toggle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 292ca921cd4242d4be9f76bef9bfc08f
|
guid: 292ca921cd4242d4be9f76bef9bfc08f
|
||||||
timeCreated: 1760343702
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
@ -11,10 +11,12 @@ namespace UnityEditor.UI
|
|||||||
private SerializedProperty _hotkeyAction;
|
private SerializedProperty _hotkeyAction;
|
||||||
private SerializedProperty _hotkeyPressType;
|
private SerializedProperty _hotkeyPressType;
|
||||||
private SerializedProperty _component;
|
private SerializedProperty _component;
|
||||||
|
private SerializedProperty _holder;
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
_component = serializedObject.FindProperty("_component");
|
_component = serializedObject.FindProperty("_component");
|
||||||
|
_holder = serializedObject.FindProperty("_holder");
|
||||||
_hotkeyAction = serializedObject.FindProperty("_hotkeyAction");
|
_hotkeyAction = serializedObject.FindProperty("_hotkeyAction");
|
||||||
_hotkeyPressType = serializedObject.FindProperty("_hotkeyPressType");
|
_hotkeyPressType = serializedObject.FindProperty("_hotkeyPressType");
|
||||||
}
|
}
|
||||||
@ -30,7 +32,7 @@ namespace UnityEditor.UI
|
|||||||
MessageType.Info
|
MessageType.Info
|
||||||
);
|
);
|
||||||
|
|
||||||
if (hotkeyComponent.GetComponentInParent<UIHolderObjectBase>(true) == null)
|
if (_holder.objectReferenceValue == null)
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox(
|
EditorGUILayout.HelpBox(
|
||||||
"No UIHolderObjectBase was found in parents. This hotkey will not register at runtime.",
|
"No UIHolderObjectBase was found in parents. This hotkey will not register at runtime.",
|
||||||
@ -46,6 +48,16 @@ namespace UnityEditor.UI
|
|||||||
_component.objectReferenceValue = submitHandler;
|
_component.objectReferenceValue = submitHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (_component.objectReferenceValue is not ISubmitHandler)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Submit target must implement ISubmitHandler. The invalid reference will be cleared.", MessageType.Error);
|
||||||
|
_component.objectReferenceValue = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_hotkeyAction.objectReferenceValue == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("Input Action is required. This hotkey will not register at runtime.", MessageType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
||||||
{
|
{
|
||||||
@ -53,6 +65,7 @@ namespace UnityEditor.UI
|
|||||||
|
|
||||||
EditorGUI.BeginDisabledGroup(true);
|
EditorGUI.BeginDisabledGroup(true);
|
||||||
EditorGUILayout.PropertyField(_component, new GUIContent("Component"));
|
EditorGUILayout.PropertyField(_component, new GUIContent("Component"));
|
||||||
|
EditorGUILayout.PropertyField(_holder, new GUIContent("Holder"));
|
||||||
EditorGUI.EndDisabledGroup();
|
EditorGUI.EndDisabledGroup();
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(_hotkeyAction, new GUIContent("Input Action"));
|
EditorGUILayout.PropertyField(_hotkeyAction, new GUIContent("Input Action"));
|
||||||
|
|||||||
@ -4,7 +4,6 @@ using UnityEditor;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using UnityEditorInternal;
|
using UnityEditorInternal;
|
||||||
using System.Linq;
|
|
||||||
using UnityEditor.AnimatedValues;
|
using UnityEditor.AnimatedValues;
|
||||||
|
|
||||||
namespace UnityEngine.UI
|
namespace UnityEngine.UI
|
||||||
@ -289,15 +288,6 @@ namespace UnityEngine.UI
|
|||||||
if (EditorGUI.EndChangeCheck())
|
if (EditorGUI.EndChangeCheck())
|
||||||
{
|
{
|
||||||
m_ColorType.intValue = (int)type;
|
m_ColorType.intValue = (int)type;
|
||||||
if ((int)type == 1 && m_Type.intValue != 0)
|
|
||||||
{
|
|
||||||
m_Type.intValue = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((int)type == 1 && m_FlipMode.intValue != 0)
|
|
||||||
{
|
|
||||||
m_FlipMode.intValue = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GUILayout.BeginHorizontal();
|
GUILayout.BeginHorizontal();
|
||||||
@ -399,15 +389,7 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
protected void TypeGUI()
|
protected void TypeGUI()
|
||||||
{
|
{
|
||||||
EditorGUI.BeginChangeCheck();
|
|
||||||
EditorGUILayout.PropertyField(m_Type, m_SpriteTypeContent);
|
EditorGUILayout.PropertyField(m_Type, m_SpriteTypeContent);
|
||||||
if (EditorGUI.EndChangeCheck())
|
|
||||||
{
|
|
||||||
if (m_Type.intValue != 0 && m_ColorType.intValue == 1)
|
|
||||||
{
|
|
||||||
m_ColorType.intValue = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
++EditorGUI.indentLevel;
|
++EditorGUI.indentLevel;
|
||||||
{
|
{
|
||||||
@ -415,7 +397,17 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
bool showSlicedOrTiled = (!m_Type.hasMultipleDifferentValues && (typeEnum == Image.Type.Sliced || typeEnum == Image.Type.Tiled));
|
bool showSlicedOrTiled = (!m_Type.hasMultipleDifferentValues && (typeEnum == Image.Type.Sliced || typeEnum == Image.Type.Tiled));
|
||||||
if (showSlicedOrTiled && targets.Length > 1)
|
if (showSlicedOrTiled && targets.Length > 1)
|
||||||
showSlicedOrTiled = targets.Select(obj => obj as Image).All(img => img.hasBorder);
|
{
|
||||||
|
for (int i = 0; i < targets.Length; i++)
|
||||||
|
{
|
||||||
|
Image targetImage = targets[i] as Image;
|
||||||
|
if (targetImage == null || !targetImage.hasBorder)
|
||||||
|
{
|
||||||
|
showSlicedOrTiled = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_ShowSlicedOrTiled.target = showSlicedOrTiled;
|
m_ShowSlicedOrTiled.target = showSlicedOrTiled;
|
||||||
m_ShowSliced.target = (showSlicedOrTiled && !m_Type.hasMultipleDifferentValues && typeEnum == Image.Type.Sliced);
|
m_ShowSliced.target = (showSlicedOrTiled && !m_Type.hasMultipleDifferentValues && typeEnum == Image.Type.Sliced);
|
||||||
@ -517,15 +509,7 @@ namespace UnityEngine.UI
|
|||||||
"None", "Horizontal",
|
"None", "Horizontal",
|
||||||
"Vertical", "FourCorner"
|
"Vertical", "FourCorner"
|
||||||
};
|
};
|
||||||
EditorGUI.BeginChangeCheck();
|
|
||||||
m_FlipMode.intValue = EnumPopupLayoutEx(m_FlipModeContent.text, typeof(UXImage.FlipMode), m_FlipMode.intValue, labels);
|
m_FlipMode.intValue = EnumPopupLayoutEx(m_FlipModeContent.text, typeof(UXImage.FlipMode), m_FlipMode.intValue, labels);
|
||||||
if (EditorGUI.EndChangeCheck())
|
|
||||||
{
|
|
||||||
if (m_FlipMode.intValue != 0 && m_ColorType.intValue == 1)
|
|
||||||
{
|
|
||||||
m_ColorType.intValue = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//EditorGUILayout.PropertyField(m_FlipMode, m_FlipModeContent);
|
//EditorGUILayout.PropertyField(m_FlipMode, m_FlipModeContent);
|
||||||
UXImage.FlipMode flipmodeEnum = (UXImage.FlipMode)m_FlipMode.enumValueIndex;
|
UXImage.FlipMode flipmodeEnum = (UXImage.FlipMode)m_FlipMode.enumValueIndex;
|
||||||
@ -805,8 +789,12 @@ namespace UnityEngine.UI
|
|||||||
GUILayout.Space(EditorGUIUtility.labelWidth);
|
GUILayout.Space(EditorGUIUtility.labelWidth);
|
||||||
if (GUILayout.Button(m_CorrectButtonContent, EditorStyles.miniButton))
|
if (GUILayout.Button(m_CorrectButtonContent, EditorStyles.miniButton))
|
||||||
{
|
{
|
||||||
foreach (Graphic graphic in targets.Select(obj => obj as Graphic))
|
for (int i = 0; i < targets.Length; i++)
|
||||||
{
|
{
|
||||||
|
Graphic graphic = targets[i] as Graphic;
|
||||||
|
if (graphic == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
Undo.RecordObject(graphic.rectTransform, "Set Native Size");
|
Undo.RecordObject(graphic.rectTransform, "Set Native Size");
|
||||||
graphic.SetNativeSize();
|
graphic.SetNativeSize();
|
||||||
EditorUtility.SetDirty(graphic);
|
EditorUtility.SetDirty(graphic);
|
||||||
@ -819,10 +807,40 @@ namespace UnityEngine.UI
|
|||||||
EditorGUILayout.EndFadeGroup();
|
EditorGUILayout.EndFadeGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly Dictionary<Type, int[]> s_EnumValues = new Dictionary<Type, int[]>();
|
||||||
|
private static readonly Dictionary<Type, string[]> s_EnumNames = new Dictionary<Type, string[]>();
|
||||||
|
|
||||||
|
private static int[] GetEnumValues(Type type)
|
||||||
|
{
|
||||||
|
if (!s_EnumValues.TryGetValue(type, out int[] values))
|
||||||
|
{
|
||||||
|
Array rawValues = Enum.GetValues(type);
|
||||||
|
values = new int[rawValues.Length];
|
||||||
|
for (int i = 0; i < rawValues.Length; i++)
|
||||||
|
{
|
||||||
|
values[i] = (int)rawValues.GetValue(i);
|
||||||
|
}
|
||||||
|
s_EnumValues.Add(type, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string[] GetEnumNames(Type type)
|
||||||
|
{
|
||||||
|
if (!s_EnumNames.TryGetValue(type, out string[] names))
|
||||||
|
{
|
||||||
|
names = Enum.GetNames(type);
|
||||||
|
s_EnumNames.Add(type, names);
|
||||||
|
}
|
||||||
|
|
||||||
|
return names;
|
||||||
|
}
|
||||||
public int EnumPopupLayoutEx(string label, Type type, int enumValueIndex, string[] labels)
|
public int EnumPopupLayoutEx(string label, Type type, int enumValueIndex, string[] labels)
|
||||||
{
|
{
|
||||||
int[] ints = (int[])Enum.GetValues(type);
|
int[] ints = GetEnumValues(type);
|
||||||
string[] strings = Enum.GetNames(type);
|
string[] strings = GetEnumNames(type);
|
||||||
if (labels.Length != ints.Length)
|
if (labels.Length != ints.Length)
|
||||||
{
|
{
|
||||||
return EditorGUILayout.IntPopup(label, enumValueIndex, strings, ints);
|
return EditorGUILayout.IntPopup(label, enumValueIndex, strings, ints);
|
||||||
|
|||||||
8
Editor/UX/Navigation.meta
Normal file
8
Editor/UX/Navigation.meta
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 55d4289f6579aec4eaeeeda649282af4
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
202
Editor/UX/Navigation/UXNavigationScopeEditor.cs
Normal file
202
Editor/UX/Navigation/UXNavigationScopeEditor.cs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace AlicizaX.UI.Extension.Editor
|
||||||
|
{
|
||||||
|
[CustomEditor(typeof(UXNavigationScope))]
|
||||||
|
public sealed class UXNavigationScopeEditor : UnityEditor.Editor
|
||||||
|
{
|
||||||
|
private SerializedProperty _defaultSelectable;
|
||||||
|
private SerializedProperty _bakedSelectables;
|
||||||
|
private SerializedProperty _runtimeSelectableCapacity;
|
||||||
|
private SerializedProperty _rememberLastSelection;
|
||||||
|
private SerializedProperty _requireSelectionWhenGamepad;
|
||||||
|
private SerializedProperty _blockLowerScopes;
|
||||||
|
private SerializedProperty _autoSelectFirstAvailable;
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
_defaultSelectable = serializedObject.FindProperty("_defaultSelectable");
|
||||||
|
_bakedSelectables = serializedObject.FindProperty("_bakedSelectables");
|
||||||
|
_runtimeSelectableCapacity = serializedObject.FindProperty("_runtimeSelectableCapacity");
|
||||||
|
_rememberLastSelection = serializedObject.FindProperty("_rememberLastSelection");
|
||||||
|
_requireSelectionWhenGamepad = serializedObject.FindProperty("_requireSelectionWhenGamepad");
|
||||||
|
_blockLowerScopes = serializedObject.FindProperty("_blockLowerScopes");
|
||||||
|
_autoSelectFirstAvailable = serializedObject.FindProperty("_autoSelectFirstAvailable");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnInspectorGUI()
|
||||||
|
{
|
||||||
|
serializedObject.Update();
|
||||||
|
|
||||||
|
EditorGUILayout.PropertyField(_defaultSelectable);
|
||||||
|
EditorGUILayout.PropertyField(_runtimeSelectableCapacity);
|
||||||
|
EditorGUILayout.PropertyField(_rememberLastSelection);
|
||||||
|
EditorGUILayout.PropertyField(_requireSelectionWhenGamepad);
|
||||||
|
EditorGUILayout.PropertyField(_blockLowerScopes);
|
||||||
|
EditorGUILayout.PropertyField(_autoSelectFirstAvailable);
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
DrawBakeTools();
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
EditorGUILayout.PropertyField(_bakedSelectables, true);
|
||||||
|
|
||||||
|
EditorGUILayout.Space();
|
||||||
|
DrawDiagnostics();
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawBakeTools()
|
||||||
|
{
|
||||||
|
using (new EditorGUILayout.HorizontalScope())
|
||||||
|
{
|
||||||
|
if (GUILayout.Button("收集子 Selectable"))
|
||||||
|
{
|
||||||
|
BakeSelectables();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("清理空引用"))
|
||||||
|
{
|
||||||
|
RemoveNullEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GUILayout.Button("按层级排序"))
|
||||||
|
{
|
||||||
|
SortBakedSelectables();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawDiagnostics()
|
||||||
|
{
|
||||||
|
UXNavigationScope scope = (UXNavigationScope)target;
|
||||||
|
EditorGUILayout.LabelField("诊断", EditorStyles.boldLabel);
|
||||||
|
EditorGUILayout.LabelField("烘焙控件数", _bakedSelectables.arraySize.ToString());
|
||||||
|
EditorGUILayout.LabelField("Runtime 动态控件数", Application.isPlaying ? scope.RuntimeSelectableCount.ToString() : "仅 Play Mode");
|
||||||
|
EditorGUILayout.LabelField("被 Skip", scope.GetComponentInParent<UXNavigationSkip>(true) != null ? "是" : "否");
|
||||||
|
EditorGUILayout.LabelField("当前 Suppressed", Application.isPlaying && scope.NavigationSuppressed ? "是" : "否");
|
||||||
|
ValidateReferences(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateReferences(UXNavigationScope scope)
|
||||||
|
{
|
||||||
|
Selectable defaultSelectable = _defaultSelectable.objectReferenceValue as Selectable;
|
||||||
|
if (defaultSelectable != null && defaultSelectable.GetComponentInParent<UXNavigationScope>(true) != scope)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("默认选中控件不属于当前 UXNavigationScope。", MessageType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _bakedSelectables.arraySize; i++)
|
||||||
|
{
|
||||||
|
Selectable selectable = _bakedSelectables.GetArrayElementAtIndex(i).objectReferenceValue as Selectable;
|
||||||
|
if (selectable == null)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("烘焙列表存在空引用。", MessageType.Warning);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectable.GetComponentInParent<UXNavigationScope>(true) != scope)
|
||||||
|
{
|
||||||
|
EditorGUILayout.HelpBox("烘焙列表存在跨 Scope 引用。", MessageType.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BakeSelectables()
|
||||||
|
{
|
||||||
|
UXNavigationScope scope = (UXNavigationScope)target;
|
||||||
|
Selectable[] allSelectables = scope.GetComponentsInChildren<Selectable>(true);
|
||||||
|
List<Selectable> ownedSelectables = new List<Selectable>(allSelectables.Length);
|
||||||
|
for (int i = 0; i < allSelectables.Length; i++)
|
||||||
|
{
|
||||||
|
Selectable selectable = allSelectables[i];
|
||||||
|
if (selectable != null && selectable.GetComponentInParent<UXNavigationScope>(true) == scope)
|
||||||
|
{
|
||||||
|
ownedSelectables.Add(selectable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Undo.RecordObject(scope, "Bake UX Navigation Selectables");
|
||||||
|
_bakedSelectables.arraySize = ownedSelectables.Count;
|
||||||
|
for (int i = 0; i < ownedSelectables.Count; i++)
|
||||||
|
{
|
||||||
|
_bakedSelectables.GetArrayElementAtIndex(i).objectReferenceValue = ownedSelectables[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
EditorUtility.SetDirty(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveNullEntries()
|
||||||
|
{
|
||||||
|
UXNavigationScope scope = (UXNavigationScope)target;
|
||||||
|
Undo.RecordObject(scope, "Clean UX Navigation Selectables");
|
||||||
|
for (int i = _bakedSelectables.arraySize - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (_bakedSelectables.GetArrayElementAtIndex(i).objectReferenceValue == null)
|
||||||
|
{
|
||||||
|
_bakedSelectables.DeleteArrayElementAtIndex(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
EditorUtility.SetDirty(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SortBakedSelectables()
|
||||||
|
{
|
||||||
|
UXNavigationScope scope = (UXNavigationScope)target;
|
||||||
|
List<Selectable> selectables = new List<Selectable>(_bakedSelectables.arraySize);
|
||||||
|
for (int i = 0; i < _bakedSelectables.arraySize; i++)
|
||||||
|
{
|
||||||
|
Selectable selectable = _bakedSelectables.GetArrayElementAtIndex(i).objectReferenceValue as Selectable;
|
||||||
|
if (selectable != null)
|
||||||
|
{
|
||||||
|
selectables.Add(selectable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectables.Sort(CompareSiblingPath);
|
||||||
|
Undo.RecordObject(scope, "Sort UX Navigation Selectables");
|
||||||
|
_bakedSelectables.arraySize = selectables.Count;
|
||||||
|
for (int i = 0; i < selectables.Count; i++)
|
||||||
|
{
|
||||||
|
_bakedSelectables.GetArrayElementAtIndex(i).objectReferenceValue = selectables[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
EditorUtility.SetDirty(scope);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CompareSiblingPath(Selectable left, Selectable right)
|
||||||
|
{
|
||||||
|
if (left == right)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
string leftPath = GetSiblingPath(left.transform);
|
||||||
|
string rightPath = GetSiblingPath(right.transform);
|
||||||
|
return string.CompareOrdinal(leftPath, rightPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSiblingPath(Transform transform)
|
||||||
|
{
|
||||||
|
if (transform == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform.parent == null
|
||||||
|
? transform.GetSiblingIndex().ToString("D4")
|
||||||
|
: GetSiblingPath(transform.parent) + "/" + transform.GetSiblingIndex().ToString("D4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@ -1,5 +1,5 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 0268df3dd46bb194fa4ae7ec48be7702
|
guid: 7cc921173c16d4b4ba1fcf1fcaa80479
|
||||||
MonoImporter:
|
MonoImporter:
|
||||||
externalObjects: {}
|
externalObjects: {}
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
@ -1,7 +1,7 @@
|
|||||||
|
using UnityEditor;
|
||||||
using UnityEditor.DrawUtils;
|
using UnityEditor.DrawUtils;
|
||||||
using UnityEditor.Extensions;
|
using UnityEditor.Extensions;
|
||||||
using UnityEditor.SceneManagement;
|
using UnityEditor.SceneManagement;
|
||||||
using UnityEditor;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
@ -11,11 +11,11 @@ namespace UnityEditor.UI
|
|||||||
[CanEditMultipleObjects]
|
[CanEditMultipleObjects]
|
||||||
internal class UXToggleEditor : UXSelectableEditor
|
internal class UXToggleEditor : UXSelectableEditor
|
||||||
{
|
{
|
||||||
SerializedProperty m_OnValueChangedProperty;
|
private SerializedProperty m_OnValueChangedProperty;
|
||||||
SerializedProperty m_TransitionProperty;
|
private SerializedProperty m_TransitionProperty;
|
||||||
SerializedProperty m_GraphicProperty;
|
private SerializedProperty m_GraphicProperty;
|
||||||
SerializedProperty m_GroupProperty;
|
private SerializedProperty m_GroupProperty;
|
||||||
SerializedProperty m_IsOnProperty;
|
private SerializedProperty m_IsOnProperty;
|
||||||
|
|
||||||
private SerializedProperty hoverAudioClip;
|
private SerializedProperty hoverAudioClip;
|
||||||
private SerializedProperty clickAudioClip;
|
private SerializedProperty clickAudioClip;
|
||||||
@ -48,10 +48,8 @@ namespace UnityEditor.UI
|
|||||||
private void DrawEventTab()
|
private void DrawEventTab()
|
||||||
{
|
{
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
EditorGUILayout.PropertyField(m_OnValueChangedProperty);
|
EditorGUILayout.PropertyField(m_OnValueChangedProperty);
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,100 +58,88 @@ namespace UnityEditor.UI
|
|||||||
GUILayoutHelper.DrawProperty(hoverAudioClip, customSkin, "Hover Sound", "Play", () =>
|
GUILayoutHelper.DrawProperty(hoverAudioClip, customSkin, "Hover Sound", "Play", () =>
|
||||||
{
|
{
|
||||||
if (hoverAudioClip.objectReferenceValue != null)
|
if (hoverAudioClip.objectReferenceValue != null)
|
||||||
{
|
|
||||||
PlayAudio((AudioClip)hoverAudioClip.objectReferenceValue);
|
PlayAudio((AudioClip)hoverAudioClip.objectReferenceValue);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
GUILayoutHelper.DrawProperty(clickAudioClip, customSkin, "Click Sound", "Play", () =>
|
GUILayoutHelper.DrawProperty(clickAudioClip, customSkin, "Click Sound", "Play", () =>
|
||||||
{
|
{
|
||||||
if (clickAudioClip.objectReferenceValue != null)
|
if (clickAudioClip.objectReferenceValue != null)
|
||||||
{
|
|
||||||
PlayAudio((AudioClip)clickAudioClip.objectReferenceValue);
|
PlayAudio((AudioClip)clickAudioClip.objectReferenceValue);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PlayAudio(AudioClip clip)
|
private void PlayAudio(AudioClip clip)
|
||||||
{
|
{
|
||||||
if (clip != null)
|
if (clip != null)
|
||||||
{
|
|
||||||
ExtensionHelper.PreviewAudioClip(clip);
|
ExtensionHelper.PreviewAudioClip(clip);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawImageTab()
|
private void DrawImageTab()
|
||||||
{
|
{
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
|
||||||
UXToggle toggle = serializedObject.targetObject as UXToggle;
|
UXToggle toggle = serializedObject.targetObject as UXToggle;
|
||||||
EditorGUI.BeginChangeCheck();
|
DrawIsOn(toggle);
|
||||||
GUILayoutHelper.DrawProperty(m_IsOnProperty, customSkin, "Is On");
|
|
||||||
if (EditorGUI.EndChangeCheck())
|
|
||||||
{
|
|
||||||
if (!Application.isPlaying)
|
|
||||||
EditorSceneManager.MarkSceneDirty(toggle.gameObject.scene);
|
|
||||||
|
|
||||||
UXGroup group = m_GroupProperty.objectReferenceValue as UXGroup;
|
|
||||||
|
|
||||||
bool newIsOn = m_IsOnProperty.boolValue;
|
|
||||||
bool oldIsOn = toggle.isOn;
|
|
||||||
|
|
||||||
// 编辑器下:如果属于某组且不允许 all-off,且当前正是被选中的项,则禁止通过 Inspector 将其关闭
|
|
||||||
if (!Application.isPlaying && group != null && !group.allowSwitchOff && oldIsOn && !newIsOn)
|
|
||||||
{
|
|
||||||
Debug.LogWarning($"Cannot turn off toggle '{toggle.name}' because its group '{group.name}' does not allow all toggles to be off.", toggle);
|
|
||||||
// 恢复 Inspector 中的显示为 true
|
|
||||||
m_IsOnProperty.boolValue = true;
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 使用属性赋值以保证视觉刷新(PlayEffect 会在 setter 被调用)
|
|
||||||
toggle.isOn = newIsOn;
|
|
||||||
|
|
||||||
if (group != null && group.isActiveAndEnabled && toggle.IsActive())
|
|
||||||
{
|
|
||||||
if (toggle.isOn || (!group.AnyTogglesOn() && !group.allowSwitchOff))
|
|
||||||
{
|
|
||||||
toggle.isOn = true;
|
|
||||||
group.NotifyToggleOn(toggle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayoutHelper.DrawProperty(m_TransitionProperty, customSkin, "Transition");
|
GUILayoutHelper.DrawProperty(m_TransitionProperty, customSkin, "Transition");
|
||||||
GUILayoutHelper.DrawProperty(m_GraphicProperty, customSkin, "Graphic");
|
GUILayoutHelper.DrawProperty(m_GraphicProperty, customSkin, "Graphic");
|
||||||
EditorGUI.BeginChangeCheck();
|
DrawGroup(toggle);
|
||||||
GUILayoutHelper.DrawProperty<UXGroup>(m_GroupProperty, customSkin, "UXGroup", (oldValue, newValue) =>
|
|
||||||
{
|
|
||||||
UXToggle self = target as UXToggle;
|
|
||||||
if (oldValue != null)
|
|
||||||
{
|
|
||||||
oldValue.UnregisterToggle(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newValue != null)
|
|
||||||
{
|
|
||||||
newValue.RegisterToggle(self);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (EditorGUI.EndChangeCheck())
|
|
||||||
{
|
|
||||||
if (!Application.isPlaying)
|
|
||||||
EditorSceneManager.MarkSceneDirty(toggle.gameObject.scene);
|
|
||||||
|
|
||||||
UXGroup group = m_GroupProperty.objectReferenceValue as UXGroup;
|
|
||||||
|
|
||||||
// Use the property setter to ensure consistent registration/unregistration
|
|
||||||
toggle.group = group;
|
|
||||||
}
|
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
serializedObject.ApplyModifiedProperties();
|
serializedObject.ApplyModifiedProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawIsOn(UXToggle toggle)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
GUILayoutHelper.DrawProperty(m_IsOnProperty, customSkin, "Is On");
|
||||||
|
if (!EditorGUI.EndChangeCheck())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!Application.isPlaying)
|
||||||
|
EditorSceneManager.MarkSceneDirty(toggle.gameObject.scene);
|
||||||
|
|
||||||
|
UXGroup group = m_GroupProperty.objectReferenceValue as UXGroup;
|
||||||
|
bool newIsOn = m_IsOnProperty.boolValue;
|
||||||
|
bool oldIsOn = toggle.isOn;
|
||||||
|
|
||||||
|
if (!Application.isPlaying && group != null && !group.allowSwitchOff && oldIsOn && !newIsOn)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Cannot turn off the selected toggle because its group does not allow all toggles to be off.", toggle);
|
||||||
|
m_IsOnProperty.boolValue = true;
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle.isOn = newIsOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawGroup(UXToggle toggle)
|
||||||
|
{
|
||||||
|
EditorGUI.BeginChangeCheck();
|
||||||
|
GUILayoutHelper.DrawProperty<UXGroup>(m_GroupProperty, customSkin, "UXGroup", OnGroupChanged);
|
||||||
|
if (!EditorGUI.EndChangeCheck())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!Application.isPlaying)
|
||||||
|
EditorSceneManager.MarkSceneDirty(toggle.gameObject.scene);
|
||||||
|
|
||||||
|
UXGroup group = m_GroupProperty.objectReferenceValue as UXGroup;
|
||||||
|
toggle.group = group;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGroupChanged(UXGroup oldValue, UXGroup newValue)
|
||||||
|
{
|
||||||
|
UXToggle toggle = target as UXToggle;
|
||||||
|
if (toggle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (oldValue != null)
|
||||||
|
oldValue.UnregisterToggle(toggle);
|
||||||
|
|
||||||
|
if (newValue != null)
|
||||||
|
newValue.RegisterToggle(toggle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 9301ee465f2c46d08b2fade637710625
|
guid: 9301ee465f2c46d08b2fade637710625
|
||||||
timeCreated: 1766136386
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
@ -106,6 +106,9 @@ namespace AlicizaX.UI
|
|||||||
public void OnSelect(BaseEventData eventData)
|
public void OnSelect(BaseEventData eventData)
|
||||||
{
|
{
|
||||||
#if UX_NAVIGATION
|
#if UX_NAVIGATION
|
||||||
|
#if INPUTSYSTEM_SUPPORT
|
||||||
|
UXNavigationRuntime.NotifySelection(gameObject);
|
||||||
|
#endif
|
||||||
if ((flags & ItemInteractionFlags.Select) != 0)
|
if ((flags & ItemInteractionFlags.Select) != 0)
|
||||||
{
|
{
|
||||||
host?.HandleSelect(eventData);
|
host?.HandleSelect(eventData);
|
||||||
|
|||||||
@ -29,6 +29,9 @@ namespace AlicizaX.UI
|
|||||||
public override void OnSelect(BaseEventData eventData)
|
public override void OnSelect(BaseEventData eventData)
|
||||||
{
|
{
|
||||||
base.OnSelect(eventData);
|
base.OnSelect(eventData);
|
||||||
|
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
||||||
|
UXNavigationRuntime.NotifySelection(gameObject);
|
||||||
|
#endif
|
||||||
TryEnter(defaultEntryDirection);
|
TryEnter(defaultEntryDirection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -37,6 +37,9 @@ namespace UnityEngine.UI
|
|||||||
public override void OnSelect(BaseEventData eventData)
|
public override void OnSelect(BaseEventData eventData)
|
||||||
{
|
{
|
||||||
base.OnSelect(eventData);
|
base.OnSelect(eventData);
|
||||||
|
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
||||||
|
UXNavigationRuntime.NotifySelection(gameObject);
|
||||||
|
#endif
|
||||||
if (eventData is PointerEventData)
|
if (eventData is PointerEventData)
|
||||||
return;
|
return;
|
||||||
PlayAudio(hoverAudioClip);
|
PlayAudio(hoverAudioClip);
|
||||||
|
|||||||
@ -115,10 +115,27 @@ namespace UnityEngine.UI
|
|||||||
[Serializable]
|
[Serializable]
|
||||||
public sealed class BindingEntry
|
public sealed class BindingEntry
|
||||||
{
|
{
|
||||||
|
[Serializable]
|
||||||
|
public sealed class IndexedValue
|
||||||
|
{
|
||||||
|
[SerializeField] private int _index;
|
||||||
|
[SerializeField] private UXBindingValue _value = new UXBindingValue();
|
||||||
|
|
||||||
|
public int Index
|
||||||
|
{
|
||||||
|
get => Mathf.Max(0, _index);
|
||||||
|
set => _index = Mathf.Max(0, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UXBindingValue Value => _value;
|
||||||
|
}
|
||||||
|
|
||||||
[SerializeField] private string _controllerId = string.Empty;
|
[SerializeField] private string _controllerId = string.Empty;
|
||||||
[SerializeField] private int _controllerIndex;
|
[SerializeField] private int _controllerIndex;
|
||||||
|
[SerializeField] private int _controllerIndexMask = 1;
|
||||||
[SerializeField] private UXBindingProperty _property = UXBindingProperty.GameObjectActive;
|
[SerializeField] private UXBindingProperty _property = UXBindingProperty.GameObjectActive;
|
||||||
[SerializeField] private UXBindingValue _value = new UXBindingValue();
|
[SerializeField] private UXBindingValue _value = new UXBindingValue();
|
||||||
|
[SerializeField] private List<IndexedValue> _indexedValues = new List<IndexedValue>();
|
||||||
[SerializeField] private UXBindingFallbackMode _fallbackMode = UXBindingFallbackMode.RestoreCapturedDefault;
|
[SerializeField] private UXBindingFallbackMode _fallbackMode = UXBindingFallbackMode.RestoreCapturedDefault;
|
||||||
[SerializeField] private UXBindingValue _fallbackValue = new UXBindingValue();
|
[SerializeField] private UXBindingValue _fallbackValue = new UXBindingValue();
|
||||||
[HideInInspector] [SerializeField] private UXBindingValue _capturedDefault = new UXBindingValue();
|
[HideInInspector] [SerializeField] private UXBindingValue _capturedDefault = new UXBindingValue();
|
||||||
@ -134,7 +151,17 @@ namespace UnityEngine.UI
|
|||||||
public int ControllerIndex
|
public int ControllerIndex
|
||||||
{
|
{
|
||||||
get => Mathf.Max(0, _controllerIndex);
|
get => Mathf.Max(0, _controllerIndex);
|
||||||
set => _controllerIndex = Mathf.Max(0, value);
|
set
|
||||||
|
{
|
||||||
|
_controllerIndex = Mathf.Max(0, value);
|
||||||
|
_controllerIndexMask = IndexToMask(_controllerIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int ControllerIndexMask
|
||||||
|
{
|
||||||
|
get => NormalizeMask(_controllerIndexMask, _controllerIndex);
|
||||||
|
set => _controllerIndexMask = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UXBindingProperty Property
|
public UXBindingProperty Property
|
||||||
@ -144,6 +171,7 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
public UXBindingValue Value => _value;
|
public UXBindingValue Value => _value;
|
||||||
|
public List<IndexedValue> IndexedValues => _indexedValues;
|
||||||
|
|
||||||
public UXBindingFallbackMode FallbackMode
|
public UXBindingFallbackMode FallbackMode
|
||||||
{
|
{
|
||||||
@ -156,6 +184,13 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
internal void Normalize()
|
internal void Normalize()
|
||||||
{
|
{
|
||||||
|
_controllerIndex = Mathf.Max(0, _controllerIndex);
|
||||||
|
_controllerIndexMask = NormalizeMask(_controllerIndexMask, _controllerIndex);
|
||||||
|
if (_indexedValues.Count == 0)
|
||||||
|
{
|
||||||
|
EnsureIndexedValue(_controllerIndex).Value.CopyFrom(_value);
|
||||||
|
}
|
||||||
|
|
||||||
if (_property != UXBindingProperty.GameObjectActive)
|
if (_property != UXBindingProperty.GameObjectActive)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@ -168,36 +203,86 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CaptureDefault(GameObject target)
|
[NonSerialized] private int _runtimeControllerSlot = -1;
|
||||||
|
[NonSerialized] private UXBindingResolvedTarget _runtimeTarget;
|
||||||
|
[NonSerialized] private bool _runtimeSupported;
|
||||||
|
|
||||||
|
internal int RuntimeControllerSlot => _runtimeControllerSlot;
|
||||||
|
internal bool RuntimeSupported => _runtimeSupported;
|
||||||
|
|
||||||
|
internal void BuildRuntime(GameObject target, UXController controller)
|
||||||
{
|
{
|
||||||
if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property))
|
_runtimeControllerSlot = -1;
|
||||||
|
_runtimeSupported = UXBindingPropertyUtility.Resolve(target, _property, out _runtimeTarget);
|
||||||
|
|
||||||
|
if (controller != null && !string.IsNullOrEmpty(_controllerId))
|
||||||
{
|
{
|
||||||
|
controller.TryGetControllerSlot(_controllerId, out _runtimeControllerSlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_runtimeSupported)
|
||||||
|
{
|
||||||
|
_hasCapturedDefault = false;
|
||||||
|
_capturedProperty = _property;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_hasCapturedDefault || _capturedProperty != _property)
|
||||||
|
{
|
||||||
|
CaptureDefault(in _runtimeTarget);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CaptureDefault(GameObject target)
|
||||||
|
{
|
||||||
|
if (!UXBindingPropertyUtility.Resolve(target, _property, out UXBindingResolvedTarget resolvedTarget))
|
||||||
|
{
|
||||||
|
_hasCapturedDefault = false;
|
||||||
|
_capturedProperty = _property;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CaptureDefault(in resolvedTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureDefault(in UXBindingResolvedTarget target)
|
||||||
|
{
|
||||||
|
if (!UXBindingPropertyUtility.CaptureValue(in target, _property, _capturedDefault))
|
||||||
|
{
|
||||||
|
_hasCapturedDefault = false;
|
||||||
|
_capturedProperty = _property;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UXBindingPropertyUtility.CaptureValue(target, _property, _capturedDefault);
|
|
||||||
_capturedProperty = _property;
|
_capturedProperty = _property;
|
||||||
_hasCapturedDefault = true;
|
_hasCapturedDefault = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CaptureCurrentAsValue(GameObject target)
|
internal void CaptureCurrentAsValue(GameObject target)
|
||||||
{
|
{
|
||||||
if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property))
|
CaptureCurrentAsValue(target, _controllerIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void CaptureCurrentAsValue(GameObject target, int selectedIndex)
|
||||||
|
{
|
||||||
|
UXBindingValue value = GetMutableValue(selectedIndex);
|
||||||
|
if (!UXBindingPropertyUtility.CaptureValue(target, _property, value))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UXBindingPropertyUtility.CaptureValue(target, _property, _value);
|
if (selectedIndex == _controllerIndex)
|
||||||
|
{
|
||||||
|
_value.CopyFrom(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void CaptureCurrentAsFallback(GameObject target)
|
internal void CaptureCurrentAsFallback(GameObject target)
|
||||||
{
|
{
|
||||||
if (target == null || !UXBindingPropertyUtility.IsSupported(target, _property))
|
if (!UXBindingPropertyUtility.CaptureValue(target, _property, _fallbackValue))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UXBindingPropertyUtility.CaptureValue(target, _property, _fallbackValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ResetToCapturedDefault(GameObject target)
|
internal void ResetToCapturedDefault(GameObject target)
|
||||||
@ -215,26 +300,29 @@ namespace UnityEngine.UI
|
|||||||
UXBindingPropertyUtility.ApplyValue(target, _property, _capturedDefault);
|
UXBindingPropertyUtility.ApplyValue(target, _property, _capturedDefault);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Apply(GameObject target, string controllerId, int selectedIndex)
|
internal void ApplyRuntime(int selectedIndex)
|
||||||
{
|
{
|
||||||
if (target == null || !string.Equals(_controllerId, controllerId, StringComparison.Ordinal))
|
if (!_runtimeSupported)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_hasCapturedDefault || _capturedProperty != _property)
|
if (!_hasCapturedDefault || _capturedProperty != _property)
|
||||||
{
|
{
|
||||||
CaptureDefault(target);
|
CaptureDefault(in _runtimeTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!UXBindingPropertyUtility.IsSupported(target, _property))
|
if (_property == UXBindingProperty.GameObjectActive)
|
||||||
{
|
{
|
||||||
return;
|
if (IsSelectedIndexMatched(selectedIndex))
|
||||||
|
{
|
||||||
|
UXBindingPropertyUtility.ApplyValue(in _runtimeTarget, _property, _value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else if (TryGetValue(selectedIndex, out UXBindingValue indexedValue))
|
||||||
if (selectedIndex == _controllerIndex)
|
|
||||||
{
|
{
|
||||||
UXBindingPropertyUtility.ApplyValue(target, _property, _value);
|
UXBindingPropertyUtility.ApplyValue(in _runtimeTarget, _property, indexedValue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,14 +333,90 @@ namespace UnityEngine.UI
|
|||||||
case UXBindingFallbackMode.RestoreCapturedDefault:
|
case UXBindingFallbackMode.RestoreCapturedDefault:
|
||||||
if (_hasCapturedDefault)
|
if (_hasCapturedDefault)
|
||||||
{
|
{
|
||||||
UXBindingPropertyUtility.ApplyValue(target, _property, _capturedDefault);
|
UXBindingPropertyUtility.ApplyValue(in _runtimeTarget, _property, _capturedDefault);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
case UXBindingFallbackMode.UseCustomValue:
|
case UXBindingFallbackMode.UseCustomValue:
|
||||||
UXBindingPropertyUtility.ApplyValue(target, _property, _fallbackValue);
|
UXBindingPropertyUtility.ApplyValue(in _runtimeTarget, _property, _fallbackValue);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool IsSelectedIndexMatched(int selectedIndex)
|
||||||
|
{
|
||||||
|
if (selectedIndex < 0 || selectedIndex >= 31)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ControllerIndexMask & IndexToMask(selectedIndex)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool TryGetValue(int selectedIndex, out UXBindingValue value)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _indexedValues.Count; i++)
|
||||||
|
{
|
||||||
|
IndexedValue indexedValue = _indexedValues[i];
|
||||||
|
if (indexedValue != null && indexedValue.Index == selectedIndex)
|
||||||
|
{
|
||||||
|
value = indexedValue.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedIndex == _controllerIndex)
|
||||||
|
{
|
||||||
|
value = _value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal UXBindingValue GetMutableValue(int selectedIndex)
|
||||||
|
{
|
||||||
|
return EnsureIndexedValue(selectedIndex).Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IndexedValue EnsureIndexedValue(int selectedIndex)
|
||||||
|
{
|
||||||
|
selectedIndex = Mathf.Max(0, selectedIndex);
|
||||||
|
for (int i = 0; i < _indexedValues.Count; i++)
|
||||||
|
{
|
||||||
|
IndexedValue indexedValue = _indexedValues[i];
|
||||||
|
if (indexedValue != null && indexedValue.Index == selectedIndex)
|
||||||
|
{
|
||||||
|
return indexedValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexedValue nextValue = new IndexedValue();
|
||||||
|
nextValue.Index = selectedIndex;
|
||||||
|
nextValue.Value.CopyFrom(_value);
|
||||||
|
_indexedValues.Add(nextValue);
|
||||||
|
return nextValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static int IndexToMask(int index)
|
||||||
|
{
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= 31)
|
||||||
|
{
|
||||||
|
return 1 << 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1 << index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int NormalizeMask(int mask, int fallbackIndex)
|
||||||
|
{
|
||||||
|
return mask != 0 ? mask : IndexToMask(fallbackIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SerializeField] private UXController _controller;
|
[SerializeField] private UXController _controller;
|
||||||
@ -261,7 +425,8 @@ namespace UnityEngine.UI
|
|||||||
private bool _initialized;
|
private bool _initialized;
|
||||||
|
|
||||||
public UXController Controller => _controller;
|
public UXController Controller => _controller;
|
||||||
public List<BindingEntry> Entries => _entries;
|
public IReadOnlyList<BindingEntry> Entries => _entries;
|
||||||
|
internal int RuntimeEntryCount => _entries.Count;
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize()
|
||||||
{
|
{
|
||||||
@ -274,7 +439,34 @@ namespace UnityEngine.UI
|
|||||||
NormalizeEntries();
|
NormalizeEntries();
|
||||||
EnsureControllerReference();
|
EnsureControllerReference();
|
||||||
RegisterToController();
|
RegisterToController();
|
||||||
CaptureDefaults();
|
BuildRuntimeEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void RebuildRuntime(UXController controller)
|
||||||
|
{
|
||||||
|
_controller = controller;
|
||||||
|
NormalizeEntries();
|
||||||
|
BuildRuntimeEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int GetRuntimeControllerSlot(int entryIndex)
|
||||||
|
{
|
||||||
|
return entryIndex >= 0 && entryIndex < _entries.Count && _entries[entryIndex] != null
|
||||||
|
? _entries[entryIndex].RuntimeControllerSlot
|
||||||
|
: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsRuntimeEntrySupported(int entryIndex)
|
||||||
|
{
|
||||||
|
return entryIndex >= 0 && entryIndex < _entries.Count && _entries[entryIndex] != null && _entries[entryIndex].RuntimeSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ApplyRuntimeEntry(int entryIndex, int selectedIndex)
|
||||||
|
{
|
||||||
|
if (entryIndex >= 0 && entryIndex < _entries.Count && _entries[entryIndex] != null)
|
||||||
|
{
|
||||||
|
_entries[entryIndex].ApplyRuntime(selectedIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetController(UXController controller)
|
public void SetController(UXController controller)
|
||||||
@ -291,6 +483,7 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
_controller = controller;
|
_controller = controller;
|
||||||
RegisterToController();
|
RegisterToController();
|
||||||
|
BuildRuntimeEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CaptureDefaults()
|
public void CaptureDefaults()
|
||||||
@ -328,7 +521,20 @@ namespace UnityEngine.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_controller.SetControllerIndex(entry.ControllerId, entry.ControllerIndex);
|
_controller.SetControllerIndex(entry.ControllerId, GetFirstSelectedIndex(entry.ControllerIndexMask));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetFirstSelectedIndex(int mask)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 31; i++)
|
||||||
|
{
|
||||||
|
if ((mask & BindingEntry.IndexToMask(i)) != 0)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CaptureEntryValue(int entryIndex)
|
public void CaptureEntryValue(int entryIndex)
|
||||||
@ -338,7 +544,28 @@ namespace UnityEngine.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_entries[entryIndex].CaptureCurrentAsValue(gameObject);
|
_entries[entryIndex].CaptureCurrentAsValue(gameObject, GetFirstSelectedIndex(_entries[entryIndex].ControllerIndexMask));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CaptureEntryValue(int entryIndex, int selectedIndex)
|
||||||
|
{
|
||||||
|
if (entryIndex < 0 || entryIndex >= _entries.Count || _entries[entryIndex] == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_entries[entryIndex].CaptureCurrentAsValue(gameObject, selectedIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ApplyEntryValue(int entryIndex, int selectedIndex)
|
||||||
|
{
|
||||||
|
if (entryIndex < 0 || entryIndex >= _entries.Count || _entries[entryIndex] == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_entries[entryIndex].BuildRuntime(gameObject, _controller);
|
||||||
|
_entries[entryIndex].ApplyRuntime(selectedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CaptureEntryFallbackValue(int entryIndex)
|
public void CaptureEntryFallbackValue(int entryIndex)
|
||||||
@ -351,18 +578,6 @@ namespace UnityEngine.UI
|
|||||||
_entries[entryIndex].CaptureCurrentAsFallback(gameObject);
|
_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()
|
private void Reset()
|
||||||
{
|
{
|
||||||
EnsureControllerReference();
|
EnsureControllerReference();
|
||||||
@ -405,6 +620,17 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BuildRuntimeEntries()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _entries.Count; i++)
|
||||||
|
{
|
||||||
|
if (_entries[i] != null)
|
||||||
|
{
|
||||||
|
_entries[i].BuildRuntime(gameObject, _controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void NormalizeEntries()
|
private void NormalizeEntries()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _entries.Count; i++)
|
for (int i = 0; i < _entries.Count; i++)
|
||||||
|
|||||||
@ -24,6 +24,18 @@ namespace UnityEngine.UI
|
|||||||
public Type ObjectReferenceType { get; }
|
public Type ObjectReferenceType { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct UXBindingResolvedTarget
|
||||||
|
{
|
||||||
|
public GameObject GameObject;
|
||||||
|
public Transform Transform;
|
||||||
|
public CanvasGroup CanvasGroup;
|
||||||
|
public Graphic Graphic;
|
||||||
|
public Image Image;
|
||||||
|
public Text Text;
|
||||||
|
public TextMeshProUGUI TmpText;
|
||||||
|
public RectTransform RectTransform;
|
||||||
|
}
|
||||||
|
|
||||||
public static class UXBindingPropertyUtility
|
public static class UXBindingPropertyUtility
|
||||||
{
|
{
|
||||||
private static readonly UXBindingPropertyMetadata[] Metadata =
|
private static readonly UXBindingPropertyMetadata[] Metadata =
|
||||||
@ -46,6 +58,12 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
public static UXBindingPropertyMetadata GetMetadata(UXBindingProperty property)
|
public static UXBindingPropertyMetadata GetMetadata(UXBindingProperty property)
|
||||||
{
|
{
|
||||||
|
int index = (int)property;
|
||||||
|
if ((uint)index < (uint)Metadata.Length && Metadata[index].Property == property)
|
||||||
|
{
|
||||||
|
return Metadata[index];
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < Metadata.Length; i++)
|
for (int i = 0; i < Metadata.Length; i++)
|
||||||
{
|
{
|
||||||
if (Metadata[i].Property == property)
|
if (Metadata[i].Property == property)
|
||||||
@ -57,13 +75,17 @@ namespace UnityEngine.UI
|
|||||||
return Metadata[0];
|
return Metadata[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsSupported(GameObject target, UXBindingProperty property)
|
public static bool Resolve(GameObject target, UXBindingProperty property, out UXBindingResolvedTarget resolvedTarget)
|
||||||
{
|
{
|
||||||
|
resolvedTarget = default;
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolvedTarget.GameObject = target;
|
||||||
|
resolvedTarget.Transform = target.transform;
|
||||||
|
|
||||||
switch (property)
|
switch (property)
|
||||||
{
|
{
|
||||||
case UXBindingProperty.GameObjectActive:
|
case UXBindingProperty.GameObjectActive:
|
||||||
@ -73,22 +95,32 @@ namespace UnityEngine.UI
|
|||||||
case UXBindingProperty.CanvasGroupAlpha:
|
case UXBindingProperty.CanvasGroupAlpha:
|
||||||
case UXBindingProperty.CanvasGroupInteractable:
|
case UXBindingProperty.CanvasGroupInteractable:
|
||||||
case UXBindingProperty.CanvasGroupBlocksRaycasts:
|
case UXBindingProperty.CanvasGroupBlocksRaycasts:
|
||||||
return target.GetComponent<CanvasGroup>() != null;
|
return target.TryGetComponent(out resolvedTarget.CanvasGroup);
|
||||||
case UXBindingProperty.GraphicColor:
|
case UXBindingProperty.GraphicColor:
|
||||||
case UXBindingProperty.GraphicMaterial:
|
case UXBindingProperty.GraphicMaterial:
|
||||||
return target.GetComponent<Graphic>() != null;
|
return target.TryGetComponent(out resolvedTarget.Graphic);
|
||||||
case UXBindingProperty.ImageSprite:
|
case UXBindingProperty.ImageSprite:
|
||||||
return target.GetComponent<Image>() != null;
|
return target.TryGetComponent(out resolvedTarget.Image);
|
||||||
case UXBindingProperty.TextContent:
|
case UXBindingProperty.TextContent:
|
||||||
case UXBindingProperty.TextColor:
|
case UXBindingProperty.TextColor:
|
||||||
return target.GetComponent<Text>() != null || target.GetComponent<TextMeshProUGUI>() != null;
|
if (target.TryGetComponent(out resolvedTarget.Text))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return target.TryGetComponent(out resolvedTarget.TmpText);
|
||||||
case UXBindingProperty.RectTransformAnchoredPosition:
|
case UXBindingProperty.RectTransformAnchoredPosition:
|
||||||
return target.GetComponent<RectTransform>() != null;
|
return target.TryGetComponent(out resolvedTarget.RectTransform);
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool IsSupported(GameObject target, UXBindingProperty property)
|
||||||
|
{
|
||||||
|
return Resolve(target, property, out _);
|
||||||
|
}
|
||||||
|
|
||||||
public static void GetSupportedProperties(GameObject target, List<UXBindingProperty> output)
|
public static void GetSupportedProperties(GameObject target, List<UXBindingProperty> output)
|
||||||
{
|
{
|
||||||
output.Clear();
|
output.Clear();
|
||||||
@ -107,127 +139,185 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void CaptureValue(GameObject target, UXBindingProperty property, UXBindingValue destination)
|
public static bool CaptureValue(GameObject target, UXBindingProperty property, UXBindingValue destination)
|
||||||
{
|
{
|
||||||
if (target == null || destination == null)
|
if (!Resolve(target, property, out UXBindingResolvedTarget resolvedTarget))
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return CaptureValue(in resolvedTarget, property, destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CaptureValue(in UXBindingResolvedTarget target, UXBindingProperty property, UXBindingValue destination)
|
||||||
|
{
|
||||||
|
if (destination == null || target.GameObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (property)
|
switch (property)
|
||||||
{
|
{
|
||||||
case UXBindingProperty.GameObjectActive:
|
case UXBindingProperty.GameObjectActive:
|
||||||
destination.BoolValue = target.activeSelf;
|
destination.BoolValue = target.GameObject.activeSelf;
|
||||||
return;
|
return true;
|
||||||
case UXBindingProperty.CanvasGroupAlpha:
|
case UXBindingProperty.CanvasGroupAlpha:
|
||||||
destination.FloatValue = target.GetComponent<CanvasGroup>().alpha;
|
if (target.CanvasGroup == null) return false;
|
||||||
return;
|
destination.FloatValue = target.CanvasGroup.alpha;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.CanvasGroupInteractable:
|
case UXBindingProperty.CanvasGroupInteractable:
|
||||||
destination.BoolValue = target.GetComponent<CanvasGroup>().interactable;
|
if (target.CanvasGroup == null) return false;
|
||||||
return;
|
destination.BoolValue = target.CanvasGroup.interactable;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.CanvasGroupBlocksRaycasts:
|
case UXBindingProperty.CanvasGroupBlocksRaycasts:
|
||||||
destination.BoolValue = target.GetComponent<CanvasGroup>().blocksRaycasts;
|
if (target.CanvasGroup == null) return false;
|
||||||
return;
|
destination.BoolValue = target.CanvasGroup.blocksRaycasts;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.GraphicColor:
|
case UXBindingProperty.GraphicColor:
|
||||||
destination.ColorValue = target.GetComponent<Graphic>().color;
|
if (target.Graphic == null) return false;
|
||||||
return;
|
destination.ColorValue = target.Graphic.color;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.GraphicMaterial:
|
case UXBindingProperty.GraphicMaterial:
|
||||||
destination.ObjectValue = target.GetComponent<Graphic>().material;
|
if (target.Graphic == null) return false;
|
||||||
return;
|
destination.ObjectValue = target.Graphic.defaultMaterial;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.ImageSprite:
|
case UXBindingProperty.ImageSprite:
|
||||||
destination.ObjectValue = target.GetComponent<Image>().sprite;
|
if (target.Image == null) return false;
|
||||||
return;
|
destination.ObjectValue = target.Image.sprite;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.TextContent:
|
case UXBindingProperty.TextContent:
|
||||||
if (target.TryGetComponent<Text>(out Text text))
|
if (target.Text != null)
|
||||||
{
|
{
|
||||||
destination.StringValue = text.text;
|
destination.StringValue = target.Text.text;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmp))
|
|
||||||
|
if (target.TmpText != null)
|
||||||
{
|
{
|
||||||
destination.StringValue = tmp.text;
|
destination.StringValue = target.TmpText.text;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
return false;
|
||||||
case UXBindingProperty.TextColor:
|
case UXBindingProperty.TextColor:
|
||||||
if (target.TryGetComponent<Text>(out Text legacyText))
|
if (target.Text != null)
|
||||||
{
|
{
|
||||||
destination.ColorValue = legacyText.color;
|
destination.ColorValue = target.Text.color;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmpText))
|
|
||||||
|
if (target.TmpText != null)
|
||||||
{
|
{
|
||||||
destination.ColorValue = tmpText.color;
|
destination.ColorValue = target.TmpText.color;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
return false;
|
||||||
case UXBindingProperty.RectTransformAnchoredPosition:
|
case UXBindingProperty.RectTransformAnchoredPosition:
|
||||||
destination.Vector2Value = target.GetComponent<RectTransform>().anchoredPosition;
|
if (target.RectTransform == null) return false;
|
||||||
return;
|
destination.Vector2Value = target.RectTransform.anchoredPosition;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.TransformLocalScale:
|
case UXBindingProperty.TransformLocalScale:
|
||||||
destination.Vector3Value = target.transform.localScale;
|
if (target.Transform == null) return false;
|
||||||
return;
|
destination.Vector3Value = target.Transform.localScale;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.TransformLocalEulerAngles:
|
case UXBindingProperty.TransformLocalEulerAngles:
|
||||||
destination.Vector3Value = target.transform.localEulerAngles;
|
if (target.Transform == null) return false;
|
||||||
return;
|
destination.Vector3Value = target.Transform.localEulerAngles;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ApplyValue(GameObject target, UXBindingProperty property, UXBindingValue value)
|
public static bool ApplyValue(GameObject target, UXBindingProperty property, UXBindingValue value)
|
||||||
{
|
{
|
||||||
if (target == null || value == null)
|
if (!Resolve(target, property, out UXBindingResolvedTarget resolvedTarget))
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ApplyValue(in resolvedTarget, property, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool ApplyValue(in UXBindingResolvedTarget target, UXBindingProperty property, UXBindingValue value)
|
||||||
|
{
|
||||||
|
if (value == null || target.GameObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (property)
|
switch (property)
|
||||||
{
|
{
|
||||||
case UXBindingProperty.GameObjectActive:
|
case UXBindingProperty.GameObjectActive:
|
||||||
target.SetActive(value.BoolValue);
|
target.GameObject.SetActive(value.BoolValue);
|
||||||
return;
|
return true;
|
||||||
case UXBindingProperty.CanvasGroupAlpha:
|
case UXBindingProperty.CanvasGroupAlpha:
|
||||||
target.GetComponent<CanvasGroup>().alpha = value.FloatValue;
|
if (target.CanvasGroup == null) return false;
|
||||||
return;
|
target.CanvasGroup.alpha = value.FloatValue;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.CanvasGroupInteractable:
|
case UXBindingProperty.CanvasGroupInteractable:
|
||||||
target.GetComponent<CanvasGroup>().interactable = value.BoolValue;
|
if (target.CanvasGroup == null) return false;
|
||||||
return;
|
target.CanvasGroup.interactable = value.BoolValue;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.CanvasGroupBlocksRaycasts:
|
case UXBindingProperty.CanvasGroupBlocksRaycasts:
|
||||||
target.GetComponent<CanvasGroup>().blocksRaycasts = value.BoolValue;
|
if (target.CanvasGroup == null) return false;
|
||||||
return;
|
target.CanvasGroup.blocksRaycasts = value.BoolValue;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.GraphicColor:
|
case UXBindingProperty.GraphicColor:
|
||||||
target.GetComponent<Graphic>().color = value.ColorValue;
|
if (target.Graphic == null) return false;
|
||||||
return;
|
target.Graphic.color = value.ColorValue;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.GraphicMaterial:
|
case UXBindingProperty.GraphicMaterial:
|
||||||
target.GetComponent<Graphic>().material = value.ObjectValue as Material;
|
if (target.Graphic == null) return false;
|
||||||
return;
|
target.Graphic.material = value.ObjectValue as Material;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.ImageSprite:
|
case UXBindingProperty.ImageSprite:
|
||||||
target.GetComponent<Image>().sprite = value.ObjectValue as Sprite;
|
if (target.Image == null) return false;
|
||||||
return;
|
target.Image.sprite = value.ObjectValue as Sprite;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.TextContent:
|
case UXBindingProperty.TextContent:
|
||||||
if (target.TryGetComponent<Text>(out Text text))
|
if (target.Text != null)
|
||||||
{
|
{
|
||||||
text.text = value.StringValue;
|
target.Text.text = value.StringValue;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmp))
|
|
||||||
|
if (target.TmpText != null)
|
||||||
{
|
{
|
||||||
tmp.text = value.StringValue;
|
target.TmpText.text = value.StringValue;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
return false;
|
||||||
case UXBindingProperty.TextColor:
|
case UXBindingProperty.TextColor:
|
||||||
if (target.TryGetComponent<Text>(out Text legacyText))
|
if (target.Text != null)
|
||||||
{
|
{
|
||||||
legacyText.color = value.ColorValue;
|
target.Text.color = value.ColorValue;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else if (target.TryGetComponent<TextMeshProUGUI>(out TextMeshProUGUI tmpText))
|
|
||||||
|
if (target.TmpText != null)
|
||||||
{
|
{
|
||||||
tmpText.color = value.ColorValue;
|
target.TmpText.color = value.ColorValue;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
|
return false;
|
||||||
case UXBindingProperty.RectTransformAnchoredPosition:
|
case UXBindingProperty.RectTransformAnchoredPosition:
|
||||||
target.GetComponent<RectTransform>().anchoredPosition = value.Vector2Value;
|
if (target.RectTransform == null) return false;
|
||||||
return;
|
target.RectTransform.anchoredPosition = value.Vector2Value;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.TransformLocalScale:
|
case UXBindingProperty.TransformLocalScale:
|
||||||
target.transform.localScale = value.Vector3Value;
|
if (target.Transform == null) return false;
|
||||||
return;
|
target.Transform.localScale = value.Vector3Value;
|
||||||
|
return true;
|
||||||
case UXBindingProperty.TransformLocalEulerAngles:
|
case UXBindingProperty.TransformLocalEulerAngles:
|
||||||
target.transform.localEulerAngles = value.Vector3Value;
|
if (target.Transform == null) return false;
|
||||||
return;
|
target.Transform.localEulerAngles = value.Vector3Value;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ namespace UnityEngine.UI
|
|||||||
[SerializeField] private string _id = string.Empty;
|
[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 int _defaultIndex;
|
||||||
[SerializeField] private string _description = string.Empty;
|
[SerializeField] private string _description = string.Empty;
|
||||||
[NonSerialized] private int _selectedIndex = -1;
|
[NonSerialized] private int _selectedIndex = -1;
|
||||||
[NonSerialized] private UXController _owner;
|
[NonSerialized] private UXController _owner;
|
||||||
@ -41,6 +42,12 @@ namespace UnityEngine.UI
|
|||||||
set => _description = value;
|
set => _description = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int DefaultIndex
|
||||||
|
{
|
||||||
|
get => Mathf.Clamp(_defaultIndex, 0, Length - 1);
|
||||||
|
set => _defaultIndex = Mathf.Clamp(value, 0, Length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
public int SelectedIndex
|
public int SelectedIndex
|
||||||
{
|
{
|
||||||
get => _selectedIndex;
|
get => _selectedIndex;
|
||||||
@ -80,6 +87,15 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
private readonly Dictionary<string, int> _controllerIdMap = new Dictionary<string, int>();
|
private readonly Dictionary<string, int> _controllerIdMap = new Dictionary<string, int>();
|
||||||
private readonly Dictionary<string, int> _controllerNameMap = new Dictionary<string, int>();
|
private readonly Dictionary<string, int> _controllerNameMap = new Dictionary<string, int>();
|
||||||
|
private RuntimeBindingEntry[][] _runtimeEntriesByController = Array.Empty<RuntimeBindingEntry[]>();
|
||||||
|
private int[] _runtimeEntryCounts = Array.Empty<int>();
|
||||||
|
private bool _runtimeReady;
|
||||||
|
|
||||||
|
private struct RuntimeBindingEntry
|
||||||
|
{
|
||||||
|
public UXBinding Binding;
|
||||||
|
public int EntryIndex;
|
||||||
|
}
|
||||||
|
|
||||||
public IReadOnlyList<ControllerDefinition> Controllers
|
public IReadOnlyList<ControllerDefinition> Controllers
|
||||||
{
|
{
|
||||||
@ -111,6 +127,18 @@ namespace UnityEngine.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal bool TryGetControllerSlot(string controllerId, out int slot)
|
||||||
|
{
|
||||||
|
slot = -1;
|
||||||
|
if (string.IsNullOrEmpty(controllerId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureInitialized();
|
||||||
|
return _controllerIdMap.TryGetValue(controllerId, out slot);
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetControllerByName(string controllerName, out ControllerDefinition controller)
|
public bool TryGetControllerByName(string controllerName, out ControllerDefinition controller)
|
||||||
{
|
{
|
||||||
controller = null;
|
controller = null;
|
||||||
@ -190,7 +218,11 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
for (int i = 0; i < _controllers.Count; i++)
|
for (int i = 0; i < _controllers.Count; i++)
|
||||||
{
|
{
|
||||||
SetControllerIndexInternal(_controllers[i], 0, true);
|
ControllerDefinition controller = _controllers[i];
|
||||||
|
if (controller != null)
|
||||||
|
{
|
||||||
|
SetControllerIndexInternal(controller, controller.DefaultIndex, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,6 +241,12 @@ namespace UnityEngine.UI
|
|||||||
if (!_bindings.Contains(binding))
|
if (!_bindings.Contains(binding))
|
||||||
{
|
{
|
||||||
_bindings.Add(binding);
|
_bindings.Add(binding);
|
||||||
|
_runtimeReady = false;
|
||||||
|
if (Application.isPlaying)
|
||||||
|
{
|
||||||
|
RebuildRuntimeEntries();
|
||||||
|
ApplyCurrentStateToBinding(binding);
|
||||||
|
}
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
if (!Application.isPlaying)
|
if (!Application.isPlaying)
|
||||||
{
|
{
|
||||||
@ -226,6 +264,7 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
_bindings.Remove(binding);
|
_bindings.Remove(binding);
|
||||||
|
_runtimeReady = false;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
if (!Application.isPlaying)
|
if (!Application.isPlaying)
|
||||||
{
|
{
|
||||||
@ -252,10 +291,11 @@ namespace UnityEngine.UI
|
|||||||
{
|
{
|
||||||
if (_bindings[i] != null)
|
if (_bindings[i] != null)
|
||||||
{
|
{
|
||||||
_bindings[i].Initialize();
|
_bindings[i].RebuildRuntime(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RebuildRuntimeEntries();
|
||||||
ResetAllControllers();
|
ResetAllControllers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +318,9 @@ namespace UnityEngine.UI
|
|||||||
_controllerIdMap.Clear();
|
_controllerIdMap.Clear();
|
||||||
_controllerNameMap.Clear();
|
_controllerNameMap.Clear();
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
var usedNames = new HashSet<string>(StringComparer.Ordinal);
|
var usedNames = new HashSet<string>(StringComparer.Ordinal);
|
||||||
|
#endif
|
||||||
|
|
||||||
for (int i = 0; i < _controllers.Count; i++)
|
for (int i = 0; i < _controllers.Count; i++)
|
||||||
{
|
{
|
||||||
@ -291,7 +333,9 @@ namespace UnityEngine.UI
|
|||||||
controller.EnsureId();
|
controller.EnsureId();
|
||||||
controller.SetOwner(this);
|
controller.SetOwner(this);
|
||||||
controller.SetSelectedIndexSilently(Mathf.Clamp(controller.SelectedIndex, -1, controller.Length - 1));
|
controller.SetSelectedIndexSilently(Mathf.Clamp(controller.SelectedIndex, -1, controller.Length - 1));
|
||||||
|
controller.DefaultIndex = controller.DefaultIndex;
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
if (string.IsNullOrWhiteSpace(controller.Name))
|
if (string.IsNullOrWhiteSpace(controller.Name))
|
||||||
{
|
{
|
||||||
controller.Name = $"Controller{i + 1}";
|
controller.Name = $"Controller{i + 1}";
|
||||||
@ -302,10 +346,13 @@ namespace UnityEngine.UI
|
|||||||
controller.Name = $"{controller.Name}_{i + 1}";
|
controller.Name = $"{controller.Name}_{i + 1}";
|
||||||
usedNames.Add(controller.Name);
|
usedNames.Add(controller.Name);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
_controllerIdMap[controller.Id] = i;
|
_controllerIdMap[controller.Id] = i;
|
||||||
_controllerNameMap[controller.Name] = i;
|
_controllerNameMap[controller.Name] = i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_runtimeReady = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CleanupBindings()
|
private void CleanupBindings()
|
||||||
@ -333,18 +380,112 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
controller.SetSelectedIndexSilently(selectedIndex);
|
controller.SetSelectedIndexSilently(selectedIndex);
|
||||||
NotifyBindings(controller.Id, selectedIndex);
|
if (TryGetControllerSlot(controller.Id, out int slot))
|
||||||
|
{
|
||||||
|
NotifyBindings(slot, selectedIndex);
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NotifyBindings(string controllerId, int selectedIndex)
|
private void NotifyBindings(int controllerSlot, int selectedIndex)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _bindings.Count; i++)
|
if (!_runtimeReady)
|
||||||
{
|
{
|
||||||
UXBinding binding = _bindings[i];
|
RebuildRuntimeEntries();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((uint)controllerSlot >= (uint)_runtimeEntriesByController.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RuntimeBindingEntry[] entries = _runtimeEntriesByController[controllerSlot];
|
||||||
|
int count = _runtimeEntryCounts[controllerSlot];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
UXBinding binding = entries[i].Binding;
|
||||||
if (binding != null)
|
if (binding != null)
|
||||||
{
|
{
|
||||||
binding.OnControllerChanged(controllerId, selectedIndex);
|
binding.ApplyRuntimeEntry(entries[i].EntryIndex, selectedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RebuildRuntimeEntries()
|
||||||
|
{
|
||||||
|
int controllerCount = _controllers.Count;
|
||||||
|
if (_runtimeEntriesByController.Length != controllerCount)
|
||||||
|
{
|
||||||
|
_runtimeEntriesByController = new RuntimeBindingEntry[controllerCount][];
|
||||||
|
_runtimeEntryCounts = new int[controllerCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < controllerCount; i++)
|
||||||
|
{
|
||||||
|
_runtimeEntryCounts[i] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int bindingIndex = 0; bindingIndex < _bindings.Count; bindingIndex++)
|
||||||
|
{
|
||||||
|
UXBinding binding = _bindings[bindingIndex];
|
||||||
|
if (binding == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.RebuildRuntime(this);
|
||||||
|
|
||||||
|
for (int entryIndex = 0; entryIndex < binding.RuntimeEntryCount; entryIndex++)
|
||||||
|
{
|
||||||
|
int controllerSlot = binding.GetRuntimeControllerSlot(entryIndex);
|
||||||
|
if ((uint)controllerSlot >= (uint)controllerCount || !binding.IsRuntimeEntrySupported(entryIndex))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nextIndex = _runtimeEntryCounts[controllerSlot];
|
||||||
|
RuntimeBindingEntry[] entries = _runtimeEntriesByController[controllerSlot];
|
||||||
|
if (entries == null || nextIndex >= entries.Length)
|
||||||
|
{
|
||||||
|
int nextLength = entries == null ? 4 : entries.Length << 1;
|
||||||
|
RuntimeBindingEntry[] nextEntries = new RuntimeBindingEntry[nextLength];
|
||||||
|
if (entries != null)
|
||||||
|
{
|
||||||
|
Array.Copy(entries, nextEntries, entries.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
entries = nextEntries;
|
||||||
|
_runtimeEntriesByController[controllerSlot] = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
entries[nextIndex].Binding = binding;
|
||||||
|
entries[nextIndex].EntryIndex = entryIndex;
|
||||||
|
_runtimeEntryCounts[controllerSlot] = nextIndex + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_runtimeReady = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyCurrentStateToBinding(UXBinding binding)
|
||||||
|
{
|
||||||
|
if (binding == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int entryIndex = 0; entryIndex < binding.RuntimeEntryCount; entryIndex++)
|
||||||
|
{
|
||||||
|
int controllerSlot = binding.GetRuntimeControllerSlot(entryIndex);
|
||||||
|
if ((uint)controllerSlot >= (uint)_controllers.Count)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ControllerDefinition controller = _controllers[controllerSlot];
|
||||||
|
if (controller != null)
|
||||||
|
{
|
||||||
|
binding.ApplyRuntimeEntry(entryIndex, controller.SelectedIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using UnityEngine.EventSystems;
|
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
namespace UnityEngine.UI
|
namespace UnityEngine.UI
|
||||||
{
|
{
|
||||||
@ -12,22 +9,34 @@ namespace UnityEngine.UI
|
|||||||
{
|
{
|
||||||
[SerializeField] private bool m_AllowSwitchOff = false;
|
[SerializeField] private bool m_AllowSwitchOff = false;
|
||||||
|
|
||||||
public bool allowSwitchOff
|
|
||||||
{
|
|
||||||
get { return m_AllowSwitchOff; }
|
|
||||||
set { m_AllowSwitchOff = value; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private List<UXToggle> m_Toggles = new List<UXToggle>();
|
private UXToggle[] m_Toggles = new UXToggle[0];
|
||||||
|
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
private UXToggle m_DefaultToggle;
|
private UXToggle m_DefaultToggle;
|
||||||
|
|
||||||
|
private int m_ToggleCount;
|
||||||
|
private UXToggle m_CurrentToggle;
|
||||||
|
private int m_CurrentIndex = -1;
|
||||||
|
|
||||||
|
public bool allowSwitchOff
|
||||||
|
{
|
||||||
|
get { return m_AllowSwitchOff; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_AllowSwitchOff = value;
|
||||||
|
EnsureValidState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public UXToggle defaultToggle
|
public UXToggle defaultToggle
|
||||||
{
|
{
|
||||||
get { return m_DefaultToggle; }
|
get { return m_DefaultToggle; }
|
||||||
set { m_DefaultToggle = value; EnsureValidState(); }
|
set
|
||||||
|
{
|
||||||
|
m_DefaultToggle = ContainsToggle(value) ? value : null;
|
||||||
|
EnsureValidState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected UXGroup()
|
protected UXGroup()
|
||||||
@ -46,157 +55,90 @@ namespace UnityEngine.UI
|
|||||||
base.OnEnable();
|
base.OnEnable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ValidateToggleIsInGroup(UXToggle toggle)
|
|
||||||
{
|
|
||||||
if (toggle == null || !m_Toggles.Contains(toggle))
|
|
||||||
throw new ArgumentException(string.Format("UXToggle {0} is not part of ToggleGroup {1}", new object[] { toggle, this }));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void NotifyToggleOn(UXToggle toggle, bool sendCallback = true)
|
public void NotifyToggleOn(UXToggle toggle, bool sendCallback = true)
|
||||||
{
|
{
|
||||||
ValidateToggleIsInGroup(toggle);
|
EnsureStorage();
|
||||||
for (var i = 0; i < m_Toggles.Count; i++)
|
int index = IndexOfToggle(toggle);
|
||||||
|
if (index < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_CurrentToggle = toggle;
|
||||||
|
m_CurrentIndex = index;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_ToggleCount; i++)
|
||||||
{
|
{
|
||||||
if (m_Toggles[i] == toggle)
|
UXToggle item = m_Toggles[i];
|
||||||
|
if (item == null || item == toggle)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (sendCallback)
|
if (sendCallback)
|
||||||
m_Toggles[i].isOn = false;
|
item.isOn = false;
|
||||||
else
|
else
|
||||||
m_Toggles[i].SetIsOnWithoutNotify(false);
|
item.SetIsOnWithoutNotify(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnregisterToggle(UXToggle toggle)
|
public void UnregisterToggle(UXToggle toggle)
|
||||||
{
|
{
|
||||||
if (toggle == null)
|
EnsureStorage();
|
||||||
|
int index = IndexOfToggle(toggle);
|
||||||
|
if (index < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (m_Toggles.Contains(toggle))
|
RemoveAt(index);
|
||||||
m_Toggles.Remove(toggle);
|
|
||||||
|
|
||||||
if (m_DefaultToggle == toggle)
|
if (m_DefaultToggle == toggle)
|
||||||
{
|
|
||||||
m_DefaultToggle = null;
|
m_DefaultToggle = null;
|
||||||
|
|
||||||
|
if (m_CurrentToggle == toggle)
|
||||||
|
{
|
||||||
|
m_CurrentToggle = null;
|
||||||
|
m_CurrentIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureSingleSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterToggle(UXToggle toggle)
|
public void RegisterToggle(UXToggle toggle)
|
||||||
{
|
{
|
||||||
if (toggle == null)
|
EnsureStorage();
|
||||||
|
if (toggle == null || ContainsToggle(toggle))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!m_Toggles.Contains(toggle))
|
EnsureCapacity(m_ToggleCount + 1);
|
||||||
m_Toggles.Add(toggle);
|
m_Toggles[m_ToggleCount] = toggle;
|
||||||
|
m_ToggleCount++;
|
||||||
|
|
||||||
|
if (toggle.isOn)
|
||||||
if (!allowSwitchOff)
|
NotifyToggleOn(toggle);
|
||||||
{
|
else
|
||||||
|
EnsureSingleSelection();
|
||||||
var firstActive = GetFirstActiveToggle();
|
|
||||||
if (firstActive != null && firstActive != toggle && toggle.isOn)
|
|
||||||
{
|
|
||||||
toggle.SetIsOnWithoutNotify(false);
|
|
||||||
}
|
|
||||||
else if (firstActive == null)
|
|
||||||
{
|
|
||||||
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle))
|
|
||||||
{
|
|
||||||
var dt = m_DefaultToggle;
|
|
||||||
if (dt != null && dt != toggle)
|
|
||||||
{
|
|
||||||
dt.isOn = true;
|
|
||||||
NotifyToggleOn(dt);
|
|
||||||
}
|
|
||||||
else if (dt == toggle)
|
|
||||||
{
|
|
||||||
toggle.isOn = true;
|
|
||||||
NotifyToggleOn(toggle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public bool ContainsToggle(UXToggle toggle)
|
public bool ContainsToggle(UXToggle toggle)
|
||||||
{
|
{
|
||||||
return m_Toggles != null && m_Toggles.Contains(toggle);
|
EnsureStorage();
|
||||||
|
return IndexOfToggle(toggle) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnsureValidState()
|
public void EnsureValidState()
|
||||||
{
|
{
|
||||||
if (m_Toggles == null)
|
EnsureStorage();
|
||||||
m_Toggles = new List<UXToggle>();
|
CompactNulls();
|
||||||
|
SyncToggleGroups();
|
||||||
m_Toggles.RemoveAll(x => x == null);
|
EnsureDefaultToggle();
|
||||||
|
EnsureSingleSelection();
|
||||||
if (!allowSwitchOff && !AnyTogglesOn() && m_Toggles.Count != 0)
|
|
||||||
{
|
|
||||||
UXToggle toSelect = null;
|
|
||||||
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle))
|
|
||||||
{
|
|
||||||
toSelect = m_DefaultToggle;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
toSelect = m_Toggles[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toSelect != null)
|
|
||||||
{
|
|
||||||
toSelect.isOn = true;
|
|
||||||
NotifyToggleOn(toSelect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<UXToggle> activeToggles = ActiveToggles();
|
|
||||||
|
|
||||||
if (activeToggles.Count() > 1)
|
|
||||||
{
|
|
||||||
UXToggle firstActive = GetFirstActiveToggle();
|
|
||||||
|
|
||||||
foreach (UXToggle toggle in activeToggles)
|
|
||||||
{
|
|
||||||
if (toggle == firstActive)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggle.isOn = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < m_Toggles.Count; i++)
|
|
||||||
{
|
|
||||||
var t = m_Toggles[i];
|
|
||||||
if (t == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (t.group != this)
|
|
||||||
{
|
|
||||||
t.group = this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AnyTogglesOn()
|
public bool AnyTogglesOn()
|
||||||
{
|
{
|
||||||
return m_Toggles.Find(x => x != null && x.isOn) != null;
|
return FindFirstActiveIndex() >= 0;
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<UXToggle> ActiveToggles()
|
|
||||||
{
|
|
||||||
return m_Toggles.Where(x => x != null && x.isOn);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public UXToggle GetFirstActiveToggle()
|
public UXToggle GetFirstActiveToggle()
|
||||||
{
|
{
|
||||||
if (m_DefaultToggle != null && m_Toggles.Contains(m_DefaultToggle) && m_DefaultToggle.isOn)
|
int index = FindFirstActiveIndex();
|
||||||
return m_DefaultToggle;
|
return index >= 0 ? m_Toggles[index] : null;
|
||||||
|
|
||||||
IEnumerable<UXToggle> activeToggles = ActiveToggles();
|
|
||||||
return activeToggles.Count() > 0 ? activeToggles.First() : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAllTogglesOff(bool sendCallback = true)
|
public void SetAllTogglesOff(bool sendCallback = true)
|
||||||
@ -204,19 +146,20 @@ namespace UnityEngine.UI
|
|||||||
bool oldAllowSwitchOff = m_AllowSwitchOff;
|
bool oldAllowSwitchOff = m_AllowSwitchOff;
|
||||||
m_AllowSwitchOff = true;
|
m_AllowSwitchOff = true;
|
||||||
|
|
||||||
if (sendCallback)
|
for (int i = 0; i < m_ToggleCount; i++)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < m_Toggles.Count; i++)
|
UXToggle toggle = m_Toggles[i];
|
||||||
if (m_Toggles[i] != null)
|
if (toggle == null)
|
||||||
m_Toggles[i].isOn = false;
|
continue;
|
||||||
}
|
|
||||||
else
|
if (sendCallback)
|
||||||
{
|
toggle.isOn = false;
|
||||||
for (var i = 0; i < m_Toggles.Count; i++)
|
else
|
||||||
if (m_Toggles[i] != null)
|
toggle.SetIsOnWithoutNotify(false);
|
||||||
m_Toggles[i].SetIsOnWithoutNotify(false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_CurrentToggle = null;
|
||||||
|
m_CurrentIndex = -1;
|
||||||
m_AllowSwitchOff = oldAllowSwitchOff;
|
m_AllowSwitchOff = oldAllowSwitchOff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -225,47 +168,229 @@ namespace UnityEngine.UI
|
|||||||
SelectAdjacent(true);
|
SelectAdjacent(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Preview()
|
public void Previous()
|
||||||
{
|
{
|
||||||
SelectAdjacent(false);
|
SelectAdjacent(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SelectAdjacent(bool forward)
|
internal UXToggle GetToggleAt(int index)
|
||||||
{
|
{
|
||||||
if (m_Toggles == null || m_Toggles.Count == 0)
|
return index >= 0 && index < m_ToggleCount ? m_Toggles[index] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal int ToggleCount
|
||||||
|
{
|
||||||
|
get { return m_ToggleCount; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureStorage()
|
||||||
|
{
|
||||||
|
if (m_Toggles == null)
|
||||||
|
m_Toggles = new UXToggle[0];
|
||||||
|
|
||||||
|
if (m_ToggleCount == 0)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
for (int i = 0; i < m_Toggles.Length; i++)
|
||||||
|
{
|
||||||
|
if (m_Toggles[i] != null)
|
||||||
|
count = i + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ToggleCount = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_ToggleCount > m_Toggles.Length)
|
||||||
|
m_ToggleCount = m_Toggles.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompactNulls()
|
||||||
|
{
|
||||||
|
int write = 0;
|
||||||
|
for (int read = 0; read < m_Toggles.Length; read++)
|
||||||
|
{
|
||||||
|
UXToggle toggle = m_Toggles[read];
|
||||||
|
if (toggle == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_Toggles[write] = toggle;
|
||||||
|
write++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = write; i < m_Toggles.Length; i++)
|
||||||
|
m_Toggles[i] = null;
|
||||||
|
|
||||||
|
m_ToggleCount = write;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncToggleGroups()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_ToggleCount; i++)
|
||||||
|
{
|
||||||
|
UXToggle toggle = m_Toggles[i];
|
||||||
|
if (toggle != null && toggle.group != this)
|
||||||
|
toggle.SetToggleGroupInternal(this, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureDefaultToggle()
|
||||||
|
{
|
||||||
|
if (m_ToggleCount == 0)
|
||||||
|
{
|
||||||
|
m_DefaultToggle = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_DefaultToggle != null && IndexOfToggle(m_DefaultToggle) >= 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
UXToggle current = GetFirstActiveToggle();
|
if (!m_AllowSwitchOff)
|
||||||
int currentIndex = current != null ? m_Toggles.IndexOf(current) : -1;
|
m_DefaultToggle = m_Toggles[0];
|
||||||
|
else
|
||||||
|
m_DefaultToggle = null;
|
||||||
|
}
|
||||||
|
|
||||||
int idx = currentIndex;
|
private void EnsureSingleSelection()
|
||||||
if (idx == -1 && !forward)
|
{
|
||||||
idx = 0;
|
int selectedIndex = FindSelectedIndex();
|
||||||
|
if (selectedIndex < 0 && !m_AllowSwitchOff && m_ToggleCount > 0)
|
||||||
for (int step = 0; step < m_Toggles.Count; step++)
|
|
||||||
{
|
{
|
||||||
if (forward)
|
selectedIndex = GetDefaultIndex();
|
||||||
|
if (selectedIndex < 0)
|
||||||
|
selectedIndex = 0;
|
||||||
|
|
||||||
|
UXToggle toggle = m_Toggles[selectedIndex];
|
||||||
|
if (toggle != null)
|
||||||
|
toggle.isOn = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedIndex >= 0)
|
||||||
|
{
|
||||||
|
m_CurrentToggle = m_Toggles[selectedIndex];
|
||||||
|
m_CurrentIndex = selectedIndex;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_ToggleCount; i++)
|
||||||
{
|
{
|
||||||
idx = (idx + 1) % m_Toggles.Count;
|
UXToggle toggle = m_Toggles[i];
|
||||||
}
|
if (toggle != null && i != selectedIndex && toggle.isOn)
|
||||||
else
|
toggle.SetIsOnWithoutNotify(false);
|
||||||
{
|
|
||||||
idx = (idx - 1 + m_Toggles.Count) % m_Toggles.Count;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_CurrentToggle = null;
|
||||||
|
m_CurrentIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UXToggle t = m_Toggles[idx];
|
private int FindSelectedIndex()
|
||||||
if (t == null)
|
{
|
||||||
|
int defaultIndex = GetDefaultIndex();
|
||||||
|
if (defaultIndex >= 0 && m_Toggles[defaultIndex].isOn)
|
||||||
|
return defaultIndex;
|
||||||
|
|
||||||
|
return FindFirstActiveIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int FindFirstActiveIndex()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_ToggleCount; i++)
|
||||||
|
{
|
||||||
|
UXToggle toggle = m_Toggles[i];
|
||||||
|
if (toggle != null && toggle.isOn)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetDefaultIndex()
|
||||||
|
{
|
||||||
|
if (m_DefaultToggle == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
return IndexOfToggle(m_DefaultToggle);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int IndexOfToggle(UXToggle toggle)
|
||||||
|
{
|
||||||
|
if (toggle == null || m_Toggles == null)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_ToggleCount; i++)
|
||||||
|
{
|
||||||
|
if (m_Toggles[i] == toggle)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int capacity)
|
||||||
|
{
|
||||||
|
if (m_Toggles == null)
|
||||||
|
m_Toggles = new UXToggle[capacity < 4 ? 4 : capacity];
|
||||||
|
|
||||||
|
if (m_Toggles.Length >= capacity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int newCapacity = m_Toggles.Length == 0 ? 4 : m_Toggles.Length * 2;
|
||||||
|
while (newCapacity < capacity)
|
||||||
|
newCapacity *= 2;
|
||||||
|
|
||||||
|
UXToggle[] newToggles = new UXToggle[newCapacity];
|
||||||
|
for (int i = 0; i < m_ToggleCount; i++)
|
||||||
|
newToggles[i] = m_Toggles[i];
|
||||||
|
|
||||||
|
m_Toggles = newToggles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveAt(int index)
|
||||||
|
{
|
||||||
|
int lastIndex = m_ToggleCount - 1;
|
||||||
|
for (int i = index; i < lastIndex; i++)
|
||||||
|
m_Toggles[i] = m_Toggles[i + 1];
|
||||||
|
|
||||||
|
if (lastIndex >= 0)
|
||||||
|
m_Toggles[lastIndex] = null;
|
||||||
|
|
||||||
|
m_ToggleCount = lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectAdjacent(bool forward)
|
||||||
|
{
|
||||||
|
if (m_ToggleCount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int index = ResolveCurrentIndex();
|
||||||
|
if (index < 0)
|
||||||
|
index = forward ? -1 : 0;
|
||||||
|
|
||||||
|
for (int step = 0; step < m_ToggleCount; step++)
|
||||||
|
{
|
||||||
|
index = forward ? index + 1 : index - 1;
|
||||||
|
if (index >= m_ToggleCount)
|
||||||
|
index = 0;
|
||||||
|
else if (index < 0)
|
||||||
|
index = m_ToggleCount - 1;
|
||||||
|
|
||||||
|
UXToggle toggle = m_Toggles[index];
|
||||||
|
if (toggle == null || !toggle.IsActive() || !toggle.IsInteractable())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!t.IsActive())
|
toggle.isOn = true;
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!t.IsInteractable())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
t.isOn = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int ResolveCurrentIndex()
|
||||||
|
{
|
||||||
|
if (m_CurrentIndex >= 0 && m_CurrentIndex < m_ToggleCount && m_Toggles[m_CurrentIndex] == m_CurrentToggle)
|
||||||
|
return m_CurrentIndex;
|
||||||
|
|
||||||
|
m_CurrentIndex = FindFirstActiveIndex();
|
||||||
|
m_CurrentToggle = m_CurrentIndex >= 0 ? m_Toggles[m_CurrentIndex] : null;
|
||||||
|
return m_CurrentIndex;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ MonoImporter:
|
|||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
defaultReferences: []
|
defaultReferences: []
|
||||||
executionOrder: 0
|
executionOrder: 0
|
||||||
icon: {fileID: 2800000, guid: 42b2d97a2cb439b4395c6dca63357d89, type: 3}
|
icon: {instanceID: 0}
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
|||||||
@ -51,7 +51,7 @@ namespace UnityEngine.UI
|
|||||||
{
|
{
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
if (executing == CanvasUpdate.Prelayout)
|
if (executing == CanvasUpdate.Prelayout)
|
||||||
onValueChanged.Invoke(m_IsOn);
|
PlayEffect(true);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,13 +66,24 @@ namespace UnityEngine.UI
|
|||||||
protected override void OnDestroy()
|
protected override void OnDestroy()
|
||||||
{
|
{
|
||||||
if (m_Group != null)
|
if (m_Group != null)
|
||||||
m_Group.EnsureValidState();
|
m_Group.UnregisterToggle(this);
|
||||||
base.OnDestroy();
|
base.OnDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnDisable()
|
||||||
|
{
|
||||||
|
if (m_Group != null)
|
||||||
|
m_Group.UnregisterToggle(this);
|
||||||
|
|
||||||
|
base.OnDisable();
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnEnable()
|
protected override void OnEnable()
|
||||||
{
|
{
|
||||||
base.OnEnable();
|
base.OnEnable();
|
||||||
|
if (m_Group != null)
|
||||||
|
SetToggleGroup(m_Group, false);
|
||||||
|
|
||||||
PlayEffect(true);
|
PlayEffect(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +103,13 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void SetToggleGroup(UXGroup newGroup, bool setMemberValue)
|
internal void SetToggleGroupInternal(UXGroup newGroup, bool setMemberValue)
|
||||||
|
{
|
||||||
|
if (setMemberValue)
|
||||||
|
m_Group = newGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void SetToggleGroup(UXGroup newGroup, bool setMemberValue)
|
||||||
{
|
{
|
||||||
if (m_Group == newGroup)
|
if (m_Group == newGroup)
|
||||||
{
|
{
|
||||||
@ -155,11 +172,13 @@ namespace UnityEngine.UI
|
|||||||
base.DoStateTransition(state, instant);
|
base.DoStateTransition(state, instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Set(bool value, bool sendCallback = true)
|
protected virtual void Set(bool value, bool sendCallback = true)
|
||||||
{
|
{
|
||||||
if (m_IsOn == value)
|
if (m_IsOn == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
OnBeforeValueChanged(value);
|
||||||
|
|
||||||
m_IsOn = value;
|
m_IsOn = value;
|
||||||
if (m_Group != null && m_Group.isActiveAndEnabled && IsActive())
|
if (m_Group != null && m_Group.isActiveAndEnabled && IsActive())
|
||||||
{
|
{
|
||||||
@ -182,20 +201,33 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
var stateToApply = m_IsOn ? Selectable.SelectionState.Selected : currentSelectionState;
|
var stateToApply = m_IsOn ? Selectable.SelectionState.Selected : currentSelectionState;
|
||||||
DoStateTransition(stateToApply, instant);
|
DoStateTransition(stateToApply, instant);
|
||||||
|
OnAfterValueChanged(m_IsOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnBeforeValueChanged(bool value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private void PlayEffect(bool instant)
|
protected virtual void OnAfterValueChanged(bool value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void PlayEffect(bool instant)
|
||||||
{
|
{
|
||||||
if (graphic == null)
|
if (graphic == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
float alpha = m_IsOn ? 1f : 0f;
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
if (!Application.isPlaying)
|
if (!Application.isPlaying)
|
||||||
graphic.canvasRenderer.SetAlpha(m_IsOn ? 1f : 0f);
|
graphic.canvasRenderer.SetAlpha(alpha);
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
graphic.CrossFadeAlpha(m_IsOn ? 1f : 0f, instant ? 0f : 0.1f, true);
|
if (instant)
|
||||||
|
graphic.canvasRenderer.SetAlpha(alpha);
|
||||||
|
else
|
||||||
|
graphic.CrossFadeAlpha(alpha, 0.1f, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -230,6 +262,9 @@ namespace UnityEngine.UI
|
|||||||
public override void OnSelect(BaseEventData eventData)
|
public override void OnSelect(BaseEventData eventData)
|
||||||
{
|
{
|
||||||
base.OnSelect(eventData);
|
base.OnSelect(eventData);
|
||||||
|
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
||||||
|
UXNavigationRuntime.NotifySelection(gameObject);
|
||||||
|
#endif
|
||||||
if (eventData is PointerEventData)
|
if (eventData is PointerEventData)
|
||||||
return;
|
return;
|
||||||
PlayAudio(hoverAudioClip);
|
PlayAudio(hoverAudioClip);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#if INPUTSYSTEM_SUPPORT
|
#if INPUTSYSTEM_SUPPORT
|
||||||
|
using AlicizaX.UI.Runtime;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
|
|
||||||
@ -8,29 +9,42 @@ namespace UnityEngine.UI
|
|||||||
public sealed class HotkeyComponent : MonoBehaviour, IHotkeyTrigger
|
public sealed class HotkeyComponent : MonoBehaviour, IHotkeyTrigger
|
||||||
{
|
{
|
||||||
[SerializeField] private Component _component;
|
[SerializeField] private Component _component;
|
||||||
|
[SerializeField] private UIHolderObjectBase _holder;
|
||||||
[SerializeField] private InputActionReference _hotkeyAction;
|
[SerializeField] private InputActionReference _hotkeyAction;
|
||||||
[SerializeField] private EHotkeyPressType _hotkeyPressType = EHotkeyPressType.Performed;
|
[SerializeField] private EHotkeyPressType _hotkeyPressType = EHotkeyPressType.Performed;
|
||||||
|
|
||||||
|
private ISubmitHandler _submitHandler;
|
||||||
|
private BaseEventData _eventData;
|
||||||
|
private EventSystem _eventSystem;
|
||||||
|
|
||||||
public InputActionReference HotkeyAction
|
public InputActionReference HotkeyAction
|
||||||
{
|
{
|
||||||
get => _hotkeyAction;
|
get => _hotkeyAction;
|
||||||
set => _hotkeyAction = value;
|
set => _hotkeyAction = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
EHotkeyPressType IHotkeyTrigger.HotkeyPressType
|
public EHotkeyPressType HotkeyPressType => _hotkeyPressType;
|
||||||
{
|
public UIHolderObjectBase HotkeyHolder => _holder;
|
||||||
get => _hotkeyPressType;
|
|
||||||
set => _hotkeyPressType = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Reset()
|
private void Reset()
|
||||||
{
|
{
|
||||||
AutoAssignTarget();
|
AutoAssignTarget();
|
||||||
|
AutoAssignHolder();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
AutoAssignTarget();
|
||||||
|
AutoAssignHolder();
|
||||||
|
CacheTarget();
|
||||||
|
CacheEventData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
AutoAssignTarget();
|
AutoAssignTarget();
|
||||||
|
AutoAssignHolder();
|
||||||
|
CacheTarget();
|
||||||
((IHotkeyTrigger)this).BindHotKey();
|
((IHotkeyTrigger)this).BindHotKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,36 +53,54 @@ namespace UnityEngine.UI
|
|||||||
((IHotkeyTrigger)this).UnBindHotKey();
|
((IHotkeyTrigger)this).UnBindHotKey();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnApplicationFocus(bool hasFocus)
|
||||||
|
{
|
||||||
|
if (hasFocus)
|
||||||
|
{
|
||||||
|
CacheEventData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
{
|
{
|
||||||
((IHotkeyTrigger)this).UnBindHotKey();
|
((IHotkeyTrigger)this).UnBindHotKey();
|
||||||
|
_submitHandler = null;
|
||||||
|
_eventData = null;
|
||||||
|
_eventSystem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
AutoAssignTarget();
|
AutoAssignTarget();
|
||||||
|
AutoAssignHolder();
|
||||||
|
CacheTarget();
|
||||||
|
|
||||||
|
if (_component != null && _submitHandler == null)
|
||||||
|
{
|
||||||
|
_component = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void IHotkeyTrigger.HotkeyActionTrigger()
|
public void HotkeyActionTrigger()
|
||||||
{
|
{
|
||||||
if (!isActiveAndEnabled || _component == null)
|
if (!isActiveAndEnabled || _submitHandler == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_component is ISubmitHandler)
|
if (_eventData == null)
|
||||||
{
|
{
|
||||||
ExecuteEvents.Execute(
|
|
||||||
_component.gameObject,
|
|
||||||
new BaseEventData(EventSystem.current),
|
|
||||||
ExecuteEvents.submitHandler
|
|
||||||
);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.LogWarning($"{nameof(HotkeyComponent)} target must implement {nameof(ISubmitHandler)}: {_component.name}", this);
|
if (!ReferenceEquals(_eventSystem, EventSystem.current))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_submitHandler.OnSubmit(_eventData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AutoAssignTarget()
|
private void AutoAssignTarget()
|
||||||
@ -83,6 +115,27 @@ namespace UnityEngine.UI
|
|||||||
_component = submitHandler;
|
_component = submitHandler;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AutoAssignHolder()
|
||||||
|
{
|
||||||
|
if (_holder != null && _holder.IsValid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_holder = GetComponentInParent<UIHolderObjectBase>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CacheTarget()
|
||||||
|
{
|
||||||
|
_submitHandler = _component as ISubmitHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CacheEventData()
|
||||||
|
{
|
||||||
|
_eventSystem = EventSystem.current;
|
||||||
|
_eventData = new BaseEventData(_eventSystem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
#if INPUTSYSTEM_SUPPORT
|
#if INPUTSYSTEM_SUPPORT
|
||||||
|
using AlicizaX.UI.Runtime;
|
||||||
using UnityEngine.InputSystem;
|
using UnityEngine.InputSystem;
|
||||||
|
|
||||||
namespace UnityEngine.UI
|
namespace UnityEngine.UI
|
||||||
{
|
{
|
||||||
public interface IHotkeyTrigger
|
public interface IHotkeyTrigger
|
||||||
{
|
{
|
||||||
public InputActionReference HotkeyAction { get; }
|
InputActionReference HotkeyAction { get; }
|
||||||
internal EHotkeyPressType HotkeyPressType { get; set; }
|
EHotkeyPressType HotkeyPressType { get; }
|
||||||
internal void HotkeyActionTrigger();
|
UIHolderObjectBase HotkeyHolder { get; }
|
||||||
|
void HotkeyActionTrigger();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,3 +1,11 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 0aa77908962c48199d63710fa15b8c37
|
guid: 0aa77908962c48199d63710fa15b8c37
|
||||||
timeCreated: 1754555268
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,9 @@ namespace UnityEngine.UI
|
|||||||
public enum UXInputMode : byte
|
public enum UXInputMode : byte
|
||||||
{
|
{
|
||||||
Pointer = 0,
|
Pointer = 0,
|
||||||
Gamepad = 1
|
Keyboard = 1,
|
||||||
|
Gamepad = 2,
|
||||||
|
Touch = 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -7,10 +7,15 @@ namespace UnityEngine.UI
|
|||||||
{
|
{
|
||||||
internal sealed class UXInputModeService : MonoBehaviour
|
internal sealed class UXInputModeService : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
private const float StickThresholdSqr = 0.04f;
|
||||||
|
private const float AxisThreshold = 0.2f;
|
||||||
|
|
||||||
private static UXInputModeService _instance;
|
private static UXInputModeService _instance;
|
||||||
|
|
||||||
private InputAction _pointerAction;
|
private InputAction _pointerAction;
|
||||||
|
private InputAction _keyboardAction;
|
||||||
private InputAction _gamepadAction;
|
private InputAction _gamepadAction;
|
||||||
|
private InputAction _touchAction;
|
||||||
|
|
||||||
public static UXInputMode CurrentMode { get; private set; } = UXInputMode.Pointer;
|
public static UXInputMode CurrentMode { get; private set; } = UXInputMode.Pointer;
|
||||||
|
|
||||||
@ -29,7 +34,7 @@ namespace UnityEngine.UI
|
|||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var go = new GameObject("[UXInputModeService]");
|
GameObject go = new GameObject("[UXInputModeService]");
|
||||||
go.hideFlags = HideFlags.HideAndDontSave;
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
DontDestroyOnLoad(go);
|
DontDestroyOnLoad(go);
|
||||||
_instance = go.AddComponent<UXInputModeService>();
|
_instance = go.AddComponent<UXInputModeService>();
|
||||||
@ -47,16 +52,17 @@ namespace UnityEngine.UI
|
|||||||
_instance = this;
|
_instance = this;
|
||||||
DontDestroyOnLoad(gameObject);
|
DontDestroyOnLoad(gameObject);
|
||||||
hideFlags = HideFlags.HideAndDontSave;
|
hideFlags = HideFlags.HideAndDontSave;
|
||||||
|
CreateActions();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
CreateActions();
|
SetActionsEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void OnDisable()
|
||||||
{
|
{
|
||||||
DisposeActions();
|
SetActionsEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDestroy()
|
private void OnDestroy()
|
||||||
@ -70,26 +76,63 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
private void CreateActions()
|
private void CreateActions()
|
||||||
{
|
{
|
||||||
if (_pointerAction != null || _gamepadAction != null)
|
if (_pointerAction != null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_pointerAction = new InputAction("UXPointerInput", InputActionType.PassThrough);
|
_pointerAction = new InputAction("UXPointerInput", InputActionType.PassThrough);
|
||||||
_pointerAction.AddBinding("<Keyboard>/anyKey");
|
|
||||||
_pointerAction.AddBinding("<Mouse>/delta");
|
_pointerAction.AddBinding("<Mouse>/delta");
|
||||||
_pointerAction.AddBinding("<Mouse>/scroll");
|
_pointerAction.AddBinding("<Mouse>/scroll");
|
||||||
_pointerAction.AddBinding("<Mouse>/leftButton");
|
_pointerAction.AddBinding("<Mouse>/leftButton");
|
||||||
_pointerAction.AddBinding("<Mouse>/rightButton");
|
_pointerAction.AddBinding("<Mouse>/rightButton");
|
||||||
_pointerAction.AddBinding("<Mouse>/middleButton");
|
_pointerAction.AddBinding("<Mouse>/middleButton");
|
||||||
_pointerAction.performed += OnPointerInput;
|
_pointerAction.performed += OnPointerInput;
|
||||||
_pointerAction.Enable();
|
|
||||||
|
_keyboardAction = new InputAction("UXKeyboardInput", InputActionType.PassThrough);
|
||||||
|
_keyboardAction.AddBinding("<Keyboard>/anyKey");
|
||||||
|
_keyboardAction.performed += OnKeyboardInput;
|
||||||
|
|
||||||
_gamepadAction = new InputAction("UXGamepadInput", InputActionType.PassThrough);
|
_gamepadAction = new InputAction("UXGamepadInput", InputActionType.PassThrough);
|
||||||
_gamepadAction.AddBinding("<Gamepad>/*");
|
_gamepadAction.AddBinding("<Gamepad>/buttonSouth");
|
||||||
_gamepadAction.AddBinding("<Joystick>/*");
|
_gamepadAction.AddBinding("<Gamepad>/buttonNorth");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/buttonEast");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/buttonWest");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/startButton");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/selectButton");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/leftShoulder");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/rightShoulder");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/dpad");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/leftStick");
|
||||||
|
_gamepadAction.AddBinding("<Gamepad>/rightStick");
|
||||||
_gamepadAction.performed += OnGamepadInput;
|
_gamepadAction.performed += OnGamepadInput;
|
||||||
_gamepadAction.Enable();
|
|
||||||
|
_touchAction = new InputAction("UXTouchInput", InputActionType.PassThrough);
|
||||||
|
_touchAction.AddBinding("<Touchscreen>/primaryTouch/press");
|
||||||
|
_touchAction.AddBinding("<Touchscreen>/primaryTouch/delta");
|
||||||
|
_touchAction.performed += OnTouchInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetActionsEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
if (_pointerAction == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
_pointerAction.Enable();
|
||||||
|
_keyboardAction.Enable();
|
||||||
|
_gamepadAction.Enable();
|
||||||
|
_touchAction.Enable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pointerAction.Disable();
|
||||||
|
_keyboardAction.Disable();
|
||||||
|
_gamepadAction.Disable();
|
||||||
|
_touchAction.Disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeActions()
|
private void DisposeActions()
|
||||||
@ -97,38 +140,62 @@ namespace UnityEngine.UI
|
|||||||
if (_pointerAction != null)
|
if (_pointerAction != null)
|
||||||
{
|
{
|
||||||
_pointerAction.performed -= OnPointerInput;
|
_pointerAction.performed -= OnPointerInput;
|
||||||
_pointerAction.Disable();
|
|
||||||
_pointerAction.Dispose();
|
_pointerAction.Dispose();
|
||||||
_pointerAction = null;
|
_pointerAction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_keyboardAction != null)
|
||||||
|
{
|
||||||
|
_keyboardAction.performed -= OnKeyboardInput;
|
||||||
|
_keyboardAction.Dispose();
|
||||||
|
_keyboardAction = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (_gamepadAction != null)
|
if (_gamepadAction != null)
|
||||||
{
|
{
|
||||||
_gamepadAction.performed -= OnGamepadInput;
|
_gamepadAction.performed -= OnGamepadInput;
|
||||||
_gamepadAction.Disable();
|
|
||||||
_gamepadAction.Dispose();
|
_gamepadAction.Dispose();
|
||||||
_gamepadAction = null;
|
_gamepadAction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_touchAction != null)
|
||||||
|
{
|
||||||
|
_touchAction.performed -= OnTouchInput;
|
||||||
|
_touchAction.Dispose();
|
||||||
|
_touchAction = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnPointerInput(InputAction.CallbackContext context)
|
private static void OnPointerInput(InputAction.CallbackContext context)
|
||||||
{
|
{
|
||||||
if (!IsInputMeaningful(context.control))
|
if (IsInputMeaningful(context.control))
|
||||||
{
|
{
|
||||||
return;
|
SetMode(UXInputMode.Pointer);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SetMode(UXInputMode.Pointer);
|
private static void OnKeyboardInput(InputAction.CallbackContext context)
|
||||||
|
{
|
||||||
|
if (IsInputMeaningful(context.control))
|
||||||
|
{
|
||||||
|
SetMode(UXInputMode.Keyboard);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnGamepadInput(InputAction.CallbackContext context)
|
private static void OnGamepadInput(InputAction.CallbackContext context)
|
||||||
{
|
{
|
||||||
if (!IsInputMeaningful(context.control))
|
if (IsInputMeaningful(context.control))
|
||||||
{
|
{
|
||||||
return;
|
SetMode(UXInputMode.Gamepad);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SetMode(UXInputMode.Gamepad);
|
private static void OnTouchInput(InputAction.CallbackContext context)
|
||||||
|
{
|
||||||
|
if (IsInputMeaningful(context.control))
|
||||||
|
{
|
||||||
|
SetMode(UXInputMode.Touch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsInputMeaningful(InputControl control)
|
private static bool IsInputMeaningful(InputControl control)
|
||||||
@ -143,11 +210,11 @@ namespace UnityEngine.UI
|
|||||||
case ButtonControl button:
|
case ButtonControl button:
|
||||||
return button.IsPressed();
|
return button.IsPressed();
|
||||||
case StickControl stick:
|
case StickControl stick:
|
||||||
return stick.ReadValue().sqrMagnitude >= 0.04f;
|
return stick.ReadValue().sqrMagnitude >= StickThresholdSqr;
|
||||||
case Vector2Control vector2:
|
case Vector2Control vector2:
|
||||||
return vector2.ReadValue().sqrMagnitude >= 0.04f;
|
return vector2.ReadValue().sqrMagnitude >= StickThresholdSqr;
|
||||||
case AxisControl axis:
|
case AxisControl axis:
|
||||||
return Mathf.Abs(axis.ReadValue()) >= 0.2f;
|
return Mathf.Abs(axis.ReadValue()) >= AxisThreshold;
|
||||||
default:
|
default:
|
||||||
return !control.noisy;
|
return !control.noisy;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +0,0 @@
|
|||||||
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
|
||||||
namespace UnityEngine.UI
|
|
||||||
{
|
|
||||||
[DisallowMultipleComponent]
|
|
||||||
internal sealed class UXNavigationLayerWatcher : MonoBehaviour
|
|
||||||
{
|
|
||||||
private UXNavigationRuntime _runtime;
|
|
||||||
|
|
||||||
internal void Initialize(UXNavigationRuntime runtime)
|
|
||||||
{
|
|
||||||
_runtime = runtime;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTransformChildrenChanged()
|
|
||||||
{
|
|
||||||
_runtime?.MarkDiscoveryDirty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
@ -1,35 +1,42 @@
|
|||||||
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
||||||
using System.Collections.Generic;
|
|
||||||
using AlicizaX;
|
using AlicizaX;
|
||||||
using AlicizaX.UI.Runtime;
|
using AlicizaX.UI.Runtime;
|
||||||
using UnityEngine.EventSystems;
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
namespace UnityEngine.UI
|
namespace UnityEngine.UI
|
||||||
{
|
{
|
||||||
internal sealed class UXNavigationRuntime : MonoBehaviour
|
public interface IUXNavigationCursorPolicy
|
||||||
{
|
{
|
||||||
|
void OnInputModeChanged(UXInputMode mode, UXNavigationScope topScope);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UXNavigationRuntime : MonoBehaviour
|
||||||
|
{
|
||||||
|
private const int InitialScopeCapacity = 64;
|
||||||
|
private const int InvalidIndex = -1;
|
||||||
|
|
||||||
private static UXNavigationRuntime _instance;
|
private static UXNavigationRuntime _instance;
|
||||||
private static readonly string CacheLayerName = $"Layer{(int)UILayer.All}-{UILayer.All}";
|
private static IUXNavigationCursorPolicy _cursorPolicy;
|
||||||
|
|
||||||
private readonly HashSet<UXNavigationScope> _scopeSet = new();
|
private UXNavigationScope[] _scopes = new UXNavigationScope[InitialScopeCapacity];
|
||||||
private readonly List<UXNavigationScope> _scopes = new(32);
|
private int[] _freeIndices = new int[InitialScopeCapacity];
|
||||||
private readonly HashSet<Transform> _interactiveLayerRoots = new();
|
private int _freeCount;
|
||||||
private readonly List<UIHolderObjectBase> _holderBuffer = new(32);
|
private int _scopeCount;
|
||||||
|
private int _scopeCapacityHighWater;
|
||||||
|
|
||||||
private Transform _uiCanvasRoot;
|
|
||||||
private UXNavigationScope _topScope;
|
private UXNavigationScope _topScope;
|
||||||
private GameObject _lastObservedSelection;
|
|
||||||
private bool _discoveryDirty = true;
|
|
||||||
private ulong _activationSerial;
|
private ulong _activationSerial;
|
||||||
private bool _missingEventSystemLogged;
|
private bool _stateDirty = true;
|
||||||
|
|
||||||
private bool _topScopeDirty = true;
|
|
||||||
private bool _suppressionDirty = true;
|
private bool _suppressionDirty = true;
|
||||||
|
|
||||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||||||
private static void Bootstrap()
|
private static void Bootstrap()
|
||||||
{
|
{
|
||||||
if (!AppServices.TryGetApp<IUIService>(out var uiService) || uiService == null) return;
|
if (!AppServices.TryGetApp<IUIService>(out var uiService) || uiService == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EnsureInstance();
|
EnsureInstance();
|
||||||
UXInputModeService.EnsureInstance();
|
UXInputModeService.EnsureInstance();
|
||||||
}
|
}
|
||||||
@ -41,7 +48,7 @@ namespace UnityEngine.UI
|
|||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
var go = new GameObject("[UXNavigationRuntime]");
|
GameObject go = new GameObject("[UXNavigationRuntime]");
|
||||||
go.hideFlags = HideFlags.HideAndDontSave;
|
go.hideFlags = HideFlags.HideAndDontSave;
|
||||||
DontDestroyOnLoad(go);
|
DontDestroyOnLoad(go);
|
||||||
_instance = go.AddComponent<UXNavigationRuntime>();
|
_instance = go.AddComponent<UXNavigationRuntime>();
|
||||||
@ -54,14 +61,33 @@ namespace UnityEngine.UI
|
|||||||
return runtime != null;
|
return runtime != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static bool IsHolderWithinTopScope(UIHolderObjectBase holder)
|
public static void SetCursorPolicy(IUXNavigationCursorPolicy cursorPolicy)
|
||||||
|
{
|
||||||
|
_cursorPolicy = cursorPolicy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void NotifySelection(GameObject selectedObject)
|
||||||
|
{
|
||||||
|
if (_instance != null)
|
||||||
|
{
|
||||||
|
_instance.RecordSelection(selectedObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsHolderWithinTopScope(UIHolderObjectBase holder)
|
||||||
{
|
{
|
||||||
if (_instance == null || _instance._topScope == null || holder == null)
|
if (_instance == null || _instance._topScope == null || holder == null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _instance.IsHolderOwnedByScope(holder, _instance._topScope);
|
UXNavigationScope scope = holder.GetComponent<UXNavigationScope>();
|
||||||
|
if (scope == null)
|
||||||
|
{
|
||||||
|
scope = holder.GetComponentInParent<UXNavigationScope>(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scope == _instance._topScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Awake()
|
private void Awake()
|
||||||
@ -98,19 +124,42 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
internal void RegisterScope(UXNavigationScope scope)
|
internal void RegisterScope(UXNavigationScope scope)
|
||||||
{
|
{
|
||||||
if (scope == null || !_scopeSet.Add(scope))
|
if (scope == null || scope.RuntimeIndex != InvalidIndex)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_scopes.Add(scope);
|
int index;
|
||||||
_topScopeDirty = true;
|
if (_freeCount > 0)
|
||||||
_suppressionDirty = true;
|
{
|
||||||
|
index = _freeIndices[--_freeCount];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_scopeCapacityHighWater >= _scopes.Length)
|
||||||
|
{
|
||||||
|
ReportCapacityExceeded("UXNavigationRuntime scope capacity exceeded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
index = _scopeCapacityHighWater++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scopes[index] = scope;
|
||||||
|
scope.RuntimeIndex = index;
|
||||||
|
_scopeCount++;
|
||||||
|
MarkStateDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void UnregisterScope(UXNavigationScope scope)
|
internal void UnregisterScope(UXNavigationScope scope)
|
||||||
{
|
{
|
||||||
if (scope == null || !_scopeSet.Remove(scope))
|
if (scope == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = scope.RuntimeIndex;
|
||||||
|
if (index < 0 || index >= _scopes.Length || _scopes[index] != scope)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -121,42 +170,46 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
scope.IsAvailable = false;
|
scope.IsAvailable = false;
|
||||||
|
scope.WasAvailable = false;
|
||||||
scope.SetNavigationSuppressed(false);
|
scope.SetNavigationSuppressed(false);
|
||||||
|
scope.RuntimeIndex = InvalidIndex;
|
||||||
|
_scopes[index] = null;
|
||||||
|
_freeIndices[_freeCount++] = index;
|
||||||
|
_scopeCount--;
|
||||||
|
MarkStateDirty();
|
||||||
|
}
|
||||||
|
|
||||||
int idx = _scopes.IndexOf(scope);
|
internal void MarkStateDirty()
|
||||||
if (idx >= 0)
|
{
|
||||||
{
|
_stateDirty = true;
|
||||||
int last = _scopes.Count - 1;
|
|
||||||
_scopes[idx] = _scopes[last];
|
|
||||||
_scopes.RemoveAt(last);
|
|
||||||
}
|
|
||||||
|
|
||||||
_topScopeDirty = true;
|
|
||||||
_suppressionDirty = true;
|
_suppressionDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void MarkDiscoveryDirty()
|
internal void MarkSuppressionDirty()
|
||||||
{
|
{
|
||||||
_discoveryDirty = true;
|
_suppressionDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
internal void InvalidateSkipCaches()
|
||||||
{
|
{
|
||||||
TryBindUIRoot();
|
for (int i = 0; i < _scopeCapacityHighWater; i++)
|
||||||
if (_uiCanvasRoot == null)
|
|
||||||
{
|
{
|
||||||
return;
|
UXNavigationScope scope = _scopes[i];
|
||||||
|
if (scope != null)
|
||||||
|
{
|
||||||
|
scope.InvalidateSkipCacheOnly();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_discoveryDirty)
|
MarkStateDirty();
|
||||||
{
|
}
|
||||||
DiscoverScopes();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_topScopeDirty)
|
private void FlushStateIfDirty()
|
||||||
|
{
|
||||||
|
if (_stateDirty)
|
||||||
{
|
{
|
||||||
UXNavigationScope newTopScope = FindTopScope();
|
UXNavigationScope newTopScope = FindTopScope();
|
||||||
_topScopeDirty = false;
|
_stateDirty = false;
|
||||||
if (!ReferenceEquals(_topScope, newTopScope))
|
if (!ReferenceEquals(_topScope, newTopScope))
|
||||||
{
|
{
|
||||||
_topScope = newTopScope;
|
_topScope = newTopScope;
|
||||||
@ -170,122 +223,16 @@ namespace UnityEngine.UI
|
|||||||
_suppressionDirty = false;
|
_suppressionDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UXInputModeService.CurrentMode == UXInputMode.Gamepad)
|
if (UXInputModeService.CurrentMode == UXInputMode.Gamepad || UXInputModeService.CurrentMode == UXInputMode.Keyboard)
|
||||||
{
|
{
|
||||||
EnsureGamepadSelection();
|
EnsureNavigationSelection();
|
||||||
if (_topScope != null)
|
|
||||||
{
|
|
||||||
Cursor.visible = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackSelection();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void TryBindUIRoot()
|
|
||||||
{
|
|
||||||
if (_uiCanvasRoot != null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IUIService uiModule = AppServices.RequireApp<IUIService>();
|
|
||||||
if (uiModule?.UICanvasRoot == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_uiCanvasRoot = uiModule.UICanvasRoot;
|
|
||||||
EnsureWatcher(_uiCanvasRoot);
|
|
||||||
CacheInteractiveLayers();
|
|
||||||
DiscoverScopes();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CacheInteractiveLayers()
|
|
||||||
{
|
|
||||||
_interactiveLayerRoots.Clear();
|
|
||||||
if (_uiCanvasRoot == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureWatcher(_uiCanvasRoot);
|
|
||||||
for (int i = 0; i < _uiCanvasRoot.childCount; i++)
|
|
||||||
{
|
|
||||||
Transform child = _uiCanvasRoot.GetChild(i);
|
|
||||||
if (child == null || child.name == CacheLayerName)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_interactiveLayerRoots.Add(child);
|
|
||||||
EnsureWatcher(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void DiscoverScopes()
|
|
||||||
{
|
|
||||||
_discoveryDirty = false;
|
|
||||||
CacheInteractiveLayers();
|
|
||||||
bool addedScope = false;
|
|
||||||
|
|
||||||
foreach (Transform layerRoot in _interactiveLayerRoots)
|
|
||||||
{
|
|
||||||
if (layerRoot == null || IsNavigationSkipped(layerRoot))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
_holderBuffer.Clear();
|
|
||||||
layerRoot.GetComponentsInChildren(true, _holderBuffer);
|
|
||||||
for (int i = 0; i < _holderBuffer.Count; i++)
|
|
||||||
{
|
|
||||||
UIHolderObjectBase holder = _holderBuffer[i];
|
|
||||||
if (holder == null
|
|
||||||
|| holder.GetComponent<UXNavigationScope>() != null
|
|
||||||
|| IsNavigationSkipped(holder.transform))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
holder.gameObject.AddComponent<UXNavigationScope>();
|
|
||||||
addedScope = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_holderBuffer.Clear();
|
|
||||||
|
|
||||||
if (addedScope)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _scopes.Count; i++)
|
|
||||||
{
|
|
||||||
_scopes[i]?.InvalidateSelectableCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
_topScopeDirty = true;
|
|
||||||
_suppressionDirty = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void EnsureWatcher(Transform target)
|
|
||||||
{
|
|
||||||
if (target == null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!target.TryGetComponent(out UXNavigationLayerWatcher watcher))
|
|
||||||
{
|
|
||||||
watcher = target.gameObject.AddComponent<UXNavigationLayerWatcher>();
|
|
||||||
}
|
|
||||||
|
|
||||||
watcher.Initialize(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private UXNavigationScope FindTopScope()
|
private UXNavigationScope FindTopScope()
|
||||||
{
|
{
|
||||||
UXNavigationScope bestScope = null;
|
UXNavigationScope bestScope = null;
|
||||||
for (int i = 0; i < _scopes.Count; i++)
|
for (int i = 0; i < _scopeCapacityHighWater; i++)
|
||||||
{
|
{
|
||||||
UXNavigationScope scope = _scopes[i];
|
UXNavigationScope scope = _scopes[i];
|
||||||
if (scope == null)
|
if (scope == null)
|
||||||
@ -306,12 +253,7 @@ namespace UnityEngine.UI
|
|||||||
_suppressionDirty = true;
|
_suppressionDirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!available)
|
if (available && (bestScope == null || IsHigherPriority(scope, bestScope)))
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bestScope == null || CompareScopePriority(scope, bestScope) < 0)
|
|
||||||
{
|
{
|
||||||
bestScope = scope;
|
bestScope = scope;
|
||||||
}
|
}
|
||||||
@ -320,7 +262,7 @@ namespace UnityEngine.UI
|
|||||||
return bestScope;
|
return bestScope;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsScopeAvailable(UXNavigationScope scope)
|
private static bool IsScopeAvailable(UXNavigationScope scope)
|
||||||
{
|
{
|
||||||
if (scope == null || !scope.isActiveAndEnabled || !scope.gameObject.activeInHierarchy)
|
if (scope == null || !scope.isActiveAndEnabled || !scope.gameObject.activeInHierarchy)
|
||||||
{
|
{
|
||||||
@ -328,64 +270,15 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
Canvas canvas = scope.Canvas;
|
Canvas canvas = scope.Canvas;
|
||||||
if (canvas == null || canvas.gameObject.layer != UIComponent.UIShowLayer)
|
return canvas != null
|
||||||
{
|
&& canvas.gameObject.layer == UIComponent.UIShowLayer
|
||||||
return false;
|
&& !scope.IsNavigationSkipped
|
||||||
}
|
&& scope.HasAvailableSelectable();
|
||||||
|
|
||||||
return !scope.IsNavigationSkipped
|
|
||||||
&& scope.HasAvailableSelectable()
|
|
||||||
&& TryGetInteractiveLayerRoot(scope.transform, out _);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保留静态方法用于 DiscoverScopes 中对 LayerRoot / Holder 节点的检测
|
|
||||||
// 这些节点无 UXNavigationScope,调用频次低(仅在 dirty 时),无需缓存
|
|
||||||
private static bool IsNavigationSkipped(Transform current)
|
|
||||||
{
|
|
||||||
return current != null && current.GetComponentInParent<UXNavigationSkip>(true) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool TryGetInteractiveLayerRoot(Transform current, out Transform layerRoot)
|
|
||||||
{
|
|
||||||
layerRoot = null;
|
|
||||||
if (current == null || _uiCanvasRoot == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (current != null)
|
|
||||||
{
|
|
||||||
if (current.parent == _uiCanvasRoot)
|
|
||||||
{
|
|
||||||
layerRoot = current;
|
|
||||||
return _interactiveLayerRoots.Contains(current);
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsHolderOwnedByScope(UIHolderObjectBase holder, UXNavigationScope scope)
|
|
||||||
{
|
|
||||||
if (holder == null || scope == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
UXNavigationScope nearestScope = holder.GetComponent<UXNavigationScope>();
|
|
||||||
if (nearestScope == null)
|
|
||||||
{
|
|
||||||
nearestScope = holder.GetComponentInParent<UXNavigationScope>(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return nearestScope == scope;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ApplyScopeSuppression()
|
private void ApplyScopeSuppression()
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _scopes.Count; i++)
|
for (int i = 0; i < _scopeCapacityHighWater; i++)
|
||||||
{
|
{
|
||||||
UXNavigationScope scope = _scopes[i];
|
UXNavigationScope scope = _scopes[i];
|
||||||
if (scope == null)
|
if (scope == null)
|
||||||
@ -395,30 +288,17 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
bool suppress = scope.IsAvailable
|
bool suppress = scope.IsAvailable
|
||||||
&& _topScope != null
|
&& _topScope != null
|
||||||
&& !ReferenceEquals(scope, _topScope)
|
&& scope != _topScope
|
||||||
&& _topScope.BlockLowerScopes
|
&& _topScope.BlockLowerScopes
|
||||||
&& CompareScopePriority(_topScope, scope) < 0;
|
&& IsHigherPriority(_topScope, scope);
|
||||||
scope.SetNavigationSuppressed(suppress);
|
scope.SetNavigationSuppressed(suppress);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureGamepadSelection()
|
private void EnsureNavigationSelection()
|
||||||
{
|
{
|
||||||
EventSystem eventSystem = EventSystem.current;
|
EventSystem eventSystem = EventSystem.current;
|
||||||
if (eventSystem == null)
|
if (eventSystem == null || _topScope == null || !_topScope.RequireSelectionWhenGamepad)
|
||||||
{
|
|
||||||
if (!_missingEventSystemLogged)
|
|
||||||
{
|
|
||||||
Debug.LogWarning("UXNavigationRuntime requires an active EventSystem for gamepad navigation.");
|
|
||||||
_missingEventSystemLogged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_missingEventSystemLogged = false;
|
|
||||||
|
|
||||||
if (_topScope == null || !_topScope.RequireSelectionWhenGamepad)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -432,78 +312,60 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
Selectable preferred = _topScope.GetPreferredSelectable();
|
Selectable preferred = _topScope.GetPreferredSelectable();
|
||||||
eventSystem.SetSelectedGameObject(preferred != null ? preferred.gameObject : null);
|
eventSystem.SetSelectedGameObject(preferred != null ? preferred.gameObject : null);
|
||||||
_lastObservedSelection = eventSystem.currentSelectedGameObject;
|
GameObject selectedObject = eventSystem.currentSelectedGameObject;
|
||||||
if (_lastObservedSelection != null)
|
if (selectedObject != null)
|
||||||
{
|
{
|
||||||
_topScope.RecordSelection(_lastObservedSelection);
|
_topScope.RecordSelection(selectedObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrackSelection()
|
private void RecordSelection(GameObject selectedObject)
|
||||||
{
|
{
|
||||||
EventSystem eventSystem = EventSystem.current;
|
if (_stateDirty || _suppressionDirty)
|
||||||
if (eventSystem == null)
|
|
||||||
{
|
{
|
||||||
_lastObservedSelection = null;
|
FlushStateIfDirty();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GameObject currentSelected = eventSystem.currentSelectedGameObject;
|
if (_topScope != null && _topScope.IsSelectableOwnedAndValid(selectedObject))
|
||||||
if (ReferenceEquals(_lastObservedSelection, currentSelected))
|
|
||||||
{
|
{
|
||||||
return;
|
_topScope.RecordSelection(selectedObject);
|
||||||
}
|
|
||||||
|
|
||||||
_lastObservedSelection = currentSelected;
|
|
||||||
if (_topScope != null && _topScope.IsSelectableOwnedAndValid(currentSelected))
|
|
||||||
{
|
|
||||||
_topScope.RecordSelection(currentSelected);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnInputModeChanged(UXInputMode mode)
|
private void OnInputModeChanged(UXInputMode mode)
|
||||||
{
|
{
|
||||||
EventSystem eventSystem = EventSystem.current;
|
_cursorPolicy?.OnInputModeChanged(mode, _topScope);
|
||||||
if (mode == UXInputMode.Pointer)
|
if (mode == UXInputMode.Gamepad || mode == UXInputMode.Keyboard)
|
||||||
{
|
{
|
||||||
if (_topScope != null)
|
FlushStateIfDirty();
|
||||||
{
|
EnsureNavigationSelection();
|
||||||
Cursor.visible = true;
|
|
||||||
if (eventSystem != null)
|
|
||||||
{
|
|
||||||
eventSystem.SetSelectedGameObject(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastObservedSelection = null;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_topScope != null)
|
|
||||||
{
|
|
||||||
Cursor.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureGamepadSelection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int CompareScopePriority(UXNavigationScope left, UXNavigationScope right)
|
private static bool IsHigherPriority(UXNavigationScope left, UXNavigationScope right)
|
||||||
{
|
{
|
||||||
int leftOrder = left.Canvas != null ? left.Canvas.sortingOrder : int.MinValue;
|
int leftOrder = left.Canvas != null ? left.Canvas.sortingOrder : int.MinValue;
|
||||||
int rightOrder = right.Canvas != null ? right.Canvas.sortingOrder : int.MinValue;
|
int rightOrder = right.Canvas != null ? right.Canvas.sortingOrder : int.MinValue;
|
||||||
int orderCompare = rightOrder.CompareTo(leftOrder);
|
if (leftOrder != rightOrder)
|
||||||
if (orderCompare != 0)
|
|
||||||
{
|
{
|
||||||
return orderCompare;
|
return leftOrder > rightOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
int hierarchyCompare = right.GetHierarchyDepth().CompareTo(left.GetHierarchyDepth());
|
int leftDepth = left.GetHierarchyDepth();
|
||||||
if (hierarchyCompare != 0)
|
int rightDepth = right.GetHierarchyDepth();
|
||||||
|
if (leftDepth != rightDepth)
|
||||||
{
|
{
|
||||||
return hierarchyCompare;
|
return leftDepth > rightDepth;
|
||||||
}
|
}
|
||||||
|
|
||||||
return right.ActivationSerial.CompareTo(left.ActivationSerial);
|
return left.ActivationSerial > right.ActivationSerial;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReportCapacityExceeded(string message)
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||||
|
Debug.LogError(message);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,49 +1,52 @@
|
|||||||
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION
|
||||||
using System.Collections.Generic;
|
|
||||||
using AlicizaX.UI.Runtime;
|
using AlicizaX.UI.Runtime;
|
||||||
|
using UnityEngine.EventSystems;
|
||||||
|
|
||||||
namespace UnityEngine.UI
|
namespace UnityEngine.UI
|
||||||
{
|
{
|
||||||
[DisallowMultipleComponent]
|
[DisallowMultipleComponent]
|
||||||
[AddComponentMenu("UI/UX Navigation Scope")]
|
[AddComponentMenu("UI/UX Navigation Scope")]
|
||||||
public sealed class UXNavigationScope : MonoBehaviour
|
public sealed class UXNavigationScope : MonoBehaviour, ISelectHandler
|
||||||
{
|
{
|
||||||
|
private const int InvalidIndex = -1;
|
||||||
|
private const int DefaultRuntimeSelectableCapacity = 16;
|
||||||
|
|
||||||
[SerializeField, Header("默认选中控件")] private Selectable _defaultSelectable;
|
[SerializeField, Header("默认选中控件")] private Selectable _defaultSelectable;
|
||||||
|
[SerializeField, Header("编辑器烘焙导航控件")] private Selectable[] _bakedSelectables = System.Array.Empty<Selectable>();
|
||||||
[SerializeField, Header("显式导航控件列表")] private List<Selectable> _explicitSelectables = new();
|
[SerializeField, Header("动态控件容量")] private int _runtimeSelectableCapacity = DefaultRuntimeSelectableCapacity;
|
||||||
|
|
||||||
[SerializeField, Header("记住上次选中")] private bool _rememberLastSelection = true;
|
[SerializeField, Header("记住上次选中")] private bool _rememberLastSelection = true;
|
||||||
|
[SerializeField, Header("手柄/键盘模式必须有选中")] private bool _requireSelectionWhenGamepad = true;
|
||||||
[SerializeField, Header("手柄模式必须有选中")] private bool _requireSelectionWhenGamepad = true;
|
|
||||||
|
|
||||||
[SerializeField, Header("阻断下层导航域")] private bool _blockLowerScopes = true;
|
[SerializeField, Header("阻断下层导航域")] private bool _blockLowerScopes = true;
|
||||||
|
|
||||||
[SerializeField, Header("自动选中首个可用控件")] private bool _autoSelectFirstAvailable = true;
|
[SerializeField, Header("自动选中首个可用控件")] private bool _autoSelectFirstAvailable = true;
|
||||||
|
|
||||||
private readonly List<Selectable> _cachedSelectables = new(16);
|
private Selectable[] _runtimeSelectables;
|
||||||
private readonly HashSet<Selectable> _cachedSelectableSet = new();
|
private Navigation[] _bakedBaselineNavigation;
|
||||||
private readonly Dictionary<Selectable, Navigation> _baselineNavigation = new();
|
private Navigation[] _runtimeBaselineNavigation;
|
||||||
private readonly List<Selectable> _removeBuffer = new(8);
|
|
||||||
private readonly List<Selectable> _selectableScanBuffer = new(16);
|
|
||||||
|
|
||||||
private Canvas _cachedCanvas;
|
private Canvas _cachedCanvas;
|
||||||
private UIHolderObjectBase _cachedHolder;
|
private UIHolderObjectBase _cachedHolder;
|
||||||
private Selectable _lastSelected;
|
private Selectable _lastSelected;
|
||||||
private bool _cacheDirty = true;
|
|
||||||
private bool _navigationSuppressed;
|
private bool _navigationSuppressed;
|
||||||
private int _cachedHierarchyDepth = -1;
|
private int _cachedHierarchyDepth = -1;
|
||||||
|
|
||||||
private bool _cachedIsSkipped;
|
private bool _cachedIsSkipped;
|
||||||
private bool _isSkippedCacheValid;
|
private bool _isSkippedCacheValid;
|
||||||
|
private int _runtimeSelectableCount;
|
||||||
|
|
||||||
|
internal int RuntimeIndex { get; set; } = InvalidIndex;
|
||||||
internal ulong ActivationSerial { get; set; }
|
internal ulong ActivationSerial { get; set; }
|
||||||
internal bool IsAvailable { get; set; }
|
internal bool IsAvailable { get; set; }
|
||||||
internal bool WasAvailable { get; set; }
|
internal bool WasAvailable { get; set; }
|
||||||
|
public bool NavigationSuppressed => _navigationSuppressed;
|
||||||
|
internal int BakedSelectableCount => _bakedSelectables != null ? _bakedSelectables.Length : 0;
|
||||||
|
public int RuntimeSelectableCount => _runtimeSelectableCount;
|
||||||
|
|
||||||
public Selectable DefaultSelectable
|
public Selectable DefaultSelectable
|
||||||
{
|
{
|
||||||
get => _defaultSelectable;
|
get => _defaultSelectable;
|
||||||
set => _defaultSelectable = value;
|
set
|
||||||
|
{
|
||||||
|
_defaultSelectable = value;
|
||||||
|
MarkRuntimeStateDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RememberLastSelection => _rememberLastSelection;
|
public bool RememberLastSelection => _rememberLastSelection;
|
||||||
@ -59,6 +62,7 @@ namespace UnityEngine.UI
|
|||||||
_cachedIsSkipped = GetComponentInParent<UXNavigationSkip>(true) != null;
|
_cachedIsSkipped = GetComponentInParent<UXNavigationSkip>(true) != null;
|
||||||
_isSkippedCacheValid = true;
|
_isSkippedCacheValid = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _cachedIsSkipped;
|
return _cachedIsSkipped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -93,9 +97,16 @@ namespace UnityEngine.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
EnsureRuntimeBuffers();
|
||||||
|
CaptureAllBaselines();
|
||||||
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
_cacheDirty = true;
|
EnsureRuntimeBuffers();
|
||||||
|
CaptureAllBaselines();
|
||||||
UXNavigationRuntime.EnsureInstance().RegisterScope(this);
|
UXNavigationRuntime.EnsureInstance().RegisterScope(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,25 +126,108 @@ namespace UnityEngine.UI
|
|||||||
|
|
||||||
private void OnTransformChildrenChanged()
|
private void OnTransformChildrenChanged()
|
||||||
{
|
{
|
||||||
_cacheDirty = true;
|
MarkRuntimeStateDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnTransformParentChanged()
|
private void OnTransformParentChanged()
|
||||||
{
|
{
|
||||||
_cacheDirty = true;
|
|
||||||
_cachedCanvas = null;
|
_cachedCanvas = null;
|
||||||
_cachedHolder = null;
|
_cachedHolder = null;
|
||||||
_cachedHierarchyDepth = -1;
|
_cachedHierarchyDepth = -1;
|
||||||
_isSkippedCacheValid = false;
|
_isSkippedCacheValid = false;
|
||||||
|
MarkRuntimeStateDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
_cacheDirty = true;
|
if (_runtimeSelectableCapacity < 0)
|
||||||
|
{
|
||||||
|
_runtimeSelectableCapacity = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
public bool RegisterSelectable(Selectable selectable)
|
||||||
|
{
|
||||||
|
if (selectable == null || !Owns(selectable.gameObject) || ContainsSelectable(selectable))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureRuntimeBuffers();
|
||||||
|
if (_runtimeSelectableCount >= _runtimeSelectables.Length)
|
||||||
|
{
|
||||||
|
ReportCapacityExceeded();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_runtimeSelectables[_runtimeSelectableCount] = selectable;
|
||||||
|
_runtimeBaselineNavigation[_runtimeSelectableCount] = selectable.navigation;
|
||||||
|
_runtimeSelectableCount++;
|
||||||
|
if (_navigationSuppressed)
|
||||||
|
{
|
||||||
|
SetSelectableSuppressed(selectable, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkRuntimeStateDirty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool UnregisterSelectable(Selectable selectable)
|
||||||
|
{
|
||||||
|
if (selectable == null || _runtimeSelectables == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _runtimeSelectableCount; i++)
|
||||||
|
{
|
||||||
|
if (_runtimeSelectables[i] != selectable)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_navigationSuppressed)
|
||||||
|
{
|
||||||
|
selectable.navigation = _runtimeBaselineNavigation[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
int last = _runtimeSelectableCount - 1;
|
||||||
|
_runtimeSelectables[i] = _runtimeSelectables[last];
|
||||||
|
_runtimeBaselineNavigation[i] = _runtimeBaselineNavigation[last];
|
||||||
|
_runtimeSelectables[last] = null;
|
||||||
|
_runtimeBaselineNavigation[last] = default(Navigation);
|
||||||
|
_runtimeSelectableCount--;
|
||||||
|
if (_lastSelected == selectable)
|
||||||
|
{
|
||||||
|
_lastSelected = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
MarkRuntimeStateDirty();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InvalidateSelectableCache()
|
||||||
|
{
|
||||||
|
CaptureAllBaselines();
|
||||||
|
MarkRuntimeStateDirty();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnSelect(BaseEventData eventData)
|
||||||
|
{
|
||||||
|
GameObject selectedObject = eventData != null ? eventData.selectedObject : null;
|
||||||
|
UXNavigationRuntime.NotifySelection(selectedObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void InvalidateSkipCacheOnly()
|
||||||
|
{
|
||||||
|
_isSkippedCacheValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
internal int GetHierarchyDepth()
|
internal int GetHierarchyDepth()
|
||||||
{
|
{
|
||||||
if (_cachedHierarchyDepth >= 0)
|
if (_cachedHierarchyDepth >= 0)
|
||||||
@ -160,14 +254,12 @@ namespace UnityEngine.UI
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nearestScope = target.GetComponentInParent<UXNavigationScope>(true);
|
UXNavigationScope nearestScope = target.GetComponentInParent<UXNavigationScope>(true);
|
||||||
return nearestScope == this;
|
return nearestScope == this;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Selectable GetPreferredSelectable()
|
internal Selectable GetPreferredSelectable()
|
||||||
{
|
{
|
||||||
RefreshSelectableCache();
|
|
||||||
|
|
||||||
if (_rememberLastSelection && IsSelectableValid(_lastSelected))
|
if (_rememberLastSelection && IsSelectableValid(_lastSelected))
|
||||||
{
|
{
|
||||||
return _lastSelected;
|
return _lastSelected;
|
||||||
@ -183,36 +275,20 @@ namespace UnityEngine.UI
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < _cachedSelectables.Count; i++)
|
Selectable selectable = FirstUsable(_bakedSelectables, BakedSelectableCount);
|
||||||
|
if (selectable != null)
|
||||||
{
|
{
|
||||||
Selectable selectable = _cachedSelectables[i];
|
return selectable;
|
||||||
if (IsSelectableUsable(selectable))
|
|
||||||
{
|
|
||||||
return selectable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return FirstUsable(_runtimeSelectables, _runtimeSelectableCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool HasAvailableSelectable()
|
internal bool HasAvailableSelectable()
|
||||||
{
|
{
|
||||||
RefreshSelectableCache();
|
return IsSelectableValid(_defaultSelectable)
|
||||||
|
|| FirstUsable(_bakedSelectables, BakedSelectableCount) != null
|
||||||
if (IsSelectableValid(_defaultSelectable))
|
|| FirstUsable(_runtimeSelectables, _runtimeSelectableCount) != null;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < _cachedSelectables.Count; i++)
|
|
||||||
{
|
|
||||||
if (IsSelectableUsable(_cachedSelectables[i]))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RecordSelection(GameObject selectedObject)
|
internal void RecordSelection(GameObject selectedObject)
|
||||||
@ -241,109 +317,142 @@ namespace UnityEngine.UI
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefreshSelectableCache();
|
CaptureAllBaselines();
|
||||||
_navigationSuppressed = suppressed;
|
_navigationSuppressed = suppressed;
|
||||||
for (int i = 0; i < _cachedSelectables.Count; i++)
|
ApplySuppression(_bakedSelectables, _bakedBaselineNavigation, BakedSelectableCount, suppressed);
|
||||||
|
ApplySuppression(_runtimeSelectables, _runtimeBaselineNavigation, _runtimeSelectableCount, suppressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureRuntimeBuffers()
|
||||||
|
{
|
||||||
|
int capacity = _runtimeSelectableCapacity > 0 ? _runtimeSelectableCapacity : 0;
|
||||||
|
if (_runtimeSelectables == null || _runtimeSelectables.Length != capacity)
|
||||||
{
|
{
|
||||||
Selectable selectable = _cachedSelectables[i];
|
_runtimeSelectables = capacity > 0 ? new Selectable[capacity] : System.Array.Empty<Selectable>();
|
||||||
|
_runtimeBaselineNavigation = capacity > 0 ? new Navigation[capacity] : System.Array.Empty<Navigation>();
|
||||||
|
_runtimeSelectableCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bakedCount = BakedSelectableCount;
|
||||||
|
if (_bakedBaselineNavigation == null || _bakedBaselineNavigation.Length != bakedCount)
|
||||||
|
{
|
||||||
|
_bakedBaselineNavigation = bakedCount > 0 ? new Navigation[bakedCount] : System.Array.Empty<Navigation>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CaptureAllBaselines()
|
||||||
|
{
|
||||||
|
EnsureRuntimeBuffers();
|
||||||
|
CaptureBaseline(_bakedSelectables, _bakedBaselineNavigation, BakedSelectableCount);
|
||||||
|
CaptureBaseline(_runtimeSelectables, _runtimeBaselineNavigation, _runtimeSelectableCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CaptureBaseline(Selectable[] selectables, Navigation[] baseline, int count)
|
||||||
|
{
|
||||||
|
if (selectables == null || baseline == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Selectable selectable = selectables[i];
|
||||||
|
if (selectable != null)
|
||||||
|
{
|
||||||
|
baseline[i] = selectable.navigation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplySuppression(Selectable[] selectables, Navigation[] baseline, int count, bool suppressed)
|
||||||
|
{
|
||||||
|
if (selectables == null || baseline == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Selectable selectable = selectables[i];
|
||||||
if (selectable == null)
|
if (selectable == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_baselineNavigation.ContainsKey(selectable))
|
|
||||||
{
|
|
||||||
_baselineNavigation[selectable] = selectable.navigation;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (suppressed)
|
if (suppressed)
|
||||||
{
|
{
|
||||||
Navigation navigation = selectable.navigation;
|
SetSelectableSuppressed(selectable, true);
|
||||||
navigation.mode = Navigation.Mode.None;
|
|
||||||
selectable.navigation = navigation;
|
|
||||||
}
|
}
|
||||||
else if (_baselineNavigation.TryGetValue(selectable, out Navigation navigation))
|
else
|
||||||
{
|
{
|
||||||
selectable.navigation = navigation;
|
selectable.navigation = baseline[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void RefreshSelectableCache()
|
private static void SetSelectableSuppressed(Selectable selectable, bool suppressed)
|
||||||
{
|
{
|
||||||
if (!_cacheDirty)
|
if (selectable == null || !suppressed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheDirty = false;
|
Navigation navigation = selectable.navigation;
|
||||||
_cachedSelectables.Clear();
|
navigation.mode = Navigation.Mode.None;
|
||||||
_cachedSelectableSet.Clear();
|
selectable.navigation = navigation;
|
||||||
|
|
||||||
if (_explicitSelectables != null && _explicitSelectables.Count > 0)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < _explicitSelectables.Count; i++)
|
|
||||||
{
|
|
||||||
TryAddSelectable(_explicitSelectables[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_selectableScanBuffer.Clear();
|
|
||||||
GetComponentsInChildren(true, _selectableScanBuffer);
|
|
||||||
for (int i = 0; i < _selectableScanBuffer.Count; i++)
|
|
||||||
{
|
|
||||||
TryAddSelectable(_selectableScanBuffer[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_removeBuffer.Clear();
|
|
||||||
foreach (Selectable key in _baselineNavigation.Keys)
|
|
||||||
{
|
|
||||||
if (!_cachedSelectableSet.Contains(key))
|
|
||||||
{
|
|
||||||
_removeBuffer.Add(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < _removeBuffer.Count; i++)
|
|
||||||
{
|
|
||||||
_baselineNavigation.Remove(_removeBuffer[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
_selectableScanBuffer.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InvalidateSelectableCache()
|
private bool ContainsSelectable(Selectable selectable)
|
||||||
{
|
{
|
||||||
_cacheDirty = true;
|
return IndexOf(_bakedSelectables, BakedSelectableCount, selectable) >= 0
|
||||||
}
|
|| IndexOf(_runtimeSelectables, _runtimeSelectableCount, selectable) >= 0;
|
||||||
|
|
||||||
private void TryAddSelectable(Selectable selectable)
|
|
||||||
{
|
|
||||||
if (selectable == null || !Owns(selectable.gameObject) || !_cachedSelectableSet.Add(selectable))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_cachedSelectables.Add(selectable);
|
|
||||||
if (!_baselineNavigation.ContainsKey(selectable) || !_navigationSuppressed)
|
|
||||||
{
|
|
||||||
_baselineNavigation[selectable] = selectable.navigation;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsSelectableValid(Selectable selectable)
|
private bool IsSelectableValid(Selectable selectable)
|
||||||
{
|
{
|
||||||
return IsSelectableUsable(selectable)
|
return IsSelectableUsable(selectable) && ContainsSelectable(selectable);
|
||||||
&& (_cachedSelectableSet.Contains(selectable) || Owns(selectable.gameObject));
|
}
|
||||||
|
|
||||||
|
private static Selectable FirstUsable(Selectable[] selectables, int count)
|
||||||
|
{
|
||||||
|
if (selectables == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
Selectable selectable = selectables[i];
|
||||||
|
if (IsSelectableUsable(selectable))
|
||||||
|
{
|
||||||
|
return selectable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int IndexOf(Selectable[] selectables, int count, Selectable selectable)
|
||||||
|
{
|
||||||
|
if (selectables == null || selectable == null)
|
||||||
|
{
|
||||||
|
return InvalidIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
if (selectables[i] == selectable)
|
||||||
|
{
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return InvalidIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsSelectableUsable(Selectable selectable)
|
private static bool IsSelectableUsable(Selectable selectable)
|
||||||
{
|
{
|
||||||
return selectable != null
|
return selectable != null && selectable.IsActive() && selectable.IsInteractable();
|
||||||
&& selectable.IsActive()
|
|
||||||
&& selectable.IsInteractable();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Selectable GetSelectableFromObject(GameObject selectedObject)
|
private static Selectable GetSelectableFromObject(GameObject selectedObject)
|
||||||
@ -357,6 +466,21 @@ namespace UnityEngine.UI
|
|||||||
? selectable
|
? selectable
|
||||||
: selectedObject.GetComponentInParent<Selectable>();
|
: selectedObject.GetComponentInParent<Selectable>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MarkRuntimeStateDirty()
|
||||||
|
{
|
||||||
|
if (UXNavigationRuntime.TryGetInstance(out var runtime))
|
||||||
|
{
|
||||||
|
runtime.MarkStateDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReportCapacityExceeded()
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR || DEVELOPMENT_BUILD
|
||||||
|
Debug.LogError("UXNavigationScope runtime selectable capacity exceeded.");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -5,6 +5,28 @@ namespace UnityEngine.UI
|
|||||||
[AddComponentMenu("UI/UX Navigation Skip")]
|
[AddComponentMenu("UI/UX Navigation Skip")]
|
||||||
public sealed class UXNavigationSkip : MonoBehaviour
|
public sealed class UXNavigationSkip : MonoBehaviour
|
||||||
{
|
{
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
InvalidateNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
InvalidateNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTransformParentChanged()
|
||||||
|
{
|
||||||
|
InvalidateNavigation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void InvalidateNavigation()
|
||||||
|
{
|
||||||
|
if (UXNavigationRuntime.TryGetInstance(out var runtime))
|
||||||
|
{
|
||||||
|
runtime.InvalidateSkipCaches();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user