203 lines
8.0 KiB
C#
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
|