Files
zeling_v2/Assets/_Game/Scripts/UI/Base/UIPanelBase.cs
2026-06-06 09:00:11 +08:00

92 lines
4.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using BaseGames.Core;
using BaseGames.Core.Events;
namespace BaseGames.UI
{
/// <summary>
/// UI 面板基类:统一面板生命周期、焦点恢复、进场淡入与事件订阅清理。
///
/// 消除各面板控制器PauseMenu / Settings / InventoryHub 等)重复的样板:
/// - OnEnable取服务 / 订阅 / 设默认选中 / 播淡入
/// - OnDisable清理订阅<see cref="_subs"/>/ 停动画
/// - <see cref="IFocusable.OnFocusRestored"/>:关闭子面板回到栈顶时聚焦首项
///
/// 与 <see cref="UIManager"/> 面板栈完全兼容UIManager 仍以 SetActive 驱动开关,
/// 本基类只在 OnEnable/OnDisable 加钩子,不改变栈语义。
///
/// 子类用法:重写 <see cref="OnPanelOpen"/>(订阅/刷新)与可选 <see cref="OnPanelClose"/>
/// 在 Inspector 指定 <see cref="_firstSelected"/>(手柄/键盘默认焦点);可选挂 CanvasGroup 做淡入。
/// </summary>
[DisallowMultipleComponent]
public abstract class UIPanelBase : MonoBehaviour, IFocusable
{
[Header("Panel Base")]
[Tooltip("打开 / 焦点恢复时默认选中的控件(手柄/键盘导航起点)。可空。")]
[SerializeField] protected Selectable _firstSelected;
[Tooltip("进场淡入用的 CanvasGroup可空为空则不淡入。")]
[SerializeField] protected CanvasGroup _canvasGroup;
[Tooltip("进场淡入时长。0 表示无淡入。")]
[SerializeField] protected float _fadeInDuration = 0.15f;
[Tooltip("打开时是否自动把焦点设到首项。")]
[SerializeField] protected bool _selectFirstOnEnable = true;
/// <summary>事件订阅容器OnDisable 自动清理。子类订阅用 <c>channel.Subscribe(..).AddTo(_subs)</c>。</summary>
protected readonly CompositeDisposable _subs = new();
private Coroutine _fadeRoutine;
// ── 生命周期 ──────────────────────────────────────────────────────────
protected virtual void OnEnable()
{
OnPanelOpen();
if (_canvasGroup != null && _fadeInDuration > 0f) PlayFadeIn();
if (_selectFirstOnEnable) FocusFirst();
}
protected virtual void OnDisable()
{
_subs.Clear();
if (_fadeRoutine != null) { StopCoroutine(_fadeRoutine); _fadeRoutine = null; }
OnPanelClose();
}
/// <summary>面板打开时调用OnEnable。子类在此取服务、订阅事件、刷新内容。</summary>
protected virtual void OnPanelOpen() { }
/// <summary>面板关闭时调用OnDisable_subs 已清理之后)。子类在此释放非 _subs 资源。</summary>
protected virtual void OnPanelClose() { }
// ── 焦点 ──────────────────────────────────────────────────────────────
/// <summary>关闭子面板、本面板恢复为栈顶时调用(<see cref="IUIManager.CloseTopPanel"/> 触发)。</summary>
public virtual void OnFocusRestored() => FocusFirst();
/// <summary>将 EventSystem 焦点设到首项(优先 <see cref="_firstSelected"/>,否则 <see cref="ResolveFirstSelected"/>)。</summary>
protected void FocusFirst()
{
var go = _firstSelected != null ? _firstSelected.gameObject : ResolveFirstSelected();
if (go != null && EventSystem.current != null)
EventSystem.current.SetSelectedGameObject(go);
}
/// <summary>子类可重写:动态决定首个选中项(如列表第一行),当 <see cref="_firstSelected"/> 未指定时使用。</summary>
protected virtual GameObject ResolveFirstSelected() => null;
// ── 工具 ──────────────────────────────────────────────────────────────
/// <summary>从 ServiceLocator 取服务(未注册返回 null。</summary>
protected static T GetService<T>() where T : class => ServiceLocator.GetOrDefault<T>();
private void PlayFadeIn()
{
if (_fadeRoutine != null) StopCoroutine(_fadeRoutine);
_canvasGroup.alpha = 0f;
_fadeRoutine = StartCoroutine(UITween.FadeCanvasGroup(_canvasGroup, 1f, _fadeInDuration));
}
}
}