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;
[Header("导航栈")]
[Tooltip("被压栈时,正下方面板的处理方式:Replace 停用下层(整屏切换);Modal 保留下层可见但屏蔽其交互(对话框)。")]
[SerializeField] protected PushMode _defaultMode = PushMode.Replace;
[Tooltip("是否允许 ESC / 手柄 B 取消本面板(栈顶时)。确认破坏性操作的根面板可关闭。")]
[SerializeField] protected bool _canCancel = true;
/// 被压栈方式( 未显式指定 mode 时采用)。
public PushMode DefaultMode => _defaultMode;
/// 栈顶时 ESC / 手柄 B 是否可取消本面板。
public bool CanCancel => _canCancel;
/// 键盘 / 手柄默认焦点项( 优先,否则 )。供导航器出/入栈聚焦。
public GameObject FirstSelectableGO
=> _firstSelected != null ? _firstSelected.gameObject : ResolveFirstSelected();
/// 事件订阅容器,OnDisable 自动清理。子类订阅用 channel.Subscribe(..).AddTo(_subs)。
protected readonly CompositeDisposable _subs = new();
private Coroutine _fadeRoutine;
// ── 生命周期 ──────────────────────────────────────────────────────────
protected virtual void OnEnable()
{
if (_canvasGroup == null) _canvasGroup = GetComponent(); // 惰性解析(兼容运行时适配/未连线)
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() { }
// ── 导航栈交互屏蔽 ────────────────────────────────────────────────────
///
/// 切换本面板"是否参与交互/导航"。导航器把模态对话框压在本面板之上时,
/// 以此屏蔽本面板( interactable+blocksRaycasts=false)——
/// 子控件 Selectable.IsInteractable() 随之为 false,自动退出 Unity 导航图,
/// 杜绝方向键穿透到下层(无 CanvasGroup 时为空操作)。
///
public void SetInteractableLayer(bool on)
{
if (_canvasGroup == null) return;
_canvasGroup.interactable = on;
_canvasGroup.blocksRaycasts = on;
}
// ── 焦点 ──────────────────────────────────────────────────────────────
/// 本面板重新成为栈顶(上层出栈)时调用:默认聚焦首项。
public virtual void OnFocusGained() => FocusFirst();
/// 本面板被上层压栈覆盖时调用(可选钩子,默认无操作)。
public virtual void OnFocusLost() { }
/// 兼容旧 路径:等价于 。
public virtual void OnFocusRestored() => OnFocusGained();
/// 将 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));
}
}
}