@@ -0,0 +1,91 @@
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 ) ) ;
}
}
}