#if INPUTSYSTEM_SUPPORT using System.Collections.Generic; using AlicizaX.UI.Runtime; namespace UnityEngine.UI { [DisallowMultipleComponent] [AddComponentMenu("UI/UX Navigation Scope")] public sealed class UXNavigationScope : MonoBehaviour { [SerializeField, Header("默认选中控件")] private Selectable _defaultSelectable; [SerializeField, Header("显式导航控件列表")] private List _explicitSelectables = new(); [SerializeField, Header("记住上次选中")] private bool _rememberLastSelection = true; [SerializeField, Header("手柄模式必须有选中")] private bool _requireSelectionWhenGamepad = true; [SerializeField, Header("阻断下层导航域")] private bool _blockLowerScopes = true; [SerializeField, Header("自动选中首个可用控件")] private bool _autoSelectFirstAvailable = true; private readonly List _cachedSelectables = new(16); private readonly Dictionary _baselineNavigation = new(); private readonly List _removeBuffer = new(8); private Canvas _cachedCanvas; private UIHolderObjectBase _cachedHolder; private Selectable _lastSelected; private bool _cacheDirty = true; private bool _navigationSuppressed; internal ulong ActivationSerial { get; set; } internal bool WasAvailable { get; set; } public Selectable DefaultSelectable { get => _defaultSelectable; set => _defaultSelectable = value; } public bool RememberLastSelection => _rememberLastSelection; public bool RequireSelectionWhenGamepad => _requireSelectionWhenGamepad; public bool BlockLowerScopes => _blockLowerScopes; internal Canvas Canvas { get { if (_cachedCanvas == null) { _cachedCanvas = GetComponentInParent(true); } return _cachedCanvas; } } internal UIHolderObjectBase Holder { get { if (_cachedHolder == null) { _cachedHolder = GetComponent(); if (_cachedHolder == null) { _cachedHolder = GetComponentInParent(true); } } return _cachedHolder; } } private void OnEnable() { _cacheDirty = true; UXNavigationRuntime.EnsureInstance().RegisterScope(this); } private void OnDisable() { SetNavigationSuppressed(false); if (UXNavigationRuntime.TryGetInstance(out var runtime)) { runtime.UnregisterScope(this); } } private void OnDestroy() { SetNavigationSuppressed(false); } private void OnTransformChildrenChanged() { _cacheDirty = true; } #if UNITY_EDITOR private void OnValidate() { _cacheDirty = true; } #endif internal int GetHierarchyDepth() { int depth = 0; Transform current = transform; while (current != null) { depth++; current = current.parent; } return depth; } internal bool Owns(GameObject target) { if (target == null) { return false; } var nearestScope = target.GetComponentInParent(true); return nearestScope == this; } internal Selectable GetPreferredSelectable() { RefreshSelectableCache(); if (_rememberLastSelection && IsSelectableValid(_lastSelected)) { return _lastSelected; } if (IsSelectableValid(_defaultSelectable)) { return _defaultSelectable; } if (!_autoSelectFirstAvailable) { return null; } for (int i = 0; i < _cachedSelectables.Count; i++) { Selectable selectable = _cachedSelectables[i]; if (IsSelectableValid(selectable)) { return selectable; } } return null; } internal bool HasAvailableSelectable() { RefreshSelectableCache(); if (IsSelectableValid(_defaultSelectable)) { return true; } for (int i = 0; i < _cachedSelectables.Count; i++) { if (IsSelectableValid(_cachedSelectables[i])) { return true; } } return false; } internal void RecordSelection(GameObject selectedObject) { if (!_rememberLastSelection || selectedObject == null) { return; } Selectable selectable = selectedObject.GetComponent(); if (selectable == null) { selectable = selectedObject.GetComponentInParent(); } if (IsSelectableValid(selectable)) { _lastSelected = selectable; } } internal bool IsSelectableOwnedAndValid(GameObject selectedObject) { if (selectedObject == null || !Owns(selectedObject)) { return false; } Selectable selectable = selectedObject.GetComponent(); if (selectable == null) { selectable = selectedObject.GetComponentInParent(); } return IsSelectableValid(selectable); } internal void SetNavigationSuppressed(bool suppressed) { RefreshSelectableCache(); if (_navigationSuppressed == suppressed) { return; } _navigationSuppressed = suppressed; for (int i = 0; i < _cachedSelectables.Count; i++) { Selectable selectable = _cachedSelectables[i]; if (selectable == null) { continue; } if (!_baselineNavigation.ContainsKey(selectable)) { _baselineNavigation[selectable] = selectable.navigation; } if (suppressed) { Navigation navigation = selectable.navigation; navigation.mode = Navigation.Mode.None; selectable.navigation = navigation; } else if (_baselineNavigation.TryGetValue(selectable, out Navigation navigation)) { selectable.navigation = navigation; } } } internal void RefreshSelectableCache() { if (!_cacheDirty) { return; } _cacheDirty = false; _cachedSelectables.Clear(); if (_explicitSelectables != null && _explicitSelectables.Count > 0) { for (int i = 0; i < _explicitSelectables.Count; i++) { TryAddSelectable(_explicitSelectables[i]); } } else { Selectable[] selectables = GetComponentsInChildren(true); for (int i = 0; i < selectables.Length; i++) { TryAddSelectable(selectables[i]); } } _removeBuffer.Clear(); foreach (var pair in _baselineNavigation) { if (!_cachedSelectables.Contains(pair.Key)) { _removeBuffer.Add(pair.Key); } } for (int i = 0; i < _removeBuffer.Count; i++) { _baselineNavigation.Remove(_removeBuffer[i]); } } internal void InvalidateSelectableCache() { _cacheDirty = true; } private void TryAddSelectable(Selectable selectable) { if (selectable == null || !Owns(selectable.gameObject)) { return; } _cachedSelectables.Add(selectable); if (!_baselineNavigation.ContainsKey(selectable) || !_navigationSuppressed) { _baselineNavigation[selectable] = selectable.navigation; } } private bool IsSelectableValid(Selectable selectable) { return selectable != null && selectable.IsActive() && selectable.IsInteractable() && Owns(selectable.gameObject); } } } #endif