2026-05-10 11:47:55 -04:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using SLSUtilities.General;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
|
|
namespace SLSUtilities.UI
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 栈式 UI 页面管理器。
|
|
|
|
|
|
/// 管理所有打开的 UIPageBase 页面,提供输入阻塞事件。
|
|
|
|
|
|
/// ESC 关闭栈顶由游戏层负责调用 CloseTopPage()。
|
|
|
|
|
|
/// 需要放置在场景中的 GameObject 上(Singleton)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public class UIPageManager : Singleton<UIPageManager>
|
|
|
|
|
|
{
|
2026-06-12 17:11:39 -04:00
|
|
|
|
// ──────────────────── 动态页面 ────────────────────
|
|
|
|
|
|
[Header("Dynamic Pages")]
|
|
|
|
|
|
[Tooltip("确认 / 提示弹窗的 Prefab(需挂载 ConfirmUIPage 组件)。")]
|
|
|
|
|
|
[SerializeField] private GameObject confirmPagePrefab;
|
|
|
|
|
|
|
|
|
|
|
|
[Tooltip("动态页面实例化的父节点(通常是 Canvas 下的某个 Transform)。")]
|
|
|
|
|
|
[SerializeField] private Transform dynamicPageParent;
|
|
|
|
|
|
|
|
|
|
|
|
// ──────────────────── 页面栈 ────────────────────
|
2026-05-10 11:47:55 -04:00
|
|
|
|
private readonly List<UIPageBase> openPages = new List<UIPageBase>();
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 输入阻塞状态变化时触发。
|
|
|
|
|
|
/// true = 首个页面打开(应阻塞游戏输入),false = 所有页面已关闭(应恢复输入)。
|
|
|
|
|
|
/// 游戏层通过订阅此事件联动 isCursorLocked 等状态。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public event Action<bool> OnInputBlockChanged;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>当前是否有打开的页面。</summary>
|
|
|
|
|
|
public bool HasOpenPages => openPages.Count > 0;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>当前栈顶页面,若无则返回 null。</summary>
|
|
|
|
|
|
public UIPageBase TopPage => openPages.Count > 0 ? openPages[^1] : null;
|
|
|
|
|
|
|
2026-06-12 17:11:39 -04:00
|
|
|
|
/// <summary>确认页面的 Prefab 引用(以 GameObject 形式暴露,避免跨 Assembly 类型依赖)。</summary>
|
|
|
|
|
|
public GameObject ConfirmPagePrefab => confirmPagePrefab;
|
|
|
|
|
|
|
|
|
|
|
|
// ──────────────────── 页面注册 ────────────────────
|
|
|
|
|
|
|
2026-05-10 11:47:55 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将页面注册到栈顶。若为首个页面则触发 OnInputBlockChanged(true)。
|
|
|
|
|
|
/// 由 UIPageBase.Open() 内部调用,不要手动调用。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void RegisterPage(UIPageBase page)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (openPages.Contains(page)) return;
|
|
|
|
|
|
|
|
|
|
|
|
bool wasEmpty = openPages.Count == 0;
|
|
|
|
|
|
openPages.Add(page);
|
|
|
|
|
|
|
|
|
|
|
|
if (wasEmpty)
|
|
|
|
|
|
{
|
|
|
|
|
|
OnInputBlockChanged?.Invoke(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 将页面从栈中移除。若栈清空则触发 OnInputBlockChanged(false)。
|
|
|
|
|
|
/// 由 UIPageBase.Close() 内部调用,不要手动调用。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public void UnregisterPage(UIPageBase page)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!openPages.Remove(page)) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (openPages.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
OnInputBlockChanged?.Invoke(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-12 17:11:39 -04:00
|
|
|
|
// ──────────────────── 页面关闭 ────────────────────
|
|
|
|
|
|
|
2026-05-10 11:47:55 -04:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 关闭栈顶页面(若其允许 ESC 关闭)。由游戏层的 ESC 处理逻辑调用。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <returns>true 表示成功关闭了一个页面。</returns>
|
|
|
|
|
|
public bool CloseTopPage()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (openPages.Count == 0) return false;
|
|
|
|
|
|
|
|
|
|
|
|
UIPageBase top = openPages[^1];
|
|
|
|
|
|
if (top.CloseOnEsc)
|
|
|
|
|
|
{
|
|
|
|
|
|
top.Close();
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>关闭所有打开的页面。</summary>
|
|
|
|
|
|
public void CloseAllPages()
|
|
|
|
|
|
{
|
|
|
|
|
|
for (int i = openPages.Count - 1; i >= 0; i--)
|
|
|
|
|
|
{
|
|
|
|
|
|
openPages[i].Close();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-06-12 17:11:39 -04:00
|
|
|
|
|
|
|
|
|
|
// ──────────────────── 动态页面实例化 ────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 在 <see cref="dynamicPageParent"/> 下实例化一个动态页面 Prefab。
|
|
|
|
|
|
/// 用于创建运行时弹窗(确认框、提示框等)。
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="prefab">要实例化的 Prefab。</param>
|
|
|
|
|
|
/// <returns>实例化后的 GameObject。</returns>
|
|
|
|
|
|
public GameObject InstantiateDynamicPage(GameObject prefab)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (prefab == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("[UIPageManager] Cannot instantiate a null prefab.");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Transform parent = dynamicPageParent != null ? dynamicPageParent : transform;
|
|
|
|
|
|
return Instantiate(prefab, parent);
|
|
|
|
|
|
}
|
2026-05-10 11:47:55 -04:00
|
|
|
|
}
|
|
|
|
|
|
}
|