This commit is contained in:
2026-06-07 11:49:55 +08:00
parent ff0f3bde54
commit 1897658a00
98 changed files with 9903 additions and 13907 deletions

View File

@@ -36,6 +36,23 @@ namespace BaseGames.UI
[Tooltip("打开时是否自动把焦点设到首项。")]
[SerializeField] protected bool _selectFirstOnEnable = true;
[Header("导航栈")]
[Tooltip("被压栈时正下方面板的处理方式Replace 停用下层整屏切换Modal 保留下层可见但屏蔽其交互(对话框)。")]
[SerializeField] protected PushMode _defaultMode = PushMode.Replace;
[Tooltip("是否允许 ESC / 手柄 B 取消本面板(栈顶时)。确认破坏性操作的根面板可关闭。")]
[SerializeField] protected bool _canCancel = true;
/// <summary>被压栈方式(<see cref="IUINavigator.Push"/> 未显式指定 mode 时采用)。</summary>
public PushMode DefaultMode => _defaultMode;
/// <summary>栈顶时 ESC / 手柄 B 是否可取消本面板。</summary>
public bool CanCancel => _canCancel;
/// <summary>键盘 / 手柄默认焦点项(<see cref="_firstSelected"/> 优先,否则 <see cref="ResolveFirstSelected"/>)。供导航器出/入栈聚焦。</summary>
public GameObject FirstSelectableGO
=> _firstSelected != null ? _firstSelected.gameObject : ResolveFirstSelected();
/// <summary>事件订阅容器OnDisable 自动清理。子类订阅用 <c>channel.Subscribe(..).AddTo(_subs)</c>。</summary>
protected readonly CompositeDisposable _subs = new();
@@ -44,6 +61,7 @@ namespace BaseGames.UI
// ── 生命周期 ──────────────────────────────────────────────────────────
protected virtual void OnEnable()
{
if (_canvasGroup == null) _canvasGroup = GetComponent<CanvasGroup>(); // 惰性解析(兼容运行时适配/未连线)
OnPanelOpen();
if (_canvasGroup != null && _fadeInDuration > 0f) PlayFadeIn();
if (_selectFirstOnEnable) FocusFirst();
@@ -62,9 +80,29 @@ namespace BaseGames.UI
/// <summary>面板关闭时调用OnDisable_subs 已清理之后)。子类在此释放非 _subs 资源。</summary>
protected virtual void OnPanelClose() { }
// ── 导航栈交互屏蔽 ────────────────────────────────────────────────────
/// <summary>
/// 切换本面板"是否参与交互/导航"。导航器把模态对话框压在本面板之上时,
/// 以此屏蔽本面板(<see cref="CanvasGroup"/> interactable+blocksRaycasts=false——
/// 子控件 <c>Selectable.IsInteractable()</c> 随之为 false自动退出 Unity 导航图,
/// 杜绝方向键穿透到下层(无 CanvasGroup 时为空操作)。
/// </summary>
public void SetInteractableLayer(bool on)
{
if (_canvasGroup == null) return;
_canvasGroup.interactable = on;
_canvasGroup.blocksRaycasts = on;
}
// ── 焦点 ──────────────────────────────────────────────────────────────
/// <summary>关闭子面板、本面板恢复为栈顶时调用(<see cref="IUIManager.CloseTopPanel"/> 触发)。</summary>
public virtual void OnFocusRestored() => FocusFirst();
/// <summary>本面板重新成为栈顶(上层出栈)时调用:默认聚焦首项。</summary>
public virtual void OnFocusGained() => FocusFirst();
/// <summary>本面板被上层压栈覆盖时调用(可选钩子,默认无操作)。</summary>
public virtual void OnFocusLost() { }
/// <summary>兼容旧 <see cref="IFocusable"/> 路径:等价于 <see cref="OnFocusGained"/>。</summary>
public virtual void OnFocusRestored() => OnFocusGained();
/// <summary>将 EventSystem 焦点设到首项(优先 <see cref="_firstSelected"/>,否则 <see cref="ResolveFirstSelected"/>)。</summary>
protected void FocusFirst()