#if INPUTSYSTEM_SUPPORT && UX_NAVIGATION using System.Collections.Generic; using AlicizaX; using AlicizaX.UI.Runtime; using UnityEngine.EventSystems; namespace UnityEngine.UI { internal sealed class UXNavigationRuntime : MonoBehaviour { private static UXNavigationRuntime _instance; private static readonly string CacheLayerName = $"Layer{(int)UILayer.All}-{UILayer.All}"; private readonly HashSet _scopeSet = new(); private readonly List _scopes = new(32); private readonly HashSet _interactiveLayerRoots = new(); private readonly List _holderBuffer = new(32); private Transform _uiCanvasRoot; private UXNavigationScope _topScope; private GameObject _lastObservedSelection; private bool _discoveryDirty = true; private ulong _activationSerial; private bool _missingEventSystemLogged; private bool _topScopeDirty = true; private bool _suppressionDirty = true; [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] private static void Bootstrap() { if (AppServices.App == null || AppServices.App.Require() == null) return; EnsureInstance(); UXInputModeService.EnsureInstance(); } internal static UXNavigationRuntime EnsureInstance() { if (_instance != null) { return _instance; } var go = new GameObject("[UXNavigationRuntime]"); go.hideFlags = HideFlags.HideAndDontSave; DontDestroyOnLoad(go); _instance = go.AddComponent(); return _instance; } internal static bool TryGetInstance(out UXNavigationRuntime runtime) { runtime = _instance; return runtime != null; } internal static bool IsHolderWithinTopScope(UIHolderObjectBase holder) { if (_instance == null || _instance._topScope == null || holder == null) { return true; } return _instance.IsHolderOwnedByScope(holder, _instance._topScope); } private void Awake() { if (_instance != null && _instance != this) { Destroy(gameObject); return; } _instance = this; DontDestroyOnLoad(gameObject); hideFlags = HideFlags.HideAndDontSave; } private void OnEnable() { UXInputModeService.OnModeChanged += OnInputModeChanged; } private void OnDisable() { UXInputModeService.OnModeChanged -= OnInputModeChanged; } private void OnDestroy() { UXInputModeService.OnModeChanged -= OnInputModeChanged; if (_instance == this) { _instance = null; } } internal void RegisterScope(UXNavigationScope scope) { if (scope == null || !_scopeSet.Add(scope)) { return; } _scopes.Add(scope); _topScopeDirty = true; _suppressionDirty = true; } internal void UnregisterScope(UXNavigationScope scope) { if (scope == null || !_scopeSet.Remove(scope)) { return; } if (_topScope == scope) { _topScope = null; } scope.IsAvailable = false; scope.SetNavigationSuppressed(false); int idx = _scopes.IndexOf(scope); if (idx >= 0) { int last = _scopes.Count - 1; _scopes[idx] = _scopes[last]; _scopes.RemoveAt(last); } _topScopeDirty = true; _suppressionDirty = true; } internal void MarkDiscoveryDirty() { _discoveryDirty = true; } private void Update() { TryBindUIRoot(); if (_uiCanvasRoot == null) { return; } if (_discoveryDirty) { DiscoverScopes(); } if (_topScopeDirty) { UXNavigationScope newTopScope = FindTopScope(); _topScopeDirty = false; if (!ReferenceEquals(_topScope, newTopScope)) { _topScope = newTopScope; _suppressionDirty = true; } } if (_suppressionDirty) { ApplyScopeSuppression(); _suppressionDirty = false; } if (UXInputModeService.CurrentMode == UXInputMode.Gamepad) { EnsureGamepadSelection(); if (_topScope != null) { Cursor.visible = false; } } TrackSelection(); } private void TryBindUIRoot() { if (_uiCanvasRoot != null) { return; } IUIService uiModule = AppServices.App.Require(); 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() != null || IsNavigationSkipped(holder.transform)) { continue; } holder.gameObject.AddComponent(); 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(); } watcher.Initialize(this); } private UXNavigationScope FindTopScope() { UXNavigationScope bestScope = null; for (int i = 0; i < _scopes.Count; i++) { UXNavigationScope scope = _scopes[i]; if (scope == null) { continue; } bool available = IsScopeAvailable(scope); scope.IsAvailable = available; if (scope.WasAvailable != available) { scope.WasAvailable = available; if (available) { scope.ActivationSerial = ++_activationSerial; } _suppressionDirty = true; } if (!available) { continue; } if (bestScope == null || CompareScopePriority(scope, bestScope) < 0) { bestScope = scope; } } return bestScope; } private bool IsScopeAvailable(UXNavigationScope scope) { if (scope == null || !scope.isActiveAndEnabled || !scope.gameObject.activeInHierarchy) { return false; } Canvas canvas = scope.Canvas; if (canvas == null || canvas.gameObject.layer != UIComponent.UIShowLayer) { return false; } 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(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(); if (nearestScope == null) { nearestScope = holder.GetComponentInParent(true); } return nearestScope == scope; } private void ApplyScopeSuppression() { for (int i = 0; i < _scopes.Count; i++) { UXNavigationScope scope = _scopes[i]; if (scope == null) { continue; } bool suppress = scope.IsAvailable && _topScope != null && !ReferenceEquals(scope, _topScope) && _topScope.BlockLowerScopes && CompareScopePriority(_topScope, scope) < 0; scope.SetNavigationSuppressed(suppress); } } private void EnsureGamepadSelection() { EventSystem eventSystem = EventSystem.current; if (eventSystem == null) { if (!_missingEventSystemLogged) { Debug.LogWarning("UXNavigationRuntime requires an active EventSystem for gamepad navigation."); _missingEventSystemLogged = true; } return; } _missingEventSystemLogged = false; if (_topScope == null || !_topScope.RequireSelectionWhenGamepad) { return; } GameObject currentSelected = eventSystem.currentSelectedGameObject; if (_topScope.IsSelectableOwnedAndValid(currentSelected)) { _topScope.RecordSelection(currentSelected); return; } Selectable preferred = _topScope.GetPreferredSelectable(); eventSystem.SetSelectedGameObject(preferred != null ? preferred.gameObject : null); _lastObservedSelection = eventSystem.currentSelectedGameObject; if (_lastObservedSelection != null) { _topScope.RecordSelection(_lastObservedSelection); } } private void TrackSelection() { EventSystem eventSystem = EventSystem.current; if (eventSystem == null) { _lastObservedSelection = null; return; } GameObject currentSelected = eventSystem.currentSelectedGameObject; if (ReferenceEquals(_lastObservedSelection, currentSelected)) { return; } _lastObservedSelection = currentSelected; if (_topScope != null && _topScope.IsSelectableOwnedAndValid(currentSelected)) { _topScope.RecordSelection(currentSelected); } } private void OnInputModeChanged(UXInputMode mode) { EventSystem eventSystem = EventSystem.current; if (mode == UXInputMode.Pointer) { if (_topScope != null) { 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) { int leftOrder = left.Canvas != null ? left.Canvas.sortingOrder : int.MinValue; int rightOrder = right.Canvas != null ? right.Canvas.sortingOrder : int.MinValue; int orderCompare = rightOrder.CompareTo(leftOrder); if (orderCompare != 0) { return orderCompare; } int hierarchyCompare = right.GetHierarchyDepth().CompareTo(left.GetHierarchyDepth()); if (hierarchyCompare != 0) { return hierarchyCompare; } return right.ActivationSerial.CompareTo(left.ActivationSerial); } } } #endif