Files
Cielonos/Assets/Scripts/Settings/UI/SettingsEntryBase.cs
SoulliesOfficial 6d7ebc5825 Passion & UI
2026-06-12 17:11:39 -04:00

161 lines
5.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Reflection;
using System.Text;
using SLSUtilities.General;
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
namespace Cielonos.Settings.UI
{
/// <summary>
/// 设置条目 UI 的抽象基类。
/// <para>
/// 持有对设置实例和字段的反射引用,子类负责具体的 UI 交互逻辑。
/// 通过 <see cref="Initialize"/> 绑定到指定设置实例的字段后,
/// 子类在 <see cref="SetupUI"/> 中配置 UI 组件,
/// 在 <see cref="RefreshValue"/> 中同步字段值到 UI 显示。
/// </para>
/// <para>
/// 支持鼠标悬停时通过 <see cref="OnHoverEnter"/> / <see cref="OnHoverExit"/>
/// 通知父级显示/隐藏右侧说明面板。
/// </para>
/// </summary>
public abstract class SettingsEntryBase : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler
{
[SerializeField] protected TMP_Text labelText;
protected object settingsInstance;
protected FieldInfo fieldInfo;
protected Action onValueChanged;
/// <summary>条目的显示标题。</summary>
public string DisplayLabel { get; protected set; }
/// <summary>条目的说明文本(可为空,为空时悬停不触发说明面板)。</summary>
public string Description { get; protected set; }
/// <summary>鼠标悬停进入时触发,参数为 (标题, 说明文本)。由 SettingsUIPage 设置。</summary>
public Action<string, string> OnHoverEnter { get; set; }
/// <summary>鼠标悬停离开时触发。由 SettingsUIPage 设置。</summary>
public Action OnHoverExit { get; set; }
/// <summary>
/// 初始化设置条目,绑定到指定设置实例的字段。
/// </summary>
/// <param name="instance">设置类实例(如 GameplaySettings。</param>
/// <param name="field">要绑定的字段反射信息。</param>
/// <param name="onChanged">字段值变更时的回调。</param>
public virtual void Initialize(object instance, FieldInfo field, Action onChanged)
{
settingsInstance = instance;
fieldInfo = field;
onValueChanged = onChanged;
// 优先使用 SettingsDisplayAttribute 提供的本地化键
var displayAttr = field.GetCustomAttribute<SettingsDisplayAttribute>();
if (displayAttr != null)
{
string localized = displayAttr.LabelKey.Localize(displayAttr.TableName);
DisplayLabel = !string.IsNullOrEmpty(localized) ? localized : displayAttr.LabelKey;
if (!string.IsNullOrEmpty(displayAttr.DescriptionKey))
{
string localizedDesc = displayAttr.DescriptionKey.Localize(displayAttr.TableName);
Description = !string.IsNullOrEmpty(localizedDesc)
? localizedDesc
: displayAttr.DescriptionKey;
}
}
else
{
DisplayLabel = FormatFieldName(field.Name);
}
SetLabel(DisplayLabel);
SetupUI();
RefreshValue();
}
/// <summary>
/// 子类实现:配置 UI 组件(如 Slider 的范围、Dropdown 的选项)。
/// 在 <see cref="Initialize"/> 中于 <see cref="RefreshValue"/> 之前调用。
/// </summary>
protected abstract void SetupUI();
/// <summary>
/// 子类实现:从字段读取当前值并更新 UI 显示。
/// </summary>
public abstract void RefreshValue();
/// <summary>
/// 将值写回设置字段,并通知外部值已变更。
/// </summary>
protected void SetFieldValue(object value)
{
fieldInfo.SetValue(settingsInstance, value);
onValueChanged?.Invoke();
}
/// <summary>
/// 从设置字段读取当前值。
/// </summary>
protected object GetFieldValue()
{
return fieldInfo.GetValue(settingsInstance);
}
protected void SetLabel(string text)
{
if (labelText != null)
labelText.text = text;
}
// ──────────────────── 悬停事件 ────────────────────
public void OnPointerEnter(PointerEventData eventData)
{
OnHoverEnter?.Invoke(DisplayLabel, Description);
}
public void OnPointerExit(PointerEventData eventData)
{
OnHoverExit?.Invoke();
}
// ──────────────────── 工具方法 ────────────────────
/// <summary>
/// 将 camelCase 字段名转换为可读格式。
/// <para>
/// <c>cameraSensitivityX</c> → "Camera Sensitivity X"<br/>
/// <c>showFPS</c> → "Show FPS"<br/>
/// <c>invertYAxis</c> → "Invert Y Axis"
/// </para>
/// </summary>
protected static string FormatFieldName(string fieldName)
{
if (string.IsNullOrEmpty(fieldName)) return fieldName;
var sb = new StringBuilder(fieldName.Length + 4);
sb.Append(char.ToUpper(fieldName[0]));
for (int i = 1; i < fieldName.Length; i++)
{
char c = fieldName[i];
if (char.IsUpper(c))
{
bool prevIsLower = char.IsLower(fieldName[i - 1]);
bool nextIsLower = i + 1 < fieldName.Length && char.IsLower(fieldName[i + 1]);
if (prevIsLower || nextIsLower)
sb.Append(' ');
}
sb.Append(c);
}
return sb.ToString();
}
}
}