using UnityEngine; using UnityEngine.UI; using UnityEngine.EventSystems; using BaseGames.Core; using BaseGames.Core.Events; namespace BaseGames.UI { /// /// UI 面板基类:统一面板生命周期、焦点恢复、进场淡入与事件订阅清理。 /// /// 消除各面板控制器(PauseMenu / Settings / InventoryHub 等)重复的样板: /// - OnEnable:取服务 / 订阅 / 设默认选中 / 播淡入 /// - OnDisable:清理订阅()/ 停动画 /// - :关闭子面板回到栈顶时聚焦首项 /// /// 与 面板栈完全兼容:UIManager 仍以 SetActive 驱动开关, /// 本基类只在 OnEnable/OnDisable 加钩子,不改变栈语义。 /// /// 子类用法:重写 (订阅/刷新)与可选 ; /// 在 Inspector 指定 (手柄/键盘默认焦点);可选挂 CanvasGroup 做淡入。 /// [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; /// 事件订阅容器,OnDisable 自动清理。子类订阅用 channel.Subscribe(..).AddTo(_subs) 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(); } /// 面板打开时调用(OnEnable)。子类在此取服务、订阅事件、刷新内容。 protected virtual void OnPanelOpen() { } /// 面板关闭时调用(OnDisable,_subs 已清理之后)。子类在此释放非 _subs 资源。 protected virtual void OnPanelClose() { } // ── 焦点 ────────────────────────────────────────────────────────────── /// 关闭子面板、本面板恢复为栈顶时调用( 触发)。 public virtual void OnFocusRestored() => FocusFirst(); /// 将 EventSystem 焦点设到首项(优先 ,否则 )。 protected void FocusFirst() { var go = _firstSelected != null ? _firstSelected.gameObject : ResolveFirstSelected(); if (go != null && EventSystem.current != null) EventSystem.current.SetSelectedGameObject(go); } /// 子类可重写:动态决定首个选中项(如列表第一行),当 未指定时使用。 protected virtual GameObject ResolveFirstSelected() => null; // ── 工具 ────────────────────────────────────────────────────────────── /// 从 ServiceLocator 取服务(未注册返回 null)。 protected static T GetService() where T : class => ServiceLocator.GetOrDefault(); private void PlayFadeIn() { if (_fadeRoutine != null) StopCoroutine(_fadeRoutine); _canvasGroup.alpha = 0f; _fadeRoutine = StartCoroutine(UITween.FadeCanvasGroup(_canvasGroup, 1f, _fadeInDuration)); } } }