using System;
using System.Collections.Generic;
using System.Reflection;
using Cielonos.MainGame;
using Cielonos.Settings;
using Cielonos.Settings.UI;
using SLSUtilities.UI;
using TMPro;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UI;
namespace Cielonos.UI
{
///
/// 设置界面页面(单页滚动式 + 右侧面板)。
///
/// 左侧:所有设置分类在同一页面中按顺序展示,每个分类前插入标题。
/// 通过反射读取各设置类的公共字段,按类型自动选择 Entry Prefab 实例化。
/// 同时支持手动添加的按钮条目(如"Key Bindings"按钮)。
///
///
/// 右侧:包含两种面板状态:
///
/// - 说明面板():鼠标悬停条目时显示说明文本,离开后隐藏。
/// - 详情页面(如 ):点击按钮后打开的二级 UIPage,
/// 打开期间悬停说明被抑制,需手动关闭。
///
///
///
public class SettingsUIPage : UIPageBase
{
// ──────────────────── Entry Prefabs ──────────────────
[Header("Entry Prefabs")]
[Tooltip("bool 字段使用的 Toggle 条目预制体,需挂载 SettingsEntryToggle。")]
[SerializeField] private GameObject toggleEntryPrefab;
[Tooltip("带 [Range] 的 int 字段使用的 Slider 条目预制体,需挂载 SettingsEntrySlider。")]
[SerializeField] private GameObject sliderEntryPrefab;
[Tooltip("enum 字段使用的 Dropdown 条目预制体,需挂载 SettingsEntryDropdown。")]
[SerializeField] private GameObject dropdownEntryPrefab;
[Tooltip("按钮条目预制体,需挂载 SettingsEntryButton。")]
[SerializeField] private GameObject buttonEntryPrefab;
// ──────────────────── Section Header ──────────────────
[Header("Section Header")]
[Tooltip("分类标题预制体,需包含 TMP_Text 组件。")]
[SerializeField] private GameObject sectionHeaderPrefab;
// ──────────────────── Left Content ────────────────────
[Header("Left Content")]
[Tooltip("条目的父容器,应挂载 VerticalLayoutGroup 和 ContentSizeFitter。")]
[SerializeField] private RectTransform contentContainer;
// ──────────────────── Right Panel ─────────────────────
[Header("Right Panel - Description")]
[Tooltip("右侧悬停说明面板,显示条目的标题和说明文本。")]
[SerializeField] private SettingsDescriptionPanel descriptionPanel;
[Header("Right Panel - Detail Pages")]
[Tooltip("键位绑定二级页面。")]
[SerializeField] private KeyBindingPage keyBindingPage;
// ──────────────────── Input ───────────────────────────
[Header("Input")]
[Tooltip("用于键位绑定页面的 InputActionAsset。为空时从 Player 获取。")]
[SerializeField] private InputActionAsset inputActionAsset;
// ──────────────────── 底部按钮 ────────────────────
[Header("Bottom Buttons")]
[SerializeField] private Button resetButton;
[SerializeField] private Button backButton;
// ──────────────────── 运行时状态 ──────────────────
private readonly List activeFieldEntries = new();
private readonly List activeButtonEntries = new();
private readonly List spawnedObjects = new();
/// 是否有详情页面处于打开状态(抑制悬停说明)。
private bool isDetailPageOpen;
// ──────────────────── 分类定义 ────────────────────
///
/// 分类定义:显示名称、设置实例、Apply/Reset 回调、附加按钮列表。
///
private struct SectionDefinition
{
public string displayName;
public object settingsInstance;
public Action applyAction;
public Action resetAction;
public List buttons;
}
///
/// 手动添加的按钮条目定义。
///
private struct ButtonDefinition
{
public string label;
public string description;
public Action onClick;
}
// ──────────────────── 生命周期 ────────────────────
protected override void Start()
{
base.Start();
resetButton?.onClick.AddListener(OnResetClicked);
backButton?.onClick.AddListener(OnBackClicked);
// 监听详情页面的开关状态
if (keyBindingPage != null)
{
keyBindingPage.PageOpened += OnDetailPageOpened;
keyBindingPage.PageClosed += OnDetailPageClosed;
}
}
protected override void OnPageOpened()
{
BuildAllSections();
}
protected override void OnPageClosed()
{
// 先关闭可能打开的详情页面
if (isDetailPageOpen && keyBindingPage != null && keyBindingPage.IsOpen)
keyBindingPage.Close();
ClearAll();
descriptionPanel?.Hide();
GameSettingsManager.Instance?.Save();
}
// ──────────────────── 详情页面状态 ────────────────
private void OnDetailPageOpened()
{
isDetailPageOpen = true;
descriptionPanel?.Hide();
}
private void OnDetailPageClosed()
{
isDetailPageOpen = false;
}
// ──────────────────── 悬停回调 ────────────────────
private void HandleHoverEnter(string title, string description)
{
if (isDetailPageOpen) return;
descriptionPanel?.Show(title, description);
}
private void HandleHoverExit()
{
if (isDetailPageOpen) return;
descriptionPanel?.Hide();
}
// ──────────────────── 分类定义构建 ────────────────
///
/// 构建所有分类定义。扩展时在此处追加。
///
private List BuildSectionDefinitions()
{
var manager = GameSettingsManager.Instance;
if (manager == null) return null;
return new List
{
new()
{
displayName = "Gameplay",
settingsInstance = manager.gameplay,
applyAction = manager.ApplyGameplay,
resetAction = manager.ResetGameplayToDefault
},
new()
{
displayName = "Graphics",
settingsInstance = manager.graphics,
applyAction = manager.ApplyGraphics,
resetAction = manager.ResetGraphicsToDefault
},
new()
{
displayName = "Sound",
settingsInstance = manager.sound,
applyAction = manager.ApplySound,
resetAction = manager.ResetSoundToDefault
},
new()
{
displayName = "Controls",
settingsInstance = manager.controls,
applyAction = manager.ApplyControls,
resetAction = manager.ResetControlsToDefault,
buttons = new List
{
new()
{
label = "Key Bindings",
description = "Customize keyboard and mouse bindings for all game actions.",
onClick = OpenKeyBindingPage
}
}
}
};
}
// ──────────────────── 条目生成 ────────────────────
///
/// 构建所有分类的标题、字段条目和按钮条目。
///
private void BuildAllSections()
{
ClearAll();
var sections = BuildSectionDefinitions();
if (sections == null)
{
Debug.LogError("[SettingsUIPage] GameSettingsManager not found.");
return;
}
foreach (var section in sections)
{
BuildSection(section);
}
}
///
/// 构建单个分类的标题、字段条目和按钮条目。
///
private void BuildSection(SectionDefinition section)
{
FieldInfo[] fields = section.settingsInstance.GetType()
.GetFields(BindingFlags.Public | BindingFlags.Instance);
// 预扫描:确认该分类至少有一个可显示的字段或按钮
bool hasVisibleContent = section.buttons is { Count: > 0 };
if (!hasVisibleContent)
{
foreach (FieldInfo field in fields)
{
if (field.GetCustomAttribute() == null
&& ResolvePrefab(field) != null)
{
hasVisibleContent = true;
break;
}
}
}
if (!hasVisibleContent) return;
// 插入分类标题
SpawnSectionHeader(section.displayName);
// 生成字段条目
Action applyAction = section.applyAction;
foreach (FieldInfo field in fields)
{
if (field.GetCustomAttribute() != null)
continue;
GameObject prefab = ResolvePrefab(field);
if (prefab == null)
{
Debug.LogWarning(
$"[SettingsUIPage] No matching prefab for field '{field.Name}' " +
$"(type: {field.FieldType.Name}). Skipping.");
continue;
}
GameObject entryObj = Instantiate(prefab, contentContainer);
var entry = entryObj.GetComponent();
if (entry == null)
{
Debug.LogError(
$"[SettingsUIPage] Prefab for field '{field.Name}' " +
"is missing a SettingsEntryBase component.");
Destroy(entryObj);
continue;
}
entry.Initialize(section.settingsInstance, field, () => applyAction?.Invoke());
RegisterFieldEntryHover(entry);
activeFieldEntries.Add(entry);
spawnedObjects.Add(entryObj);
}
// 生成按钮条目
if (section.buttons != null)
{
foreach (var btnDef in section.buttons)
{
SpawnButtonEntry(btnDef);
}
}
}
///
/// 为字段条目注册悬停回调。
///
private void RegisterFieldEntryHover(SettingsEntryBase entry)
{
entry.OnHoverEnter = HandleHoverEnter;
entry.OnHoverExit = HandleHoverExit;
}
///
/// 实例化按钮条目。
///
private void SpawnButtonEntry(ButtonDefinition def)
{
if (buttonEntryPrefab == null)
{
Debug.LogWarning("[SettingsUIPage] buttonEntryPrefab is not assigned.");
return;
}
GameObject entryObj = Instantiate(buttonEntryPrefab, contentContainer);
var buttonEntry = entryObj.GetComponent();
if (buttonEntry == null)
{
Debug.LogError("[SettingsUIPage] buttonEntryPrefab is missing SettingsEntryButton.");
Destroy(entryObj);
return;
}
buttonEntry.Initialize(def.label, def.description, def.onClick);
buttonEntry.OnHoverEnter = HandleHoverEnter;
buttonEntry.OnHoverExit = HandleHoverExit;
activeButtonEntries.Add(buttonEntry);
spawnedObjects.Add(entryObj);
}
///
/// 实例化分类标题预制体。
///
private void SpawnSectionHeader(string title)
{
if (sectionHeaderPrefab == null)
{
Debug.LogWarning("[SettingsUIPage] sectionHeaderPrefab is not assigned.");
return;
}
GameObject headerObj = Instantiate(sectionHeaderPrefab, contentContainer);
var headerText = headerObj.GetComponentInChildren();
if (headerText != null)
headerText.text = title;
spawnedObjects.Add(headerObj);
}
///
/// 根据字段类型选择对应的预制体。
///
private GameObject ResolvePrefab(FieldInfo field)
{
Type fieldType = field.FieldType;
if (fieldType == typeof(bool))
return toggleEntryPrefab;
if (fieldType == typeof(int))
return sliderEntryPrefab;
if (fieldType.IsEnum)
return dropdownEntryPrefab;
return null;
}
// ──────────────────── 详情页面入口 ────────────────
private void OpenKeyBindingPage()
{
if (keyBindingPage == null)
{
Debug.LogWarning("[SettingsUIPage] keyBindingPage is not assigned.");
return;
}
// 优先使用配置的 InputActionAsset,否则尝试从 Player 获取
InputActionAsset asset = inputActionAsset;
if (asset == null)
{
var player = MainGameManager.Player;
if (player != null)
asset = player.inputSc.inputActions.asset;
}
if (asset != null)
{
keyBindingPage.Open(asset);
}
else
{
Debug.LogError("[SettingsUIPage] No InputActionAsset available for KeyBindingPage.");
}
}
// ──────────────────── 底部按钮回调 ────────────────
private void OnResetClicked()
{
GameSettingsManager.Instance?.ResetAllToDefault();
BuildAllSections();
}
private void OnBackClicked()
{
Close();
}
// ──────────────────── 清理 ────────────────────────
private void ClearAll()
{
foreach (var obj in spawnedObjects)
{
if (obj != null)
Destroy(obj);
}
activeFieldEntries.Clear();
activeButtonEntries.Clear();
spawnedObjects.Clear();
}
private void OnDestroy()
{
if (keyBindingPage != null)
{
keyBindingPage.PageOpened -= OnDetailPageOpened;
keyBindingPage.PageClosed -= OnDetailPageClosed;
}
}
}
}