using System; using System.Collections.Generic; using DG.Tweening; using TMPro; using UnityEngine; using UnityEngine.Events; namespace Ichni.Menu.UI { /// /// 可复用的多选弹窗元素。调用方提供标题、内容与任意数量的 , /// 本类动态实例化对应按钮,并确保一次显示最多只会执行一个选项回调。 /// public class SelectionBox : MonoBehaviour { [Header("Selection Box")] public CanvasGroup canvasGroup; public TMP_Text titleText; public TMP_Text contentText; public RectTransform optionContainer; public SelectionBoxButton optionButtonPrefab; private readonly List _generatedButtons = new(); private bool _isResolved; /// /// 选项被选择并且自身淡出结束后触发。页面容器使用它销毁本次生成的 SelectionBox。 /// public event Action Closed; private void Awake() { canvasGroup ??= GetComponent(); } /// /// 根据运行时选项初始化弹窗。至少需要一个选项;失败时返回 false,且不会显示半配置的弹窗。 /// public bool SetUp(string title, string content, IReadOnlyList options) { if (canvasGroup == null || optionContainer == null || optionButtonPrefab == null || options == null || options.Count == 0) { Debug.LogWarning("[SelectionBox] 缺少 CanvasGroup、选项容器、选项按钮预制体,或未提供任何选项。"); return false; } ClearGeneratedButtons(); _isResolved = false; if (titleText != null) titleText.text = title ?? string.Empty; if (contentText != null) contentText.text = content ?? string.Empty; foreach (SelectionBoxOption option in options) { SelectionBoxButton button = Instantiate(optionButtonPrefab, optionContainer); _generatedButtons.Add(button); button.SetUp(option.label, () => Select(option.action)); } gameObject.SetActive(true); canvasGroup.alpha = 0f; canvasGroup.interactable = true; canvasGroup.blocksRaycasts = true; canvasGroup.DOFade(1f, 0.2f).Play(); return true; } private void Select(UnityAction action) { if (_isResolved) return; _isResolved = true; // 先禁用所有输入,再执行回调。回调可以触发存档、场景切换或打开下一层 UI, // 因而不能让同一帧内的其它按钮再次触发。 canvasGroup.interactable = false; canvasGroup.blocksRaycasts = false; foreach (SelectionBoxButton button in _generatedButtons) { if (button?.button != null) button.button.interactable = false; } action?.Invoke(); canvasGroup.DOFade(0f, 0.2f).OnComplete(() => Closed?.Invoke(this)).Play(); } private void ClearGeneratedButtons() { foreach (SelectionBoxButton button in _generatedButtons) { if (button != null) Destroy(button.gameObject); } _generatedButtons.Clear(); } } /// /// SelectionBox 的单个运行时选项。它不序列化到场景或存档;显示时由调用方临时创建。 /// public readonly struct SelectionBoxOption { public readonly string label; public readonly UnityAction action; public SelectionBoxOption(string label, UnityAction action) { this.label = label; this.action = action; } } }