using System; using System.Reflection; using System.Text; using SLSUtilities.General; using TMPro; using UnityEngine; using UnityEngine.EventSystems; namespace Cielonos.Settings.UI { /// /// 设置条目 UI 的抽象基类。 /// /// 持有对设置实例和字段的反射引用,子类负责具体的 UI 交互逻辑。 /// 通过 绑定到指定设置实例的字段后, /// 子类在 中配置 UI 组件, /// 在 中同步字段值到 UI 显示。 /// /// /// 支持鼠标悬停时通过 / /// 通知父级显示/隐藏右侧说明面板。 /// /// public abstract class SettingsEntryBase : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler { [SerializeField] protected TMP_Text labelText; protected object settingsInstance; protected FieldInfo fieldInfo; protected Action onValueChanged; /// 条目的显示标题。 public string DisplayLabel { get; protected set; } /// 条目的说明文本(可为空,为空时悬停不触发说明面板)。 public string Description { get; protected set; } /// 鼠标悬停进入时触发,参数为 (标题, 说明文本)。由 SettingsUIPage 设置。 public Action OnHoverEnter { get; set; } /// 鼠标悬停离开时触发。由 SettingsUIPage 设置。 public Action OnHoverExit { get; set; } /// /// 初始化设置条目,绑定到指定设置实例的字段。 /// /// 设置类实例(如 GameplaySettings)。 /// 要绑定的字段反射信息。 /// 字段值变更时的回调。 public virtual void Initialize(object instance, FieldInfo field, Action onChanged) { settingsInstance = instance; fieldInfo = field; onValueChanged = onChanged; // 优先使用 SettingsDisplayAttribute 提供的本地化键 var displayAttr = field.GetCustomAttribute(); 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(); } /// /// 子类实现:配置 UI 组件(如 Slider 的范围、Dropdown 的选项)。 /// 在 中于 之前调用。 /// protected abstract void SetupUI(); /// /// 子类实现:从字段读取当前值并更新 UI 显示。 /// public abstract void RefreshValue(); /// /// 将值写回设置字段,并通知外部值已变更。 /// protected void SetFieldValue(object value) { fieldInfo.SetValue(settingsInstance, value); onValueChanged?.Invoke(); } /// /// 从设置字段读取当前值。 /// 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(); } // ──────────────────── 工具方法 ──────────────────── /// /// 将 camelCase 字段名转换为可读格式。 /// /// cameraSensitivityX → "Camera Sensitivity X"
/// showFPS → "Show FPS"
/// invertYAxis → "Invert Y Axis" ///
///
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(); } } }