using GFramework.Core.Abstractions.logging; using GFramework.Core.extensions; using GFramework.Core.logging; using GFramework.Core.system; using GFramework.Game.Abstractions.enums; using GFramework.Game.Abstractions.ui; namespace GFramework.Game.ui; /// /// UI路由类,提供页面栈管理功能 /// public abstract class UiRouterBase : AbstractSystem, IUiRouter { private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("UiRouterBase"); /// /// 路由守卫列表 /// private readonly List _guards = new(); /// /// 层级管理(非栈层级),用于Overlay、Modal、Toast等浮层 /// private readonly Dictionary> _layers = new(); /// /// UI切换处理器管道 /// private readonly UiTransitionPipeline _pipeline = new(); /// /// 页面栈,用于管理UI页面的显示顺序 /// private readonly Stack _stack = new(); /// /// UI工厂实例,用于创建UI相关的对象 /// private IUiFactory _factory = null!; private IUiRoot _uiRoot = null!; /// /// 注册UI切换处理器 /// /// 处理器实例 /// 执行选项 public void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null) { _pipeline.RegisterHandler(handler, options); } /// /// 注销UI切换处理器 /// /// 处理器实例 public void UnregisterHandler(IUiTransitionHandler handler) { _pipeline.UnregisterHandler(handler); } /// /// 绑定UI根节点 /// public void BindRoot(IUiRoot root) { _uiRoot = root; Log.Debug("Bind UI Root: {0}", root.GetType().Name); } /// /// 将指定的UI界面压入路由栈,显示新的UI界面 /// /// UI界面的唯一标识符 /// 进入界面的参数,可为空 /// 界面切换策略,默认为Exclusive(独占) public void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive) { if (IsTop(uiKey)) { Log.Warn("Push ignored: UI already on top: {0}", uiKey); return; } var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param); Log.Debug( "Push UI Page: key={0}, policy={1}, stackBefore={2}", uiKey, policy, _stack.Count ); BeforeChange(@event); DoPushPageInternal(uiKey, param, policy); AfterChange(@event); } /// /// 将已存在的UI页面压入栈顶并显示 /// /// 已创建的UI页面行为实例 /// 页面进入参数,可为空 /// 页面切换策略 public void Push( IUiPageBehavior page, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive ) { var uiKey = page.View.GetType().Name; if (IsTop(uiKey)) { Log.Warn("Push ignored: UI already on top: {0}", uiKey); return; } var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param); Log.Debug( "Push existing UI Page: key={0}, policy={1}, stackBefore={2}", uiKey, policy, _stack.Count ); BeforeChange(@event); DoPushPageInternal(page, param, policy); AfterChange(@event); } /// /// 弹出栈顶页面并根据策略处理页面 /// /// 弹出策略,默认为销毁策略 public void Pop(UiPopPolicy policy = UiPopPolicy.Destroy) { if (_stack.Count == 0) { Log.Debug("Pop ignored: stack is empty"); return; } var leavingUiKey = _stack.Peek().Key; if (!ExecuteLeaveGuardsAsync(leavingUiKey).GetAwaiter().GetResult()) { Log.Warn("Pop blocked by guard: {0}", leavingUiKey); return; } // ⚠️ 注意:nextUiKey 现在是可选的 var nextUiKey = _stack.Count > 1 ? _stack.ElementAt(1).Key : null; var @event = CreateEvent( nextUiKey, UiTransitionType.Pop ); BeforeChange(@event); DoPopInternal(policy); AfterChange(@event); } /// /// 替换当前所有页面为新页面(基于uiKey) /// /// 新UI页面标识符 /// 页面进入参数,可为空 /// 弹出页面时的销毁策略,默认为销毁 /// 推入页面时的过渡策略,默认为独占 public void Replace( string uiKey, IUiPageEnterParam? param = null, UiPopPolicy popPolicy = UiPopPolicy.Destroy, UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive) { var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param); Log.Debug( "Replace UI Stack with page: key={0}, popPolicy={1}, pushPolicy={2}", uiKey, popPolicy, pushPolicy ); BeforeChange(@event); // 使用内部方法清空栈,避免触发额外的Pipeline DoClearInternal(popPolicy); // 使用工厂的增强方法获取实例 var page = _factory.Create(uiKey); Log.Debug("Get/Create UI Page instance for Replace: {0}", page.GetType().Name); DoPushPageInternal(page, param, pushPolicy); AfterChange(@event); } /// /// 替换当前所有页面为已存在的页面(基于实例) /// /// 已创建的UI页面行为实例 /// 页面进入参数,可为空 /// 弹出页面时的销毁策略,默认为销毁 /// 推入页面时的过渡策略,默认为独占 public void Replace( IUiPageBehavior page, IUiPageEnterParam? param = null, UiPopPolicy popPolicy = UiPopPolicy.Destroy, UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive) { var uiKey = page.Key; var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param); Log.Debug( "Replace UI Stack with existing page: key={0}, popPolicy={1}, pushPolicy={2}", uiKey, popPolicy, pushPolicy ); BeforeChange(@event); // 清空栈 DoClearInternal(popPolicy); Log.Debug("Use existing UI Page instance for Replace: {0}", page.GetType().Name); DoPushPageInternal(page, param, pushPolicy); AfterChange(@event); } /// /// 清空所有页面栈中的页面 /// public void Clear() { var @event = CreateEvent(string.Empty, UiTransitionType.Clear); Log.Debug("Clear UI Stack, stackCount={0}", _stack.Count); BeforeChange(@event); // 使用内部方法,避免触发额外的Pipeline DoClearInternal(UiPopPolicy.Destroy); AfterChange(@event); } /// /// 获取页面栈顶元素的键值,但不移除该元素 /// /// 如果页面栈为空则返回空字符串,否则返回栈顶元素的键值 public string PeekKey() { return _stack.Count == 0 ? string.Empty : _stack.Peek().Key; } /// /// 获取页面栈顶元素,但不移除该元素 /// /// 返回栈顶的IUiPageBehavior元素 public IUiPageBehavior? Peek() { return _stack.Count == 0 ? null : _stack.Peek(); } /// /// 判断栈顶元素是否指定的UI类型 /// /// 要比较的UI类型名称 /// 如果栈为空或栈顶元素类型不匹配则返回false,否则返回true public bool IsTop(string uiKey) { return _stack.Count != 0 && _stack.Peek().Key.Equals(uiKey); } /// /// 判断栈中是否包含指定类型的UI元素 /// /// 要查找的UI类型名称 /// 如果栈中存在指定类型的UI元素则返回true,否则返回false public bool Contains(string uiKey) { return _stack.Any(p => p.Key.Equals(uiKey)); } /// /// 获取栈中元素的数量 /// public int Count => _stack.Count; /// /// 初始化方法,在页面初始化时获取UI工厂实例 /// protected override void OnInit() { _factory = this.GetUtility()!; Log.Debug("UiRouterBase initialized. Factory={0}", _factory.GetType().Name); RegisterHandlers(); } /// /// 注册默认的UI切换处理器 /// protected abstract void RegisterHandlers(); /// /// 创建UI切换事件 /// private UiTransitionEvent CreateEvent( string? toUiKey, UiTransitionType type, UiTransitionPolicy? policy = null, IUiPageEnterParam? param = null ) { return new UiTransitionEvent { FromUiKey = PeekKey(), ToUiKey = toUiKey, TransitionType = type, Policy = policy ?? UiTransitionPolicy.Exclusive, EnterParam = param }; } /// /// 执行UI切换前的Handler(阻塞) /// private void BeforeChange(UiTransitionEvent @event) { Log.Debug("BeforeChange phases started: {0}", @event.TransitionType); _pipeline.ExecuteAsync(@event, UITransitionPhases.BeforeChange).GetAwaiter().GetResult(); Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType); } /// /// 执行UI切换后的Handler(不阻塞) /// private void AfterChange(UiTransitionEvent @event) { Log.Debug("AfterChange phases started: {0}", @event.TransitionType); _ = Task.Run(async () => { try { await _pipeline.ExecuteAsync(@event, UITransitionPhases.AfterChange).ConfigureAwait(false); Log.Debug("AfterChange phases completed: {0}", @event.TransitionType); } catch (Exception ex) { Log.Error("AfterChange phases failed: {0}, Error: {1}", @event.TransitionType, ex.Message); } }); } /// /// 执行Push页面的核心逻辑(基于 uiKey) /// private void DoPushPageInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy) { // 执行进入守卫 if (!ExecuteEnterGuardsAsync(uiKey, param).GetAwaiter().GetResult()) { Log.Warn("Push blocked by guard: {0}", uiKey); return; } // 使用工厂的增强方法获取实例 var page = _factory.Create(uiKey); Log.Debug("Get/Create UI Page instance: {0}", page.GetType().Name); DoPushPageInternal(page, param, policy); } /// /// 执行Push页面的核心逻辑(基于 page) /// private void DoPushPageInternal(IUiPageBehavior page, IUiPageEnterParam? param, UiTransitionPolicy policy) { // 1. 处理当前栈顶页面 if (_stack.Count > 0) { var current = _stack.Peek(); Log.Debug("Pause current page: {0}", current.View.GetType().Name); current.OnPause(); if (policy == UiTransitionPolicy.Exclusive) { Log.Debug("Suspend current page (Exclusive): {0}", current.View.GetType().Name); current.OnHide(); } } // 2. 将新页面添加到UiRoot Log.Debug("Add page to UiRoot: {0}", page.View.GetType().Name); _uiRoot.AddUiPage(page); // 3. 压入栈 _stack.Push(page); // 4. 触发页面生命周期 Log.Debug( "Enter & Show page: {0}, stackAfter={1}", page.View.GetType().Name, _stack.Count ); page.OnEnter(param); page.OnShow(); } /// /// 执行Pop的核心逻辑(不触发Pipeline) /// private void DoPopInternal(UiPopPolicy policy) { if (_stack.Count == 0) return; var top = _stack.Pop(); Log.Debug( "Pop UI Page internal: {0}, policy={1}, stackAfterPop={2}", top.GetType().Name, policy, _stack.Count ); if (policy == UiPopPolicy.Destroy) { top.OnExit(); _uiRoot.RemoveUiPage(top); } else // Suspend { top.OnHide(); } if (_stack.Count <= 0) return; var next = _stack.Peek(); next.OnResume(); next.OnShow(); } /// /// 执行Clear的核心逻辑(不触发Pipeline) /// /// UI弹出策略 private void DoClearInternal(UiPopPolicy policy) { Log.Debug("Clear UI Stack internal, count={0}", _stack.Count); // 循环执行弹出操作直到栈为空 while (_stack.Count > 0) DoPopInternal(policy); } #region 层级管理 /// /// 在指定层级显示UI(非栈管理) /// public void Show( string uiKey, UiLayer layer, IUiPageEnterParam? param = null) { if (layer == UiLayer.Page) throw new ArgumentException("Use Push() for Page layer"); // 初始化层级字典 if (!_layers.ContainsKey(layer)) _layers[layer] = new Dictionary(); var layerDict = _layers[layer]; // 检查是否已存在 if (layerDict.TryGetValue(uiKey, out var existing)) { Log.Debug("UI already visible in layer: {0}, layer={1}", uiKey, layer); existing.OnEnter(param); existing.OnShow(); return; } // 获取或创建实例 var page = _factory.Create(uiKey); layerDict[uiKey] = page; // 添加到UiRoot,传入层级Z-order _uiRoot.AddUiPage(page, layer); page.OnEnter(param); page.OnShow(); Log.Debug("Show UI in layer: {0}, layer={1}", uiKey, layer); } /// /// 在指定层级显示UI(基于实例) /// public void Show(IUiPageBehavior page, UiLayer layer) { if (layer == UiLayer.Page) throw new ArgumentException("Use Push() for Page layer"); var uiKey = page.Key; if (!_layers.ContainsKey(layer)) _layers[layer] = new Dictionary(); _layers[layer][uiKey] = page; _uiRoot.AddUiPage(page, layer); page.OnShow(); Log.Debug("Show existing UI instance in layer: {0}, layer={1}", uiKey, layer); } /// /// 隐藏指定层级的UI。 /// /// 要隐藏的UI的唯一标识符。 /// UI所在的层级。 /// 是否永久销毁UI。如果为true,则UI将被彻底移除;如果为false,则UI仅被隐藏,可后续恢复。 public void Hide(string uiKey, UiLayer layer, bool destroy = false) { // 尝试获取指定层级的UI字典,若不存在则直接返回 if (!_layers.TryGetValue(layer, out var layerDict)) return; // 尝试获取指定UI键对应的页面对象,若不存在则直接返回 if (!layerDict.TryGetValue(uiKey, out var page)) return; if (destroy) { // 永久移除UI:调用OnExit方法并从UI根节点和层级字典中移除该UI page.OnExit(); // ✅ 只在 Destroy 时 Exit _uiRoot.RemoveUiPage(page); layerDict.Remove(uiKey); Log.Debug( "Hide & Destroy UI from layer: {0}, layer={1}", uiKey, layer ); } else { // 临时隐藏UI:调用OnHide方法,保留UI状态以便后续恢复 page.OnHide(); // ✅ Hide ≠ Exit Log.Debug( "Hide UI from layer (suspend): {0}, layer={1}", uiKey, layer ); } } /// /// 恢复指定层级中已隐藏的UI。 /// /// 要恢复的UI的唯一标识符。 /// UI所在的层级。 public void Resume(string uiKey, UiLayer layer) { // 尝试获取指定层级的UI字典,若不存在则直接返回 if (!_layers.TryGetValue(layer, out var layerDict)) return; // 尝试获取指定UI键对应的页面对象,若不存在则直接返回 if (!layerDict.TryGetValue(uiKey, out var page)) return; // 调用OnShow和OnResume方法以恢复UI的显示和状态 page.OnShow(); page.OnResume(); Log.Debug("Resume UI in layer: {0}, layer={1}", uiKey, layer); } /// /// 清空指定层级的所有UI /// public void ClearLayer(UiLayer layer, bool destroy = false) { if (!_layers.TryGetValue(layer, out var layerDict)) return; var keys = layerDict.Keys.ToArray(); foreach (var key in keys) Hide(key, layer, destroy); Log.Debug("Cleared layer: {0}, destroyed={1}", layer, destroy); } /// /// 获取指定层级的UI实例 /// public IUiPageBehavior? GetFromLayer(string uiKey, UiLayer layer) { return _layers.TryGetValue(layer, out var layerDict) && layerDict.TryGetValue(uiKey, out var page) ? page : null; } /// /// 判断指定层级是否有UI显示 /// public bool HasVisibleInLayer(string uiKey, UiLayer layer) { if (!_layers.TryGetValue(layer, out var layerDict) || !layerDict.TryGetValue(uiKey, out var page)) return false; return page.IsVisible; } #endregion #region 路由守卫 /// /// 注册路由守卫 /// public void AddGuard(IUiRouteGuard guard) { ArgumentNullException.ThrowIfNull(guard); if (_guards.Contains(guard)) { Log.Debug("Guard already registered: {0}", guard.GetType().Name); return; } _guards.Add(guard); // 按优先级排序 _guards.Sort((a, b) => a.Priority.CompareTo(b.Priority)); Log.Debug("Guard registered: {0}, Priority={1}", guard.GetType().Name, guard.Priority); } /// /// 注册路由守卫(泛型方法) /// public void AddGuard() where T : IUiRouteGuard, new() { var guard = new T(); AddGuard(guard); } /// /// 移除路由守卫 /// public void RemoveGuard(IUiRouteGuard guard) { ArgumentNullException.ThrowIfNull(guard); if (_guards.Remove(guard)) Log.Debug("Guard removed: {0}", guard.GetType().Name); } /// /// 执行进入守卫 /// private async Task ExecuteEnterGuardsAsync(string uiKey, IUiPageEnterParam? param) { foreach (var guard in _guards) try { Log.Debug("Executing enter guard: {0} for {1}", guard.GetType().Name, uiKey); var canEnter = await guard.CanEnterAsync(uiKey, param); if (!canEnter) { Log.Debug("Enter guard blocked: {0}", guard.GetType().Name); return false; } if (guard.CanInterrupt) { Log.Debug("Enter guard {0} passed, can interrupt = true", guard.GetType().Name); return true; } } catch (Exception ex) { Log.Error("Enter guard {0} failed: {1}", guard.GetType().Name, ex.Message); if (guard.CanInterrupt) return false; } return true; } /// /// 执行离开守卫 /// private async Task ExecuteLeaveGuardsAsync(string uiKey) { foreach (var guard in _guards) try { Log.Debug("Executing leave guard: {0} for {1}", guard.GetType().Name, uiKey); var canLeave = await guard.CanLeaveAsync(uiKey); if (!canLeave) { Log.Debug("Leave guard blocked: {0}", guard.GetType().Name); return false; } if (!guard.CanInterrupt) continue; Log.Debug("Leave guard {0} passed, can interrupt = true", guard.GetType().Name); return true; } catch (Exception ex) { Log.Error("Leave guard {0} failed: {1}", guard.GetType().Name, ex.Message); if (guard.CanInterrupt) return false; } return true; } #endregion }