feat(ui): 添加UI层级管理和Godot平台实现

- 在IUiRoot接口中添加Z-order控制和页面层级管理功能
- 实现Godot平台的UiRoot,支持UI页面的添加、移除和层级排序
- 添加UiLayer枚举定义不同UI层级(Page、Overlay、Modal、Toast、Topmost)
- 在IUiRouter中扩展层级管理方法,支持指定层级显示UI
- 实现UiRouterBase中的层级管理逻辑,包括显示、隐藏、清空等操作
- 添加对GodotSharp包的引用以支持Godot平台功能
This commit is contained in:
GeWuYou 2026-01-20 09:14:37 +08:00
parent 760cc71985
commit c9f01f5877
6 changed files with 376 additions and 1 deletions

View File

@ -0,0 +1,33 @@
namespace GFramework.Game.Abstractions.enums;
/// <summary>
/// UI层级枚举定义UI界面的显示层级
/// 用于管理不同类型的UI在屏幕上的显示顺序
/// </summary>
public enum UiLayer
{
/// <summary>
/// 页面层使用栈管理UI的切换
/// </summary>
Page = 0,
/// <summary>
/// 浮层,用于覆盖层、对话框等
/// </summary>
Overlay = 10,
/// <summary>
/// 模态层,会阻挡下层交互,带有遮罩效果
/// </summary>
Modal = 20,
/// <summary>
/// 提示层用于轻量提示如toast消息、loading指示器等
/// </summary>
Toast = 30,
/// <summary>
/// 顶层,用于系统级弹窗、全屏加载等
/// </summary>
Topmost = 40
}

View File

@ -1,3 +1,5 @@
using System.Collections.Generic;
namespace GFramework.Game.Abstractions.ui;
/// <summary>
@ -11,9 +13,34 @@ public interface IUiRoot
/// <param name="child">要添加的UI页面子节点</param>
void AddUiPage(IUiPageBehavior child);
/// <summary>
/// 向UI根节点添加子页面到指定层级
/// </summary>
/// <param name="child">要添加的UI页面子节点</param>
/// <param name="zOrder">Z-order值用于控制UI显示层级</param>
void AddUiPage(IUiPageBehavior child, int zOrder = 0);
/// <summary>
/// 从UI根节点移除子页面
/// </summary>
/// <param name="child">要移除的UI页面子节点</param>
void RemoveUiPage(IUiPageBehavior child);
/// <summary>
/// 设置页面的Z-order层级顺序
/// </summary>
/// <param name="page">UI页面</param>
/// <param name="zOrder">Z-order值</param>
void SetZOrder(IUiPageBehavior page, int zOrder);
/// <summary>
/// 获取当前所有显示的页面
/// </summary>
/// <returns>所有显示的页面列表</returns>
IReadOnlyList<IUiPageBehavior> GetVisiblePages();
/// <summary>
/// 强制刷新UI层级排序
/// </summary>
void RefreshLayerOrder();
}

View File

@ -114,4 +114,58 @@ public interface IUiRouter : ISystem
/// 判断指定UI是否存在于UI栈中
/// </summary>
bool Contains(string uiKey);
#region
/// <summary>
/// 在指定层级显示UI非栈管理
/// </summary>
/// <param name="uiKey">UI标识符</param>
/// <param name="layer">UI层级</param>
/// <param name="param">进入参数</param>
/// <param name="instancePolicy">实例策略</param>
void Show(
string uiKey,
UiLayer layer,
IUiPageEnterParam? param = null,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
/// <summary>
/// 在指定层级显示UI基于实例
/// </summary>
/// <param name="page">UI页面实例</param>
/// <param name="layer">UI层级</param>
void Show(IUiPageBehavior page, UiLayer layer);
/// <summary>
/// 隐藏指定层级的UI
/// </summary>
/// <param name="uiKey">UI标识符</param>
/// <param name="layer">UI层级</param>
/// <param name="destroy">是否销毁实例</param>
void Hide(string uiKey, UiLayer layer, bool destroy = false);
/// <summary>
/// 清空指定层级的所有UI
/// </summary>
/// <param name="layer">UI层级</param>
/// <param name="destroy">是否销毁实例</param>
void ClearLayer(UiLayer layer, bool destroy = false);
/// <summary>
/// 获取指定层级的UI实例
/// </summary>
/// <param name="uiKey">UI标识符</param>
/// <param name="layer">UI层级</param>
/// <returns>UI实例不存在则返回null</returns>
IUiPageBehavior? GetFromLayer(string uiKey, UiLayer layer);
/// <summary>
/// 判断指定层级是否有UI显示
/// </summary>
/// <param name="layer">UI层级</param>
/// <returns>是否有UI显示</returns>
bool HasVisibleInLayer(UiLayer layer);
#endregion
}

View File

@ -4,6 +4,7 @@ using GFramework.Core.logging;
using GFramework.Core.system;
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
using System.Linq;
namespace GFramework.Game.ui;
@ -24,6 +25,11 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// </summary>
private readonly Stack<IUiPageBehavior> _stack = new();
/// <summary>
/// 层级管理非栈层级用于Overlay、Modal、Toast等浮层
/// </summary>
private readonly Dictionary<UiLayer, Dictionary<string, IUiPageBehavior>> _layers = new();
/// <summary>
/// UI工厂实例用于创建UI相关的对象
/// </summary>
@ -434,4 +440,135 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
while (_stack.Count > 0)
DoPopInternal(policy);
}
#region
/// <summary>
/// 在指定层级显示UI非栈管理
/// </summary>
public void Show(
string uiKey,
Game.Abstractions.enums.UiLayer layer,
IUiPageEnterParam? param = null,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
{
if (layer == Game.Abstractions.enums.UiLayer.Page)
{
throw new ArgumentException("Use Push() for Page layer");
}
// 初始化层级字典
if (!_layers.ContainsKey(layer))
_layers[layer] = new Dictionary<string, IUiPageBehavior>();
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.GetOrCreate(uiKey, instancePolicy);
layerDict[uiKey] = page;
// 添加到UiRoot传入层级Z-order
_uiRoot.AddUiPage(page, (int)layer);
page.OnEnter(param);
page.OnShow();
Log.Debug("Show UI in layer: {0}, layer={1}", uiKey, layer);
}
/// <summary>
/// 在指定层级显示UI基于实例
/// </summary>
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<string, IUiPageBehavior>();
_layers[layer][uiKey] = page;
_uiRoot.AddUiPage(page, (int)layer);
page.OnShow();
Log.Debug("Show existing UI instance in layer: {0}, layer={1}", uiKey, layer);
}
/// <summary>
/// 隐藏指定层级的UI
/// </summary>
public void Hide(string uiKey, Game.Abstractions.enums.UiLayer layer, bool destroy = false)
{
if (!_layers.TryGetValue(layer, out var layerDict))
return;
if (!layerDict.TryGetValue(uiKey, out var page))
return;
page.OnExit();
page.OnHide();
if (destroy)
{
_uiRoot.RemoveUiPage(page);
layerDict.Remove(uiKey);
Log.Debug("Hide & Destroy UI from layer: {0}, layer={1}", uiKey, layer);
}
else
{
_uiRoot.RemoveUiPage(page);
_factory.Recycle(page);
layerDict.Remove(uiKey);
Log.Debug("Hide & Cache UI from layer: {0}, layer={1}", uiKey, layer);
}
}
/// <summary>
/// 清空指定层级的所有UI
/// </summary>
public void ClearLayer(Game.Abstractions.enums.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);
}
/// <summary>
/// 获取指定层级的UI实例
/// </summary>
public IUiPageBehavior? GetFromLayer(string uiKey, UiLayer layer)
{
return _layers.TryGetValue(layer, out var layerDict) &&
layerDict.TryGetValue(uiKey, out var page)
? page
: null;
}
/// <summary>
/// 判断指定层级是否有UI显示
/// </summary>
public bool HasVisibleInLayer(UiLayer layer)
{
return _layers.TryGetValue(layer, out var layerDict) && layerDict.Count > 0;
}
#endregion
}

View File

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
@ -9,6 +9,7 @@
<ItemGroup>
<PackageReference Include="Godot.SourceGenerators" Version="4.5.1" PrivateAssets="all"/>
<PackageReference Include="GodotSharp" Version="4.5.1"/>
<PackageReference Include="GodotSharpEditor" Version="4.5.1" PrivateAssets="all"/>
</ItemGroup>

View File

@ -0,0 +1,123 @@
using System.Collections.Generic;
using System;
using GFramework.Game.Abstractions.ui;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// Godot平台的UI根节点实现
/// 用于管理UI页面的添加、移除和层级排序
/// </summary>
public partial class GodotUiRoot : Node, IUiRoot
{
/// <summary>
/// UI节点的父容器所有UI页面都添加到这个节点下
/// </summary>
private Node? _uiContainer;
/// <summary>
/// UI页面的追踪字典记录每个页面的节点
/// </summary>
private readonly Dictionary<IUiPageBehavior, Node> _pageNodes = new();
public override void _Ready()
{
// 创建UI容器节点
_uiContainer = new Node
{
Name = "UiContainer"
};
AddChild(_uiContainer);
}
/// <summary>
/// 向UI根节点添加子页面
/// </summary>
public void AddUiPage(IUiPageBehavior child)
{
if (_uiContainer == null)
throw new InvalidOperationException("UiContainer is not initialized");
var node = GetNodeFromPage(child);
if (node == null)
throw new InvalidOperationException($"Page node is null: {child.Key}");
if (_pageNodes.ContainsKey(child))
return;
_pageNodes[child] = node;
if (node.GetParent() != _uiContainer)
{
_uiContainer.AddChild(node);
}
}
/// <summary>
/// 向UI根节点添加子页面到指定层级
/// </summary>
public void AddUiPage(IUiPageBehavior child, int zOrder = 0)
{
AddUiPage(child);
SetZOrder(child, zOrder);
}
/// <summary>
/// 从UI根节点移除子页面
/// </summary>
public void RemoveUiPage(IUiPageBehavior child)
{
if (!_pageNodes.TryGetValue(child, out var node))
return;
_pageNodes.Remove(child);
if (_uiContainer != null && node.GetParent() == _uiContainer)
{
_uiContainer.RemoveChild(node);
}
}
/// <summary>
/// 设置页面的Z-order层级顺序
/// </summary>
public void SetZOrder(IUiPageBehavior page, int zOrder)
{
if (!_pageNodes.TryGetValue(page, out var node))
return;
if (node is CanvasItem canvasItem)
{
canvasItem.ZIndex = zOrder;
}
}
/// <summary>
/// 获取当前所有显示的页面
/// </summary>
public IReadOnlyList<IUiPageBehavior> GetVisiblePages()
{
return _pageNodes.Keys.ToList().AsReadOnly();
}
/// <summary>
/// 强制刷新UI层级排序
/// </summary>
public void RefreshLayerOrder()
{
if (_uiContainer == null)
return;
_uiContainer.MoveChild(_uiContainer.GetChild(0), 0);
}
/// <summary>
/// 从页面行为获取对应的节点
/// </summary>
private static Node? GetNodeFromPage(IUiPageBehavior page)
{
return page.View as Node;
}
}