using System; using System.Collections; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.SceneManagement; using BaseGames.Core; using BaseGames.Core.Events; namespace BaseGames.UI { /// /// 的实现:统一 UI 导航栈。常驻 Persistent 场景, /// 经 ServiceLocator 暴露,主菜单与游戏内共用同一套栈语义。 /// /// 压栈:记录压栈前焦点;据新面板 处理下方面板 /// (Replace→停用;Modal→保留可见但 (false) /// 使其退出导航图);激活新面板并延后一帧聚焦其首项。 /// 出栈:关闭栈顶,按其压栈时的 mode 还原下方面板,恢复焦点到压栈前的选中项。 /// 取消:本类是 EVT_UICancelPressed 的唯一消费者,仅在栈顶 /// 时出栈一层(逐层回退)。 /// [DefaultExecutionOrder(+40)] // 早于 UIManager(+50),确保委托方解析得到本服务 public class UINavigator : MonoBehaviour, IUINavigator { [Tooltip("UI 取消操作(ESC / 手柄 B·Circle)。本导航器为唯一订阅者,按下时关闭栈顶一层。对应 EVT_UICancelPressed。")] [SerializeField] private VoidEventChannelSO _onUICancelPressed; private readonly Stack _stack = new(); private readonly CompositeDisposable _subs = new(); private Coroutine _focusRoutine; public UIPanelBase Top => _stack.Count > 0 ? _stack.Peek().Panel : null; public int Depth => _stack.Count; public event Action StackChanged; // ── 生命周期 ────────────────────────────────────────────────────────── private void OnEnable() { ServiceLocator.Register(this); _onUICancelPressed?.Subscribe(HandleCancel).AddTo(_subs); SceneManager.sceneUnloaded += OnSceneUnloaded; } private void OnDisable() { SceneManager.sceneUnloaded -= OnSceneUnloaded; _subs.Clear(); ServiceLocator.Unregister(this); } // ── 压栈 ────────────────────────────────────────────────────────────── public void Push(UIPanelBase panel, PushMode? mode = null) { if (panel == null) return; PurgeDead(); PushMode m = mode ?? panel.DefaultMode; // 处理下方面板:Replace 停用、Modal 屏蔽交互(退出导航图,杜绝上下键穿透)。 if (_stack.Count > 0) { var below = _stack.Peek().Panel; if (below != null) { below.OnFocusLost(); if (m == PushMode.Replace) below.gameObject.SetActive(false); else below.SetInteractableLayer(false); } } var entry = new UIStackEntry { Panel = panel, Mode = m, FocusToRestore = EventSystem.current != null ? EventSystem.current.currentSelectedGameObject : null, OwningScene = panel.gameObject.scene, }; _stack.Push(entry); panel.gameObject.SetActive(true); panel.SetInteractableLayer(true); FocusNextFrame(panel.FirstSelectableGO); StackChanged?.Invoke(); } // ── 出栈 ────────────────────────────────────────────────────────────── public void Pop() { PurgeDead(); if (_stack.Count == 0) return; var top = _stack.Pop(); if (top.Panel != null) top.Panel.gameObject.SetActive(false); UIPanelBase below = _stack.Count > 0 ? _stack.Peek().Panel : null; if (below != null) { // 按 top 压栈时的 mode 还原下方面板。 if (top.Mode == PushMode.Replace) below.gameObject.SetActive(true); else below.SetInteractableLayer(true); below.OnFocusGained(); } // 恢复焦点到压栈前的选中项(失效则回落到下层首项)。 GameObject restore = top.FocusToRestore != null && top.FocusToRestore.activeInHierarchy ? top.FocusToRestore : below != null ? below.FirstSelectableGO : null; FocusNextFrame(restore); StackChanged?.Invoke(); } public void PopToRoot() { while (_stack.Count > 0) Pop(); } // ── 取消(ESC / 手柄 B)────────────────────────────────────────────── private void HandleCancel() { PurgeDead(); if (_stack.Count == 0) return; // 栈空(如主菜单根):无操作 if (Top != null && !Top.CanCancel) return; Pop(); // 仅关栈顶一层 } // ── 结果面板 ────────────────────────────────────────────────────────── public Task PushForResultAsync(UIResultPanel panel, CancellationToken ct = default) { if (panel == null) return Task.FromResult(default); Task task = panel.BeginResult(ct); Push(panel, PushMode.Modal); // 结果对话框天然模态:下层保留可见但屏蔽交互 return task; } // ── 场景卸载清理 ────────────────────────────────────────────────────── private void OnSceneUnloaded(Scene s) { if (_stack.Count == 0) return; // Stack 无法删中间项:过滤后按原序重建(保留非本场景且未销毁的层)。 var kept = new List(); foreach (var e in _stack) // 枚举顺序:栈顶→栈底 if (e.Panel != null && e.OwningScene != s) kept.Add(e); if (kept.Count == _stack.Count) return; // 无变化 _stack.Clear(); for (int i = kept.Count - 1; i >= 0; i--) _stack.Push(kept[i]); StackChanged?.Invoke(); } private void PurgeDead() { bool changed = false; while (_stack.Count > 0 && _stack.Peek().Panel == null) { _stack.Pop(); changed = true; } if (changed) StackChanged?.Invoke(); } // ── 焦点(延后一帧,避开 OnEnable / UISelectionRestorer 同帧竞争)──────── private void FocusNextFrame(GameObject target) { if (_focusRoutine != null) StopCoroutine(_focusRoutine); if (!isActiveAndEnabled) return; _focusRoutine = StartCoroutine(FocusRoutine(target)); } private IEnumerator FocusRoutine(GameObject target) { yield return null; _focusRoutine = null; if (target == null || !target.activeInHierarchy) yield break; if (EventSystem.current != null) EventSystem.current.SetSelectedGameObject(target); } } }