com.alicizax.unity.ui.exten.../Editor/UX/Navigation/UXNavigationScopeEditor.cs
陈思海 526341579a 优化UIExtension扩展系统
优化UXBinding编辑器
优化UXBinding性能
优化UXBinding Bug
优化Hotkey注册器性能
优化UXNavigation导航性能
2026-04-28 20:52:06 +08:00

203 lines
8.0 KiB
C#

#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