115 lines
4.0 KiB
C#
115 lines
4.0 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using DG.Tweening;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.Events;
|
||
|
||
namespace Ichni.Menu.UI
|
||
{
|
||
/// <summary>
|
||
/// 可复用的多选弹窗元素。调用方提供标题、内容与任意数量的 <see cref="SelectionBoxOption"/>,
|
||
/// 本类动态实例化对应按钮,并确保一次显示最多只会执行一个选项回调。
|
||
/// </summary>
|
||
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<SelectionBoxButton> _generatedButtons = new();
|
||
private bool _isResolved;
|
||
|
||
/// <summary>
|
||
/// 选项被选择并且自身淡出结束后触发。页面容器使用它销毁本次生成的 SelectionBox。
|
||
/// </summary>
|
||
public event Action<SelectionBox> Closed;
|
||
|
||
private void Awake()
|
||
{
|
||
canvasGroup ??= GetComponent<CanvasGroup>();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 根据运行时选项初始化弹窗。至少需要一个选项;失败时返回 false,且不会显示半配置的弹窗。
|
||
/// </summary>
|
||
public bool SetUp(string title, string content, IReadOnlyList<SelectionBoxOption> 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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// SelectionBox 的单个运行时选项。它不序列化到场景或存档;显示时由调用方临时创建。
|
||
/// </summary>
|
||
public readonly struct SelectionBoxOption
|
||
{
|
||
public readonly string label;
|
||
public readonly UnityAction action;
|
||
|
||
public SelectionBoxOption(string label, UnityAction action)
|
||
{
|
||
this.label = label;
|
||
this.action = action;
|
||
}
|
||
}
|
||
}
|