refactor(ui): 将CanvasItemUiPageBehavior重构为分层行为基类和工厂模式

- 移除原有的CanvasItemUiPageBehavior类
- 创建CanvasItemUiPageBehaviorBase抽象基类,统一管理生命周期逻辑
- 实现PageLayerUiPageBehavior处理页面层UI行为
- 实现OverlayLayerUiPageBehavior处理覆盖层UI行为
- 实现ModalLayerUiPageBehavior处理模态层UI行为
- 实现ToastLayerUiPageBehavior处理Toast层UI行为
- 实现TopmostLayerUiPageBehavior处理顶层UI行为
- 创建UiPageBehaviorFactory工厂类按层级创建对应的行为实例
- 添加Apache License 2.0版权声明到所有新文件
This commit is contained in:
GeWuYou 2026-02-07 21:28:15 +08:00
parent 83a268690f
commit 35a06d2565
8 changed files with 496 additions and 134 deletions

View File

@ -1,134 +0,0 @@
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
using GFramework.Godot.extensions;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// 控制 UI 页面行为的泛型行为类,
/// 支持所有继承自 CanvasItem 的节点
/// </summary>
/// <typeparam name="T">CanvasItem 类型的视图节点</typeparam>
public class CanvasItemUiPageBehavior<T>(T owner, string key) : IUiPageBehavior
where T : CanvasItem
{
private readonly IUiPage? _page = owner as IUiPage;
/// <summary>
/// 获取当前UI层的类型。
/// 返回值表示当前页面所属的UI层级此处固定返回UiLayer.Page。
/// </summary>
public UiLayer Layer => UiLayer.Page;
/// <summary>
/// 判断当前Page是否允许重入。
/// 返回值为false表示该Page不支持重入操作。
/// </summary>
public bool IsReentrant => false;
/// <summary>
/// 获取页面视图对象
/// </summary>
/// <returns>返回与当前实例关联的视图对象</returns>
public object View => owner;
/// <summary>
/// 获取当前实例的键值标识符
/// </summary>
/// <returns>返回用于标识当前实例的键字符串</returns>
public string Key => key;
/// <summary>
/// 获取页面是否存活状态
/// </summary>
/// <returns>如果页面节点有效则返回true否则返回false</returns>
public bool IsAlive => owner.IsValidNode();
/// <summary>
/// 获取页面是否可见状态
/// </summary>
/// <returns>如果页面可见则返回true否则返回false</returns>
public bool IsVisible => owner.Visible;
/// <summary>
/// 页面进入时调用
/// </summary>
/// <param name="param">页面进入参数</param>
public void OnEnter(IUiPageEnterParam? param)
{
_page?.OnEnter(param);
}
/// <summary>
/// 页面退出时调用
/// </summary>
public void OnExit()
{
_page?.OnExit();
owner.QueueFreeX();
}
/// <summary>
/// 页面暂停时调用
/// </summary>
public void OnPause()
{
_page?.OnPause();
// 暂停节点的处理、物理处理和输入处理
if (!BlocksInput) return;
owner.SetProcess(false);
owner.SetPhysicsProcess(false);
owner.SetProcessInput(false);
}
/// <summary>
/// 页面恢复时调用
/// </summary>
public void OnResume()
{
if (owner.IsInvalidNode())
{
return;
}
_page?.OnResume();
// 恢复节点的处理、物理处理和输入处理
owner.SetProcess(true);
owner.SetPhysicsProcess(true);
owner.SetProcessInput(true);
}
/// <summary>
/// 页面隐藏时调用
/// </summary>
public void OnHide()
{
_page?.OnHide();
owner.Hide();
}
/// <summary>
/// 页面显示时调用
/// </summary>
public void OnShow()
{
_page?.OnShow();
owner.Show();
OnResume();
}
/// <summary>
/// 获取或设置页面是否为模态对话框
/// </summary>
public bool IsModal { get; set; }
/// <summary>
/// 获取或设置页面是否阻止输入
/// </summary>
public bool BlocksInput { get; set; } = true;
}

View File

@ -0,0 +1,185 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
using GFramework.Godot.extensions;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// UI 页面行为基类,封装通用的生命周期管理逻辑。
/// 提供对 CanvasItem 类型视图节点的统一管理,包括显示、隐藏、进入、退出等操作。
/// </summary>
/// <typeparam name="T">CanvasItem 类型的视图节点。</typeparam>
public abstract class CanvasItemUiPageBehaviorBase<T> : IUiPageBehavior
where T : CanvasItem
{
/// <summary>
/// UI 的唯一标识键。
/// </summary>
private readonly string _key;
/// <summary>
/// IUiPage 接口引用(如果视图实现了该接口)。
/// </summary>
private readonly IUiPage? _page;
/// <summary>
/// 视图节点的所有者实例。
/// </summary>
protected readonly T Owner;
/// <summary>
/// 初始化 CanvasItemUiPageBehaviorBase 实例。
/// </summary>
/// <param name="owner">视图节点的所有者实例。</param>
/// <param name="key">UI 的唯一标识键。</param>
protected CanvasItemUiPageBehaviorBase(T owner, string key)
{
Owner = owner;
_key = key;
_page = owner as IUiPage;
}
#region -
/// <summary>
/// 获取 UI 所属的层级。
/// 由子类指定具体值。
/// </summary>
public abstract UiLayer Layer { get; }
/// <summary>
/// 获取是否支持重入。
/// 由子类指定具体值。
/// </summary>
public abstract bool IsReentrant { get; }
/// <summary>
/// 获取是否为模态窗口。
/// 由子类指定默认值。
/// </summary>
public abstract bool IsModal { get; }
/// <summary>
/// 获取是否阻止下层输入。
/// 由子类指定默认值。
/// </summary>
public abstract bool BlocksInput { get; }
#endregion
#region
/// <summary>
/// 获取视图节点实例。
/// </summary>
public object View => Owner;
/// <summary>
/// 获取 UI 的唯一标识键。
/// </summary>
public string Key => _key;
/// <summary>
/// 获取视图节点是否有效。
/// </summary>
public bool IsAlive => Owner.IsValidNode();
/// <summary>
/// 获取视图节点是否可见。
/// </summary>
public bool IsVisible => Owner.Visible;
#endregion
#region
/// <summary>
/// 当 UI 进入时调用。
/// 默认调用 IUiPage 接口的 OnEnter 方法(如果存在)。
/// </summary>
/// <param name="param">进入参数。</param>
public virtual void OnEnter(IUiPageEnterParam? param)
{
_page?.OnEnter(param);
}
/// <summary>
/// 当 UI 退出时调用。
/// 默认调用 IUiPage 接口的 OnExit 方法(如果存在),并释放视图节点。
/// </summary>
public virtual void OnExit()
{
_page?.OnExit();
Owner.QueueFreeX();
}
/// <summary>
/// 当 UI 暂停时调用。
/// 默认调用 IUiPage 接口的 OnPause 方法(如果存在),并根据 BlocksInput 决定是否暂停处理逻辑。
/// </summary>
public virtual void OnPause()
{
_page?.OnPause();
// 只有阻止输入的 UI 才需要暂停处理
if (!BlocksInput) return;
Owner.SetProcess(false);
Owner.SetPhysicsProcess(false);
Owner.SetProcessInput(false);
}
/// <summary>
/// 当 UI 恢复时调用。
/// 默认调用 IUiPage 接口的 OnResume 方法(如果存在),并恢复处理逻辑。
/// </summary>
public virtual void OnResume()
{
if (Owner.IsInvalidNode())
return;
_page?.OnResume();
// 恢复处理
Owner.SetProcess(true);
Owner.SetPhysicsProcess(true);
Owner.SetProcessInput(true);
}
/// <summary>
/// 当 UI 隐藏时调用。
/// 默认调用 IUiPage 接口的 OnHide 方法(如果存在),并隐藏视图节点。
/// </summary>
public virtual void OnHide()
{
_page?.OnHide();
Owner.Hide();
}
/// <summary>
/// 当 UI 显示时调用。
/// 默认调用 IUiPage 接口的 OnShow 方法(如果存在),并显示视图节点,同时触发恢复逻辑。
/// </summary>
public virtual void OnShow()
{
_page?.OnShow();
Owner.Show();
OnResume();
}
#endregion
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Game.Abstractions.enums;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// 模态层 UI 行为 - 可重入但需谨慎,带遮罩阻止下层交互
/// </summary>
public class ModalLayerUiPageBehavior<T>(T owner, string key) : CanvasItemUiPageBehaviorBase<T>(owner, key)
where T : CanvasItem
{
public override UiLayer Layer => UiLayer.Modal;
public override bool IsReentrant => true; // ✅ 支持重入(如多层确认弹窗)
public override bool IsModal => true; // 模态窗口
public override bool BlocksInput => true; // 必须阻止下层交互
/// <summary>
/// 模态窗口显示时,可以添加遮罩逻辑
/// </summary>
public override void OnShow()
{
base.OnShow();
// TODO: 可在此添加半透明遮罩层
// AddModalMask();
}
public override void OnHide()
{
// TODO: 移除遮罩层
// RemoveModalMask();
base.OnHide();
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// 浮层 UI 行为类,用于管理覆盖层、对话框等 UI 元素。
/// 该行为支持可重入性,适用于需要叠加显示的场景。
/// </summary>
/// <typeparam name="T">UI 元素的类型,必须继承自 CanvasItem。</typeparam>
public class OverlayLayerUiPageBehavior<T> : CanvasItemUiPageBehaviorBase<T>
where T : CanvasItem
{
/// <summary>
/// 初始化 OverlayLayerUiPageBehavior 实例。
/// </summary>
/// <param name="owner">关联的 UI 元素实例。</param>
/// <param name="key">用于标识该行为的唯一键。</param>
public OverlayLayerUiPageBehavior(T owner, string key) : base(owner, key)
{
}
/// <summary>
/// 获取当前 UI 行为所属的层级固定为浮层Overlay
/// </summary>
public override UiLayer Layer => UiLayer.Overlay;
/// <summary>
/// 指示该行为是否支持可重入性,始终返回 true。
/// </summary>
public override bool IsReentrant => true;
/// <summary>
/// 指示该行为是否为模态行为,始终返回 false。
/// </summary>
public override bool IsModal => false;
/// <summary>
/// 指示该行为是否会阻塞输入,始终返回 false。
/// </summary>
public override bool BlocksInput => false;
/// <summary>
/// 当浮层被暂停时调用此方法。
/// 浮层在暂停时不中断处理逻辑(如动画等),仅触发业务层的 OnPause 方法。
/// </summary>
public override void OnPause()
{
// 浮层不暂停处理,保持动画和交互
// 只调用业务层的 OnPause
if (Owner is IUiPage page)
page.OnPause();
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Game.Abstractions.enums;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// 页面层 UI 行为类,用于实现栈式管理的页面行为。
/// 此类继承自 CanvasItemUiPageBehaviorBase提供页面层级的 UI 控制逻辑。
/// 特性包括:不可重入、非模态、阻塞输入。
/// </summary>
/// <typeparam name="T">泛型参数,表示拥有此行为的 CanvasItem 类型。</typeparam>
/// <param name="owner">拥有此行为的 CanvasItem 实例。</param>
/// <param name="key">用于标识此行为的唯一键。</param>
public class PageLayerUiPageBehavior<T>(T owner, string key) : CanvasItemUiPageBehaviorBase<T>(owner, key)
where T : CanvasItem
{
/// <summary>
/// 获取当前 UI 行为所属的层级。
/// 返回值固定为 UiLayer.Page表示页面层级。
/// </summary>
public override UiLayer Layer => UiLayer.Page;
/// <summary>
/// 指示当前 UI 行为是否可重入。
/// 返回值为 false表示不可重入。
/// </summary>
public override bool IsReentrant => false;
/// <summary>
/// 指示当前 UI 行为是否为模态。
/// 返回值为 false表示非模态。
/// </summary>
public override bool IsModal => false;
/// <summary>
/// 指示当前 UI 行为是否会阻塞输入。
/// 返回值为 true表示会阻塞输入。
/// </summary>
public override bool BlocksInput => true;
}

View File

@ -0,0 +1,52 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Game.Abstractions.enums;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// 表示一个用于管理Toast层UI页面行为的泛型类。
/// 此类继承自CanvasItemUiPageBehaviorBase专门用于处理Toast类型的UI层逻辑。
/// </summary>
/// <typeparam name="T">指定的CanvasItem类型表示此类的所有者。</typeparam>
/// <param name="owner">当前UI页面行为的所有者对象必须是CanvasItem的实例。</param>
/// <param name="key">用于标识此UI页面行为的唯一键值。</param>
public class ToastLayerUiPageBehavior<T>(T owner, string key) : CanvasItemUiPageBehaviorBase<T>(owner, key)
where T : CanvasItem
{
/// <summary>
/// 获取当前UI页面行为所属的UI层类型。
/// 对于Toast层此属性始终返回UiLayer.Toast。
/// </summary>
public override UiLayer Layer => UiLayer.Toast;
/// <summary>
/// 指示此UI页面行为是否支持重入即是否允许在已激活状态下再次被调用
/// Toast层通常允许多次触发因此此属性返回true。
/// </summary>
public override bool IsReentrant => true;
/// <summary>
/// 指示此UI页面行为是否为模态即是否会阻止用户与其他UI交互
/// Toast层通常是非模态的因此此属性返回false。
/// </summary>
public override bool IsModal => false;
/// <summary>
/// 指示此UI页面行为是否会阻塞用户输入。
/// Toast层通常不会阻塞输入因此此属性返回false。
/// </summary>
public override bool BlocksInput => false;
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Game.Abstractions.enums;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// 顶层 UI 行为 - 不可重入,最高优先级,用于系统级弹窗
/// </summary>
public class TopmostLayerUiPageBehavior<T>(T owner, string key) : CanvasItemUiPageBehaviorBase<T>(owner, key)
where T : CanvasItem
{
public override UiLayer Layer => UiLayer.Topmost;
public override bool IsReentrant => false; // ❌ 顶层不支持重入
public override bool IsModal => true; // 顶层通常是模态的
public override bool BlocksInput => true; // 必须阻止所有下层交互
/// <summary>
/// 顶层显示时,可以禁用所有下层 UI
/// </summary>
public override void OnShow()
{
base.OnShow();
// TODO: 可在此禁用其他所有层级
// DisableAllLowerLayers();
}
public override void OnHide()
{
// TODO: 恢复其他层级
// EnableAllLowerLayers();
base.OnHide();
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2026 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// UI 页面行为工厂类,根据层级创建对应的 Behavior 实例
/// </summary>
public static class UiPageBehaviorFactory
{
/// <summary>
/// 创建指定层级的 UI 页面行为实例
/// </summary>
/// <typeparam name="T">CanvasItem 类型</typeparam>
/// <param name="owner">视图节点</param>
/// <param name="key">UI 标识键</param>
/// <param name="layer">目标层级</param>
/// <returns>对应层级的 IUiPageBehavior 实例</returns>
public static IUiPageBehavior Create<T>(T owner, string key, UiLayer layer)
where T : CanvasItem
{
return layer switch
{
UiLayer.Page => new PageLayerUiPageBehavior<T>(owner, key),
UiLayer.Overlay => new OverlayLayerUiPageBehavior<T>(owner, key),
UiLayer.Modal => new ModalLayerUiPageBehavior<T>(owner, key),
UiLayer.Toast => new ToastLayerUiPageBehavior<T>(owner, key),
UiLayer.Topmost => new TopmostLayerUiPageBehavior<T>(owner, key),
_ => throw new ArgumentException($"Unsupported UI layer: {layer}")
};
}
}