mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(ui): 添加UI页面生命周期和路由管理相关接口及实现
- 定义了IPageBehavior接口,提供UI页面的生命周期方法如OnEnter、OnExit、OnPause、OnResume等 - 创建了IUiFactory接口用于创建UI页面实例,以及IUiPage接口定义页面基本操作 - 添加了IUiPageEnterParam接口用于定义页面跳转参数数据结构 - 实现了IUiRouter接口提供页面栈管理功能,支持Push、Pop、Replace、Clear等操作 - 创建了UI切换处理器相关接口和实现,包括IUiTransitionHandler和UiTransitionPipeline - 添加了UI切换事件系统,支持BeforeChange和AfterChange两个执行阶段 - 实现了日志记录处理器LoggingTransitionHandler用于记录UI切换信息 - 定义了多种UI切换策略枚举如UiTransitionPolicy、UiTransitionType等 - 提供了UI注册表接口用于管理UI实例的注册和获取功能
This commit is contained in:
parent
14894814a5
commit
8fd7e2e952
27
GFramework.Game.Abstractions/enums/UITransitionPhases.cs
Normal file
27
GFramework.Game.Abstractions/enums/UITransitionPhases.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace GFramework.Game.Abstractions.enums;
|
||||
|
||||
/// <summary>
|
||||
/// UI切换阶段枚举,定义UI切换过程中的不同阶段
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum UITransitionPhases
|
||||
{
|
||||
/// <summary>
|
||||
/// UI切换前阶段,在此阶段执行的Handler可以阻塞UI切换流程
|
||||
/// 适用于:淡入淡出动画、用户确认对话框、数据预加载等需要等待完成的操作
|
||||
/// </summary>
|
||||
BeforeChange = 1,
|
||||
|
||||
/// <summary>
|
||||
/// UI切换后阶段,在此阶段执行的Handler不阻塞UI切换流程
|
||||
/// 适用于:播放音效、日志记录、统计数据收集等后台操作
|
||||
/// </summary>
|
||||
AfterChange = 2,
|
||||
|
||||
/// <summary>
|
||||
/// 所有阶段,Handler将在BeforeChange和AfterChange阶段都执行
|
||||
/// </summary>
|
||||
All = BeforeChange | AfterChange
|
||||
}
|
||||
32
GFramework.Game.Abstractions/enums/UiTransitionPolicy.cs
Normal file
32
GFramework.Game.Abstractions/enums/UiTransitionPolicy.cs
Normal file
@ -0,0 +1,32 @@
|
||||
namespace GFramework.Game.Abstractions.enums;
|
||||
|
||||
/// <summary>
|
||||
/// UI页面过渡策略枚举
|
||||
/// 定义了UI页面在出栈时的不同处理方式
|
||||
/// </summary>
|
||||
public enum UiTransitionPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// 出栈即销毁(一次性页面)
|
||||
/// 页面从栈中移除时会完全销毁实例
|
||||
/// </summary>
|
||||
Destroy,
|
||||
|
||||
/// <summary>
|
||||
/// 出栈隐藏,保留实例
|
||||
/// 页面从栈中移除时仅隐藏显示,保留实例以便后续重用
|
||||
/// </summary>
|
||||
Hide,
|
||||
|
||||
/// <summary>
|
||||
/// 覆盖显示(不影响下层页面)
|
||||
/// 当前页面覆盖在其他页面之上显示,不影响下层页面的状态
|
||||
/// </summary>
|
||||
Overlay,
|
||||
|
||||
/// <summary>
|
||||
/// 独占显示(下层页面 Pause + Hide)
|
||||
/// 当前页面独占显示区域,下层页面会被暂停并隐藏
|
||||
/// </summary>
|
||||
Exclusive
|
||||
}
|
||||
27
GFramework.Game.Abstractions/enums/UiTransitionType.cs
Normal file
27
GFramework.Game.Abstractions/enums/UiTransitionType.cs
Normal file
@ -0,0 +1,27 @@
|
||||
namespace GFramework.Game.Abstractions.enums;
|
||||
|
||||
/// <summary>
|
||||
/// UI切换类型枚举,定义不同的UI切换操作类型
|
||||
/// </summary>
|
||||
public enum UiTransitionType
|
||||
{
|
||||
/// <summary>
|
||||
/// 压入新页面到栈顶
|
||||
/// </summary>
|
||||
Push,
|
||||
|
||||
/// <summary>
|
||||
/// 弹出栈顶页面
|
||||
/// </summary>
|
||||
Pop,
|
||||
|
||||
/// <summary>
|
||||
/// 替换当前页面
|
||||
/// </summary>
|
||||
Replace,
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有页面
|
||||
/// </summary>
|
||||
Clear,
|
||||
}
|
||||
49
GFramework.Game.Abstractions/ui/IPageBehavior.cs
Normal file
49
GFramework.Game.Abstractions/ui/IPageBehavior.cs
Normal file
@ -0,0 +1,49 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI页面接口,定义了UI页面的生命周期方法
|
||||
/// </summary>
|
||||
public interface IPageBehavior
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取页面视图对象
|
||||
/// </summary>
|
||||
/// <returns>页面视图实例</returns>
|
||||
object View { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取页面是否处于活动状态
|
||||
/// </summary>
|
||||
bool IsAlive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 页面进入时调用的方法
|
||||
/// </summary>
|
||||
/// <param name="param">页面进入时传递的参数,可为空</param>
|
||||
void OnEnter(IUiPageEnterParam? param);
|
||||
|
||||
/// <summary>
|
||||
/// 页面退出时调用的方法
|
||||
/// </summary>
|
||||
void OnExit();
|
||||
|
||||
/// <summary>
|
||||
/// 页面暂停时调用的方法
|
||||
/// </summary>
|
||||
void OnPause();
|
||||
|
||||
/// <summary>
|
||||
/// 页面恢复时调用的方法
|
||||
/// </summary>
|
||||
void OnResume();
|
||||
|
||||
/// <summary>
|
||||
/// 页面被覆盖时调用(不销毁)
|
||||
/// </summary>
|
||||
void OnHide();
|
||||
|
||||
/// <summary>
|
||||
/// 页面重新显示时调用的方法
|
||||
/// </summary>
|
||||
void OnShow();
|
||||
}
|
||||
16
GFramework.Game.Abstractions/ui/IUiFactory.cs
Normal file
16
GFramework.Game.Abstractions/ui/IUiFactory.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using GFramework.Core.Abstractions.utility;
|
||||
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI工厂接口,用于创建UI页面实例
|
||||
/// </summary>
|
||||
public interface IUiFactory : IContextUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据UI键值创建对应的UI页面实例
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI标识键,用于确定要创建的具体UI页面类型</param>
|
||||
/// <returns>创建的UI页面实例,实现IUiPage接口</returns>
|
||||
IPageBehavior Create(string uiKey);
|
||||
}
|
||||
39
GFramework.Game.Abstractions/ui/IUiPage.cs
Normal file
39
GFramework.Game.Abstractions/ui/IUiPage.cs
Normal file
@ -0,0 +1,39 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI页面生命周期接口
|
||||
/// 定义了UI页面的各种状态转换方法,用于管理UI页面的进入、退出、暂停、恢复、显示和隐藏等生命周期事件
|
||||
/// </summary>
|
||||
public interface IUiPage
|
||||
{
|
||||
/// <summary>
|
||||
/// 页面进入时调用的方法
|
||||
/// </summary>
|
||||
/// <param name="param">页面进入参数,可能为空</param>
|
||||
void OnEnter(IUiPageEnterParam? param);
|
||||
|
||||
/// <summary>
|
||||
/// 页面退出时调用的方法
|
||||
/// </summary>
|
||||
void OnExit();
|
||||
|
||||
/// <summary>
|
||||
/// 页面暂停时调用的方法
|
||||
/// </summary>
|
||||
void OnPause();
|
||||
|
||||
/// <summary>
|
||||
/// 页面恢复时调用的方法
|
||||
/// </summary>
|
||||
void OnResume();
|
||||
|
||||
/// <summary>
|
||||
/// 页面显示时调用的方法
|
||||
/// </summary>
|
||||
void OnShow();
|
||||
|
||||
/// <summary>
|
||||
/// 页面隐藏时调用的方法
|
||||
/// </summary>
|
||||
void OnHide();
|
||||
}
|
||||
7
GFramework.Game.Abstractions/ui/IUiPageEnterParam.cs
Normal file
7
GFramework.Game.Abstractions/ui/IUiPageEnterParam.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI页面进入参数接口
|
||||
/// 该接口用于定义UI页面跳转时传递的参数数据结构
|
||||
/// </summary>
|
||||
public interface IUiPageEnterParam;
|
||||
13
GFramework.Game.Abstractions/ui/IUiPageProvider.cs
Normal file
13
GFramework.Game.Abstractions/ui/IUiPageProvider.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI页面提供者接口,用于创建UI页面实例
|
||||
/// </summary>
|
||||
public interface IUiPageProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取UI页面实例
|
||||
/// </summary>
|
||||
/// <returns>UI页面实例</returns>
|
||||
IPageBehavior GetPage();
|
||||
}
|
||||
17
GFramework.Game.Abstractions/ui/IUiRegistry.cs
Normal file
17
GFramework.Game.Abstractions/ui/IUiRegistry.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using GFramework.Core.Abstractions.utility;
|
||||
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI注册表接口,用于根据UI键获取对应的UI实例
|
||||
/// </summary>
|
||||
/// <typeparam name="T">UI实例的类型参数,使用协变修饰符out</typeparam>
|
||||
public interface IUiRegistry<out T> : IUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据指定的UI键获取对应的UI实例
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI的唯一标识键</param>
|
||||
/// <returns>与指定键关联的UI实例</returns>
|
||||
T Get(string uiKey);
|
||||
}
|
||||
19
GFramework.Game.Abstractions/ui/IUiRoot.cs
Normal file
19
GFramework.Game.Abstractions/ui/IUiRoot.cs
Normal file
@ -0,0 +1,19 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI根节点接口,定义了UI页面容器的基本操作
|
||||
/// </summary>
|
||||
public interface IUiRoot
|
||||
{
|
||||
/// <summary>
|
||||
/// 向UI根节点添加子页面
|
||||
/// </summary>
|
||||
/// <param name="child">要添加的UI页面子节点</param>
|
||||
void AddUiPage(IPageBehavior child);
|
||||
|
||||
/// <summary>
|
||||
/// 从UI根节点移除子页面
|
||||
/// </summary>
|
||||
/// <param name="child">要移除的UI页面子节点</param>
|
||||
void RemoveUiPage(IPageBehavior child);
|
||||
}
|
||||
64
GFramework.Game.Abstractions/ui/IUiRouter.cs
Normal file
64
GFramework.Game.Abstractions/ui/IUiRouter.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using GFramework.Core.Abstractions.system;
|
||||
using GFramework.Game.Abstractions.enums;
|
||||
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI路由管理器接口,用于管理UI界面的导航和切换操作
|
||||
/// </summary>
|
||||
public interface IUiRouter : ISystem
|
||||
{
|
||||
/// <summary>
|
||||
/// 绑定UI根节点
|
||||
/// </summary>
|
||||
/// <param name="root">UI根节点接口实例</param>
|
||||
void BindRoot(IUiRoot root);
|
||||
|
||||
/// <summary>
|
||||
/// 将指定的UI界面压入路由栈,显示新的UI界面
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI界面的唯一标识符</param>
|
||||
/// <param name="param">进入界面的参数,可为空</param>
|
||||
/// <param name="policy">界面切换策略,默认为Exclusive(独占)</param>
|
||||
void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 弹出路由栈顶的UI界面,返回到上一个界面
|
||||
/// </summary>
|
||||
/// <param name="policy">界面弹出策略,默认为Destroy(销毁)</param>
|
||||
void Pop(UiPopPolicy policy = UiPopPolicy.Destroy);
|
||||
|
||||
/// <summary>
|
||||
/// 替换当前UI界面为指定的新界面
|
||||
/// </summary>
|
||||
/// <param name="uiKey">新UI界面的唯一标识符</param>
|
||||
/// <param name="param">进入界面的参数,可为空</param>
|
||||
/// <param name="popPolicy">界面弹出策略,默认为销毁当前界面</param>
|
||||
/// <param name="pushPolicy">界面过渡策略,默认为独占模式</param>
|
||||
void Replace(
|
||||
string uiKey,
|
||||
IUiPageEnterParam? param = null,
|
||||
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
|
||||
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive
|
||||
);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有UI界面,重置路由状态
|
||||
/// </summary>
|
||||
void Clear();
|
||||
|
||||
/// <summary>
|
||||
/// 注册UI切换处理器
|
||||
/// </summary>
|
||||
/// <param name="handler">处理器实例</param>
|
||||
/// <param name="options">执行选项</param>
|
||||
void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null);
|
||||
|
||||
/// <summary>
|
||||
/// 注销UI切换处理器
|
||||
/// </summary>
|
||||
/// <param name="handler">处理器实例</param>
|
||||
void UnregisterHandler(IUiTransitionHandler handler);
|
||||
}
|
||||
39
GFramework.Game.Abstractions/ui/IUiTransitionHandler.cs
Normal file
39
GFramework.Game.Abstractions/ui/IUiTransitionHandler.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using GFramework.Game.Abstractions.enums;
|
||||
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI切换处理器接口,定义UI切换扩展点的处理逻辑
|
||||
/// </summary>
|
||||
public interface IUiTransitionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理器优先级,数值越小越先执行
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 处理器适用的阶段,默认为所有阶段
|
||||
/// 可以使用Flags枚举指定多个阶段
|
||||
/// </summary>
|
||||
UITransitionPhases Phases { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否应该处理当前事件
|
||||
/// 可以根据事件类型、UI key等信息进行条件过滤
|
||||
/// </summary>
|
||||
/// <param name="event">UI切换事件</param>
|
||||
/// <param name="phases">当前阶段</param>
|
||||
/// <returns>是否处理</returns>
|
||||
bool ShouldHandle(UiTransitionEvent @event, UITransitionPhases phases);
|
||||
|
||||
/// <summary>
|
||||
/// 处理UI切换事件
|
||||
/// </summary>
|
||||
/// <param name="event">UI切换事件</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>异步任务</returns>
|
||||
Task HandleAsync(UiTransitionEvent @event, CancellationToken cancellationToken);
|
||||
}
|
||||
16
GFramework.Game.Abstractions/ui/IWritableUiRegistry.cs
Normal file
16
GFramework.Game.Abstractions/ui/IWritableUiRegistry.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// 可写的UI注册表接口
|
||||
/// </summary>
|
||||
/// <typeparam name="T">UI实例的类型参数</typeparam>
|
||||
public interface IWritableUiRegistry<T> : IUiRegistry<T> where T : class
|
||||
{
|
||||
/// <summary>
|
||||
/// 注册UI实例到注册表
|
||||
/// </summary>
|
||||
/// <param name="key">UI的唯一标识键</param>
|
||||
/// <param name="scene">要注册的UI实例</param>
|
||||
/// <returns>当前注册表实例</returns>
|
||||
IWritableUiRegistry<T> Register(string key, T scene);
|
||||
}
|
||||
17
GFramework.Game.Abstractions/ui/UiPopPolicy.cs
Normal file
17
GFramework.Game.Abstractions/ui/UiPopPolicy.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// 定义UI弹窗的关闭策略枚举
|
||||
/// </summary>
|
||||
public enum UiPopPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// 销毁模式:关闭时完全销毁UI对象
|
||||
/// </summary>
|
||||
Destroy,
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏模式:关闭时仅隐藏UI对象,保留实例
|
||||
/// </summary>
|
||||
Hide
|
||||
}
|
||||
105
GFramework.Game.Abstractions/ui/UiTransitionEvent.cs
Normal file
105
GFramework.Game.Abstractions/ui/UiTransitionEvent.cs
Normal file
@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Game.Abstractions.enums;
|
||||
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI切换事件,包含UI切换过程中的上下文信息
|
||||
/// </summary>
|
||||
public sealed class UiTransitionEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户自定义数据字典,用于Handler之间传递数据
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, object> _context = new(StringComparer.Ordinal);
|
||||
|
||||
/// <summary>
|
||||
/// 源UI的标识符,切换前的UI key
|
||||
/// </summary>
|
||||
public string FromUiKey { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 目标UI的标识符,切换后的UI key
|
||||
/// </summary>
|
||||
public string ToUiKey { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// UI切换类型
|
||||
/// </summary>
|
||||
public UiTransitionType TransitionType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// UI切换策略
|
||||
/// </summary>
|
||||
public UiTransitionPolicy Policy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// UI进入参数
|
||||
/// </summary>
|
||||
public IUiPageEnterParam? EnterParam { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取用户自定义数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="key">数据键</param>
|
||||
/// <param name="defaultValue">默认值(当键不存在或类型不匹配时返回)</param>
|
||||
/// <returns>用户数据</returns>
|
||||
public T Get<T>(string key, T defaultValue = default!)
|
||||
{
|
||||
if (_context.TryGetValue(key, out var obj) && obj is T value)
|
||||
return value;
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试获取用户自定义数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="key">数据键</param>
|
||||
/// <param name="value">输出值</param>
|
||||
/// <returns>是否成功获取</returns>
|
||||
public bool TryGet<T>(string key, out T value)
|
||||
{
|
||||
if (_context.TryGetValue(key, out var obj) && obj is T t)
|
||||
{
|
||||
value = t;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置用户自定义数据
|
||||
/// </summary>
|
||||
/// <typeparam name="T">数据类型</typeparam>
|
||||
/// <param name="key">数据键</param>
|
||||
/// <param name="value">数据值</param>
|
||||
public void Set<T>(string key, T value)
|
||||
{
|
||||
_context[key] = value!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否存在指定的用户数据键
|
||||
/// </summary>
|
||||
/// <param name="key">数据键</param>
|
||||
/// <returns>是否存在</returns>
|
||||
public bool Has(string key)
|
||||
{
|
||||
return _context.ContainsKey(key);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定的用户数据
|
||||
/// </summary>
|
||||
/// <param name="key">数据键</param>
|
||||
/// <returns>是否成功移除</returns>
|
||||
public bool Remove(string key)
|
||||
{
|
||||
return _context.Remove(key);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI切换处理器执行选项
|
||||
/// </summary>
|
||||
public record UiTransitionHandlerOptions(int TimeoutMs = 0, bool ContinueOnError = true);
|
||||
319
GFramework.Game/ui/UiRouterBase.cs
Normal file
319
GFramework.Game/ui/UiRouterBase.cs
Normal file
@ -0,0 +1,319 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// UI路由类,提供页面栈管理功能
|
||||
/// </summary>
|
||||
public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
{
|
||||
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("UiRouterBase");
|
||||
|
||||
/// <summary>
|
||||
/// UI切换处理器管道
|
||||
/// </summary>
|
||||
private readonly UiTransitionPipeline _pipeline = new();
|
||||
|
||||
/// <summary>
|
||||
/// 页面栈,用于管理UI页面的显示顺序
|
||||
/// </summary>
|
||||
private readonly Stack<IPageBehavior> _stack = new();
|
||||
|
||||
/// <summary>
|
||||
/// UI工厂实例,用于创建UI相关的对象
|
||||
/// </summary>
|
||||
private IUiFactory _factory = null!;
|
||||
|
||||
private IUiRoot _uiRoot = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 注册UI切换处理器
|
||||
/// </summary>
|
||||
/// <param name="handler">处理器实例</param>
|
||||
/// <param name="options">执行选项</param>
|
||||
public void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null)
|
||||
{
|
||||
_pipeline.RegisterHandler(handler, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销UI切换处理器
|
||||
/// </summary>
|
||||
/// <param name="handler">处理器实例</param>
|
||||
public void UnregisterHandler(IUiTransitionHandler handler)
|
||||
{
|
||||
_pipeline.UnregisterHandler(handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绑定UI根节点
|
||||
/// </summary>
|
||||
public void BindRoot(IUiRoot root)
|
||||
{
|
||||
_uiRoot = root;
|
||||
Log.Debug("Bind UI Root: {0}", root.GetType().Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定UI页面压入栈顶并显示
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI页面标识符</param>
|
||||
/// <param name="param">页面进入参数,可为空</param>
|
||||
/// <param name="policy">页面切换策略</param>
|
||||
public void Push(
|
||||
string uiKey,
|
||||
IUiPageEnterParam? param = null,
|
||||
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive
|
||||
)
|
||||
{
|
||||
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);
|
||||
|
||||
DoPushInternal(uiKey, param, policy);
|
||||
|
||||
AfterChange(@event);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 弹出栈顶页面并根据策略处理页面
|
||||
/// </summary>
|
||||
/// <param name="policy">弹出策略,默认为销毁策略</param>
|
||||
public void Pop(UiPopPolicy policy = UiPopPolicy.Destroy)
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
{
|
||||
Log.Debug("Pop ignored: stack is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
var nextUiKey = _stack.Count > 1
|
||||
? _stack.ElementAt(1).View.GetType().Name
|
||||
: string.Empty;
|
||||
var @event = CreateEvent(nextUiKey, UiTransitionType.Pop);
|
||||
|
||||
BeforeChange(@event);
|
||||
|
||||
DoPopInternal(policy);
|
||||
|
||||
AfterChange(@event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 替换当前所有页面为新页面
|
||||
/// </summary>
|
||||
/// <param name="uiKey">新UI页面标识符</param>
|
||||
/// <param name="param">页面进入参数,可为空</param>
|
||||
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
|
||||
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
|
||||
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);
|
||||
DoPushInternal(uiKey, param, pushPolicy);
|
||||
|
||||
AfterChange(@event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有页面栈中的页面
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化方法,在页面初始化时获取UI工厂实例
|
||||
/// </summary>
|
||||
protected override void OnInit()
|
||||
{
|
||||
_factory = this.GetUtility<IUiFactory>()!;
|
||||
Log.Debug("UiRouterBase initialized. Factory={0}", _factory.GetType().Name);
|
||||
RegisterHandlers();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册默认的UI切换处理器
|
||||
/// </summary>
|
||||
protected abstract void RegisterHandlers();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前栈顶UI的key
|
||||
/// </summary>
|
||||
/// <returns>当前UI key,如果栈为空则返回空字符串</returns>
|
||||
private string GetCurrentUiKey()
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
return string.Empty;
|
||||
var page = _stack.Peek();
|
||||
return page.View.GetType().Name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建UI切换事件
|
||||
/// </summary>
|
||||
private UiTransitionEvent CreateEvent(
|
||||
string toUiKey,
|
||||
UiTransitionType type,
|
||||
UiTransitionPolicy? policy = null,
|
||||
IUiPageEnterParam? param = null
|
||||
)
|
||||
{
|
||||
return new UiTransitionEvent
|
||||
{
|
||||
FromUiKey = GetCurrentUiKey(),
|
||||
ToUiKey = toUiKey,
|
||||
TransitionType = type,
|
||||
Policy = policy ?? UiTransitionPolicy.Exclusive,
|
||||
EnterParam = param
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行UI切换前的Handler(阻塞)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行UI切换后的Handler(不阻塞)
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行Push的核心逻辑(不触发Pipeline)
|
||||
/// </summary>
|
||||
private void DoPushInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy)
|
||||
{
|
||||
if (_stack.Count > 0)
|
||||
{
|
||||
var current = _stack.Peek();
|
||||
Log.Debug("Pause current page: {0}", current.GetType().Name);
|
||||
current.OnPause();
|
||||
|
||||
if (policy == UiTransitionPolicy.Exclusive)
|
||||
{
|
||||
Log.Debug("Hide current page (Exclusive): {0}", current.GetType().Name);
|
||||
current.OnHide();
|
||||
}
|
||||
}
|
||||
|
||||
var page = _factory.Create(uiKey);
|
||||
Log.Debug("Create UI Page instance: {0}", page.GetType().Name);
|
||||
|
||||
_uiRoot.AddUiPage(page);
|
||||
_stack.Push(page);
|
||||
|
||||
Log.Debug(
|
||||
"Enter & Show page: {0}, stackAfter={1}",
|
||||
page.GetType().Name, _stack.Count
|
||||
);
|
||||
|
||||
page.OnEnter(param);
|
||||
page.OnShow();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行Pop的核心逻辑(不触发Pipeline)
|
||||
/// </summary>
|
||||
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
|
||||
);
|
||||
|
||||
top.OnExit();
|
||||
|
||||
if (policy == UiPopPolicy.Destroy)
|
||||
{
|
||||
Log.Debug("Destroy UI Page: {0}", top.GetType().Name);
|
||||
_uiRoot.RemoveUiPage(top);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("Hide UI Page: {0}", top.GetType().Name);
|
||||
top.OnHide();
|
||||
}
|
||||
|
||||
if (_stack.Count > 0)
|
||||
{
|
||||
var next = _stack.Peek();
|
||||
Log.Debug("Resume & Show page: {0}", next.GetType().Name);
|
||||
next.OnResume();
|
||||
next.OnShow();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Debug("UI stack is now empty");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行Clear的核心逻辑(不触发Pipeline)
|
||||
/// </summary>
|
||||
private void DoClearInternal(UiPopPolicy policy)
|
||||
{
|
||||
Log.Debug("Clear UI Stack internal, count={0}", _stack.Count);
|
||||
while (_stack.Count > 0)
|
||||
DoPopInternal(policy);
|
||||
}
|
||||
}
|
||||
168
GFramework.Game/ui/UiTransitionPipeline.cs
Normal file
168
GFramework.Game/ui/UiTransitionPipeline.cs
Normal file
@ -0,0 +1,168 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.logging;
|
||||
using GFramework.Game.Abstractions.enums;
|
||||
using GFramework.Game.Abstractions.ui;
|
||||
|
||||
namespace GFramework.Game.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI切换处理器管道,负责管理和执行UI切换扩展点
|
||||
/// </summary>
|
||||
public class UiTransitionPipeline
|
||||
{
|
||||
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("UiTransitionPipeline");
|
||||
private readonly List<IUiTransitionHandler> _handlers = new();
|
||||
private readonly Dictionary<IUiTransitionHandler, UiTransitionHandlerOptions> _options = new();
|
||||
|
||||
/// <summary>
|
||||
/// 注册UI切换处理器
|
||||
/// </summary>
|
||||
/// <param name="handler">处理器实例</param>
|
||||
/// <param name="options">执行选项</param>
|
||||
public void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(handler);
|
||||
|
||||
if (_handlers.Contains(handler))
|
||||
{
|
||||
Log.Debug("Handler already registered: {0}", handler.GetType().Name);
|
||||
return;
|
||||
}
|
||||
|
||||
_handlers.Add(handler);
|
||||
_options[handler] = options ?? new UiTransitionHandlerOptions();
|
||||
Log.Debug(
|
||||
"Handler registered: {0}, Priority={1}, Phases={2}, TimeoutMs={3}",
|
||||
handler.GetType().Name,
|
||||
handler.Priority,
|
||||
handler.Phases,
|
||||
_options[handler].TimeoutMs
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销UI切换处理器
|
||||
/// </summary>
|
||||
/// <param name="handler">处理器实例</param>
|
||||
public void UnregisterHandler(IUiTransitionHandler handler)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(handler);
|
||||
|
||||
if (!_handlers.Remove(handler)) return;
|
||||
_options.Remove(handler);
|
||||
Log.Debug("Handler unregistered: {0}", handler.GetType().Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行指定阶段的所有Handler
|
||||
/// </summary>
|
||||
/// <param name="event">UI切换事件</param>
|
||||
/// <param name="phases">执行阶段</param>
|
||||
/// <param name="cancellationToken">取消令牌</param>
|
||||
/// <returns>异步任务</returns>
|
||||
public async Task ExecuteAsync(
|
||||
UiTransitionEvent @event,
|
||||
UITransitionPhases phases,
|
||||
CancellationToken cancellationToken = default
|
||||
)
|
||||
{
|
||||
@event.Set("Phases", phases.ToString());
|
||||
|
||||
Log.Debug(
|
||||
"Execute pipeline: Phases={0}, From={1}, To={2}, Type={3}, HandlerCount={4}",
|
||||
phases,
|
||||
@event.FromUiKey,
|
||||
@event.ToUiKey,
|
||||
@event.TransitionType,
|
||||
_handlers.Count
|
||||
);
|
||||
|
||||
var sortedHandlers = FilterAndSortHandlers(@event, phases);
|
||||
|
||||
if (sortedHandlers.Count == 0)
|
||||
{
|
||||
Log.Debug("No handlers to execute for phases: {0}", phases);
|
||||
return;
|
||||
}
|
||||
|
||||
Log.Debug(
|
||||
"Executing {0} handlers for phases {1}",
|
||||
sortedHandlers.Count,
|
||||
phases
|
||||
);
|
||||
|
||||
foreach (var handler in sortedHandlers)
|
||||
{
|
||||
var options = _options[handler];
|
||||
await ExecuteSingleHandlerAsync(handler, options, @event, cancellationToken);
|
||||
}
|
||||
|
||||
Log.Debug("Pipeline execution completed for phases: {0}", phases);
|
||||
}
|
||||
|
||||
private List<IUiTransitionHandler> FilterAndSortHandlers(
|
||||
UiTransitionEvent @event,
|
||||
UITransitionPhases phases)
|
||||
{
|
||||
return _handlers
|
||||
.Where(h => h.Phases.HasFlag(phases) && h.ShouldHandle(@event, phases))
|
||||
.OrderBy(h => h.Priority)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static async Task ExecuteSingleHandlerAsync(
|
||||
IUiTransitionHandler handler,
|
||||
UiTransitionHandlerOptions options,
|
||||
UiTransitionEvent @event,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
Log.Debug(
|
||||
"Executing handler: {0}, Priority={1}",
|
||||
handler.GetType().Name,
|
||||
handler.Priority
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
using var timeoutCts = options.TimeoutMs > 0
|
||||
? new CancellationTokenSource(options.TimeoutMs)
|
||||
: null;
|
||||
|
||||
using var linkedCts = timeoutCts != null && cancellationToken.CanBeCanceled
|
||||
? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token)
|
||||
: null;
|
||||
|
||||
await handler.HandleAsync(
|
||||
@event,
|
||||
linkedCts?.Token ?? cancellationToken
|
||||
).ConfigureAwait(false);
|
||||
|
||||
Log.Debug("Handler completed: {0}", handler.GetType().Name);
|
||||
}
|
||||
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Log.Error(
|
||||
"Handler timeout: {0}, TimeoutMs={1}",
|
||||
handler.GetType().Name,
|
||||
options.TimeoutMs
|
||||
);
|
||||
|
||||
if (options.ContinueOnError) return;
|
||||
Log.Error("Stopping pipeline due to timeout and ContinueOnError=false");
|
||||
throw;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
Log.Debug("Handler cancelled: {0}", handler.GetType().Name);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Handler failed: {0}, Error: {1}", handler.GetType().Name, ex.Message);
|
||||
|
||||
if (options.ContinueOnError) return;
|
||||
Log.Error("Stopping pipeline due to error and ContinueOnError=false");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
GFramework.Game/ui/handler/LoggingTransitionHandler.cs
Normal file
48
GFramework.Game/ui/handler/LoggingTransitionHandler.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.logging;
|
||||
using GFramework.Game.Abstractions.enums;
|
||||
using GFramework.Game.Abstractions.ui;
|
||||
|
||||
namespace GFramework.Game.ui.handler;
|
||||
|
||||
/// <summary>
|
||||
/// 日志UI切换处理器,用于记录UI切换的详细信息
|
||||
/// </summary>
|
||||
public sealed class LoggingTransitionHandler : UiTransitionHandlerBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 日志记录器实例,用于记录UI切换相关信息
|
||||
/// </summary>
|
||||
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("LoggingTransitionHandler");
|
||||
|
||||
/// <summary>
|
||||
/// 获取处理器优先级,数值越大优先级越高
|
||||
/// </summary>
|
||||
public override int Priority => 999;
|
||||
|
||||
/// <summary>
|
||||
/// 获取处理器处理的UI切换阶段,处理所有阶段
|
||||
/// </summary>
|
||||
public override UITransitionPhases Phases => UITransitionPhases.All;
|
||||
|
||||
/// <summary>
|
||||
/// 处理UI切换事件的异步方法
|
||||
/// </summary>
|
||||
/// <param name="event">UI切换事件对象,包含切换的相关信息</param>
|
||||
/// <param name="cancellationToken">取消令牌,用于控制异步操作的取消</param>
|
||||
/// <returns>表示异步操作的任务</returns>
|
||||
public override Task HandleAsync(UiTransitionEvent @event, CancellationToken cancellationToken)
|
||||
{
|
||||
// 记录UI切换的详细信息到日志
|
||||
Log.Info(
|
||||
"UI Transition: Phases={0}, Type={1}, From={2}, To={3}, Policy={4}",
|
||||
@event.Get<string>("Phases", "Unknown"),
|
||||
@event.TransitionType,
|
||||
@event.FromUiKey,
|
||||
@event.ToUiKey,
|
||||
@event.Policy
|
||||
);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
33
GFramework.Game/ui/handler/UiTransitionHandlerBase.cs
Normal file
33
GFramework.Game/ui/handler/UiTransitionHandlerBase.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using GFramework.Game.Abstractions.enums;
|
||||
using GFramework.Game.Abstractions.ui;
|
||||
|
||||
namespace GFramework.Game.ui.handler;
|
||||
|
||||
/// <summary>
|
||||
/// UI切换处理器抽象基类,提供一些默认实现
|
||||
/// </summary>
|
||||
public abstract class UiTransitionHandlerBase : IUiTransitionHandler
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理器适用的阶段,默认为所有阶段
|
||||
/// </summary>
|
||||
public virtual UITransitionPhases Phases => UITransitionPhases.All;
|
||||
|
||||
/// <summary>
|
||||
/// 优先级,需要在子类中实现
|
||||
/// </summary>
|
||||
public abstract int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否应该处理当前事件,默认返回true
|
||||
/// </summary>
|
||||
public virtual bool ShouldHandle(UiTransitionEvent @event, UITransitionPhases phases)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理UI切换事件,需要在子类中实现
|
||||
/// </summary>
|
||||
public abstract Task HandleAsync(UiTransitionEvent @event, CancellationToken cancellationToken);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user