重构反射构建组件,添加材质反射组件
This commit is contained in:
+110
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 材质属性访问器 — 通过 Material.Get/Set 系列方法读写 Shader 属性
|
||||
/// </summary>
|
||||
public class MaterialPropertyAccessor : IAvrgValueAccessor
|
||||
{
|
||||
private readonly Material _material;
|
||||
private readonly string _propName;
|
||||
private readonly ShaderPropertyType _shaderType;
|
||||
|
||||
public Type ValueType { get; }
|
||||
public bool IsReadOnly { get; }
|
||||
|
||||
public MaterialPropertyAccessor(
|
||||
Material material,
|
||||
string propName,
|
||||
ShaderPropertyType shaderType,
|
||||
Type valueType,
|
||||
bool isReadOnly = false)
|
||||
{
|
||||
_material = material;
|
||||
_propName = propName;
|
||||
_shaderType = shaderType;
|
||||
ValueType = valueType;
|
||||
IsReadOnly = isReadOnly;
|
||||
}
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
if (_material == null) return null;
|
||||
|
||||
return _shaderType switch
|
||||
{
|
||||
ShaderPropertyType.Float or ShaderPropertyType.Range => _material.GetFloat(_propName),
|
||||
ShaderPropertyType.Int => _material.GetInt(_propName),
|
||||
ShaderPropertyType.Color => _material.GetColor(_propName),
|
||||
ShaderPropertyType.Vector => _material.GetVector(_propName),
|
||||
ShaderPropertyType.Texture => _material.GetTexture(_propName),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public void SetValue(object value)
|
||||
{
|
||||
if (IsReadOnly || _material == null) return;
|
||||
|
||||
switch (_shaderType)
|
||||
{
|
||||
case ShaderPropertyType.Float:
|
||||
case ShaderPropertyType.Range:
|
||||
_material.SetFloat(_propName, Convert.ToSingle(value));
|
||||
break;
|
||||
case ShaderPropertyType.Int:
|
||||
_material.SetInt(_propName, Convert.ToInt32(value));
|
||||
break;
|
||||
case ShaderPropertyType.Color:
|
||||
_material.SetColor(_propName, (Color)value);
|
||||
break;
|
||||
case ShaderPropertyType.Vector:
|
||||
_material.SetVector(_propName, (Vector4)value);
|
||||
break;
|
||||
case ShaderPropertyType.Texture:
|
||||
_material.SetTexture(_propName, value as Texture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public string FormatDisplay(object value)
|
||||
{
|
||||
if (value == null) return "None";
|
||||
if (value is Texture tex && tex != null) return tex.name;
|
||||
return value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 材质属性访问器工厂 — 持有 Shader 属性名和类型,创建时绑定 Material 实例
|
||||
/// </summary>
|
||||
public class MaterialPropertyAccessorFactory : IAvrgValueAccessorFactory
|
||||
{
|
||||
private readonly string _propName;
|
||||
private readonly ShaderPropertyType _shaderType;
|
||||
private readonly Type _valueType;
|
||||
private readonly bool _isReadOnly;
|
||||
|
||||
public MaterialPropertyAccessorFactory(
|
||||
string propName,
|
||||
ShaderPropertyType shaderType,
|
||||
Type valueType,
|
||||
bool isReadOnly = false)
|
||||
{
|
||||
_propName = propName;
|
||||
_shaderType = shaderType;
|
||||
_valueType = valueType;
|
||||
_isReadOnly = isReadOnly;
|
||||
}
|
||||
|
||||
public IAvrgValueAccessor CreateAccessor(object targetInstance)
|
||||
{
|
||||
var material = targetInstance as Material;
|
||||
if (material == null) return null;
|
||||
return new MaterialPropertyAccessor(material, _propName, _shaderType, _valueType, _isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 反射值访问器 — 通过 FieldInfo / PropertyInfo 读写 C# 对象成员
|
||||
/// </summary>
|
||||
public class ReflectionValueAccessor : IAvrgValueAccessor
|
||||
{
|
||||
private readonly MemberInfo _memberInfo;
|
||||
private object _targetInstance;
|
||||
|
||||
public Type ValueType { get; }
|
||||
public bool IsReadOnly { get; }
|
||||
|
||||
public ReflectionValueAccessor(MemberInfo memberInfo, object targetInstance)
|
||||
{
|
||||
_memberInfo = memberInfo;
|
||||
_targetInstance = targetInstance;
|
||||
|
||||
ValueType = memberInfo switch
|
||||
{
|
||||
FieldInfo fi => fi.FieldType,
|
||||
PropertyInfo pi => pi.PropertyType,
|
||||
_ => typeof(object)
|
||||
};
|
||||
|
||||
IsReadOnly = memberInfo is PropertyInfo pi && !pi.CanWrite;
|
||||
}
|
||||
|
||||
public object GetValue()
|
||||
{
|
||||
if (_targetInstance == null) return null;
|
||||
return _memberInfo switch
|
||||
{
|
||||
FieldInfo fi => fi.GetValue(_targetInstance),
|
||||
PropertyInfo pi => pi.GetValue(_targetInstance),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
public void SetValue(object value)
|
||||
{
|
||||
if (IsReadOnly || _targetInstance == null) return;
|
||||
switch (_memberInfo)
|
||||
{
|
||||
case FieldInfo fi:
|
||||
fi.SetValue(_targetInstance, value);
|
||||
break;
|
||||
case PropertyInfo pi:
|
||||
pi.SetValue(_targetInstance, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public string FormatDisplay(object value) => value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 反射值访问器工厂 — 持有 MemberInfo,创建时绑定 TargetInstance
|
||||
/// </summary>
|
||||
public class ReflectionAccessorFactory : IAvrgValueAccessorFactory
|
||||
{
|
||||
private readonly MemberInfo _memberInfo;
|
||||
|
||||
public ReflectionAccessorFactory(MemberInfo memberInfo)
|
||||
{
|
||||
_memberInfo = memberInfo;
|
||||
}
|
||||
|
||||
public IAvrgValueAccessor CreateAccessor(object targetInstance)
|
||||
{
|
||||
return new ReflectionValueAccessor(_memberInfo, targetInstance);
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 为 Dropdown UI 提供预设选项
|
||||
///
|
||||
/// 使用示例:
|
||||
/// [XuiavrgFieldDropdownValue("选项A", "选项B", "选项C", MergeMode = PresetOptionsMergeMode.Union)]
|
||||
/// public string quality = "选项A";
|
||||
///
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class XuiavrgFieldDropdownValueAttribute : Attribute, IXuiavrgAttribute
|
||||
{
|
||||
/// <summary>预设选项数组</summary>
|
||||
public readonly string[] Options;
|
||||
|
||||
/// <summary>与已有选项(如 enum 名)的合并模式</summary>
|
||||
public PresetOptionsMergeMode MergeMode { get; set; } = PresetOptionsMergeMode.Replace;
|
||||
|
||||
public XuiavrgFieldDropdownValueAttribute(params string[] options)
|
||||
{
|
||||
Options = options ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void BindMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
if (metaData == null) return;
|
||||
|
||||
metaData.PresetOptions = Options;
|
||||
metaData.OptionsMergeMode = MergeMode;
|
||||
|
||||
// 自动设置 Tag 为 Dropdown
|
||||
if (metaData.Tag == XuiavrgFieldTag.None)
|
||||
metaData.Tag = XuiavrgFieldTag.Dropdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 为 ToggleGroup UI 提供预设选项
|
||||
///
|
||||
/// 使用示例:
|
||||
/// [XuiavrgFieldToggleGroupValue("低", "中", "高", MergeMode = PresetOptionsMergeMode.Replace)]
|
||||
/// public string detailLevel = "中";
|
||||
///
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
|
||||
public class XuiavrgFieldToggleGroupValueAttribute : Attribute, IXuiavrgAttribute
|
||||
{
|
||||
/// <summary>预设选项数组</summary>
|
||||
public readonly string[] Options;
|
||||
|
||||
/// <summary>与已有选项(如 enum 名)的合并模式</summary>
|
||||
public PresetOptionsMergeMode MergeMode { get; set; } = PresetOptionsMergeMode.Replace;
|
||||
|
||||
public XuiavrgFieldToggleGroupValueAttribute(params string[] options)
|
||||
{
|
||||
Options = options ?? Array.Empty<string>();
|
||||
}
|
||||
|
||||
public void BindMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
if (metaData == null) return;
|
||||
|
||||
metaData.PresetOptions = Options;
|
||||
metaData.OptionsMergeMode = MergeMode;
|
||||
|
||||
// 自动设置 Tag 为 ToggleGroup
|
||||
if (metaData.Tag == XuiavrgFieldTag.None)
|
||||
metaData.Tag = XuiavrgFieldTag.ToggleGroup;
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-5
@@ -8,12 +8,11 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// 将一个复合类型(如 Vector2)拆分为多个子属性,分别绑定到不同的 UI 元素
|
||||
///
|
||||
/// 使用示例:
|
||||
/// [XuiavrgFieldEntryGroup("speed")]
|
||||
/// [XuiavrgSplit("x", "最小")]
|
||||
/// [XuiavrgSplit("y", "最大")]
|
||||
/// public Vector2 speedRange = new Vector2(0, 100);
|
||||
///
|
||||
/// 上述代码创建一个 "speed" 编组的 Entry,内部包含两个子绑定:
|
||||
/// 上述代码创建一个编组 Entry,内部包含两个子元数据:
|
||||
/// speedRange.x 绑定到 UI 的"最小"输入,speedRange.y 绑定到"最大"输入
|
||||
/// 子绑定通过 XUIAvrgMDValue.BindingSlots 与预制体上的 UI 组件匹配
|
||||
/// </summary>
|
||||
@@ -39,13 +38,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
public void BindMetaData(AvrgMemberMetaData metaData)
|
||||
{
|
||||
// 创建子元数据
|
||||
// 创建子元数据 — 纯元数据,不持有实例
|
||||
var subMetaData = new AvrgMemberMetaData
|
||||
{
|
||||
MemberName = $"{metaData.MemberName}.{PropertyPath}",
|
||||
PropertyPath = PropertyPath,
|
||||
LabelName = DisplayLabel ?? PropertyPath,
|
||||
TargetInstance = null, // 由运行时闭包解析
|
||||
Hide = metaData.Hide,
|
||||
Required = metaData.Required,
|
||||
FieldOrder = metaData.FieldOrder,
|
||||
@@ -74,7 +72,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
if (parentType == null || string.IsNullOrEmpty(propertyPath))
|
||||
return null;
|
||||
|
||||
// 仅支持单层路径
|
||||
if (propertyPath.Contains('/'))
|
||||
return null;
|
||||
|
||||
|
||||
@@ -1,57 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个成员的元数据数据结构
|
||||
/// 记录从反射 + Attribute 标记中提取的所有信息
|
||||
/// 纯数据描述 — 不持有反射信息(MemberInfo)和实例引用(TargetInstance)
|
||||
///
|
||||
/// 数据的实际读写通过 IAvrgValueAccessorFactory 在 BuildMDVM 阶段创建的 IAvrgValueAccessor 完成。
|
||||
/// 这使得同一个元数据模板可复用于 C# 反射对象、Material Shader 属性等不同数据源。
|
||||
/// </summary>
|
||||
public class AvrgMemberMetaData
|
||||
{
|
||||
// ============ 反射信息 ============
|
||||
// ============ 基本标识 ============
|
||||
|
||||
/// <summary>字段/属性名</summary>
|
||||
/// <summary>字段/属性名(或 Shader 属性名如 "_MainTex")</summary>
|
||||
public string MemberName { get; set; }
|
||||
|
||||
/// <summary>反射信息 (FieldInfo 或 PropertyInfo)</summary>
|
||||
public MemberInfo MemberInfo { get; set; }
|
||||
|
||||
/// <summary>值类型</summary>
|
||||
public Type ValueType { get; set; }
|
||||
|
||||
/// <summary>是否为属性 (false 为字段)</summary>
|
||||
public bool IsProperty { get; set; }
|
||||
|
||||
/// <summary>绑定的目标实例 (可选,Step1 时可空)</summary>
|
||||
public object TargetInstance { get; set; }
|
||||
|
||||
// ============ 拆分属性支持 ============
|
||||
|
||||
/// <summary>
|
||||
/// 子属性路径(仅拆分属性有效)
|
||||
/// 用于从父值中访问子字段/属性,如 "x"、"y"、"width"
|
||||
/// </summary>
|
||||
public string PropertyPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拆分子元数据列表
|
||||
/// 由 [XuiavrgSplit] 特性填充,每个子元数据对应一个拆分属性
|
||||
/// </summary>
|
||||
public List<AvrgMemberMetaData> SubMetaDataList { get; set; }
|
||||
|
||||
// ============ 从 IXuiavrgAttribute 融合的元数据 ============
|
||||
|
||||
/// <summary>显示标签</summary>
|
||||
/// <summary>UI 显示标签</summary>
|
||||
public string LabelName { get; set; }
|
||||
|
||||
/// <summary>单位文本</summary>
|
||||
/// <summary>单位文本(如 "m/s")</summary>
|
||||
public string UnitLabelName { get; set; }
|
||||
|
||||
/// <summary>格式化字符串(如 "{0:F2}")</summary>
|
||||
public string FormatText { get; set; }
|
||||
|
||||
/// <summary>值类型</summary>
|
||||
public Type ValueType { get; set; }
|
||||
|
||||
// ============ UI 映射 ============
|
||||
|
||||
/// <summary>功能标记</summary>
|
||||
public XuiavrgFieldTag Tag { get; set; } = XuiavrgFieldTag.None;
|
||||
|
||||
@@ -67,21 +46,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>是否在 UI 中隐藏</summary>
|
||||
public bool Hide { get; set; }
|
||||
|
||||
/// <summary>值变更时触发的反射调用方法名</summary>
|
||||
public string OnTriggerCall { get; set; }
|
||||
// ============ 默认值与验证 ============
|
||||
|
||||
/// <summary>
|
||||
/// 类的初始默认值(由类字段初始化器决定)
|
||||
/// 例如 public float speed = 10f; → DefaultValue = 10f
|
||||
/// </summary>
|
||||
/// <summary>默认值(用于 UI 初始化和回退参考)</summary>
|
||||
public object DefaultValue { get; set; }
|
||||
|
||||
// ============ 属性检查 (由 [PropertyCheck] 填充) ============
|
||||
|
||||
/// <summary>
|
||||
/// 判断依据标识
|
||||
/// 默认使用 ValueType.FullName 与 AvrgValueRangeConfig 中的条目匹配
|
||||
/// </summary>
|
||||
/// <summary>属性检查依据标识(默认使用 ValueType.FullName)</summary>
|
||||
public string PropertyCheckKey { get; set; }
|
||||
|
||||
/// <summary>是否必填</summary>
|
||||
@@ -99,47 +69,46 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>是否启用无效值状态检查</summary>
|
||||
public bool ValidateInvalid { get; set; }
|
||||
|
||||
// ============ 原始 Attribute 引用 ============
|
||||
/// <summary>Shader Range 属性下限(null 表示非 Range 属性)</summary>
|
||||
public float? RangeMin { get; set; }
|
||||
|
||||
/// <summary>Shader Range 属性上限(null 表示非 Range 属性)</summary>
|
||||
public float? RangeMax { get; set; }
|
||||
|
||||
// ============ 拆分属性支持 ============
|
||||
|
||||
/// <summary>
|
||||
/// 收集到的所有 IXuiavrgAttribute 列表
|
||||
/// 保留原始引用,供扩展和自定义处理使用
|
||||
/// 子属性路径(仅拆分属性有效)
|
||||
/// 用于从父值中访问子字段/属性,如 "x"、"y"、"width"
|
||||
/// </summary>
|
||||
public string PropertyPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拆分子元数据列表
|
||||
/// 由 [XuiavrgSplit] 特性填充
|
||||
/// </summary>
|
||||
public List<AvrgMemberMetaData> SubMetaDataList { get; set; }
|
||||
|
||||
// ============ 预设选项 (Dropdown/ToggleGroup) ============
|
||||
|
||||
/// <summary>预设选项列表</summary>
|
||||
public IReadOnlyList<string> PresetOptions { get; set; }
|
||||
|
||||
/// <summary>预设选项合并模式</summary>
|
||||
public PresetOptionsMergeMode OptionsMergeMode { get; set; }
|
||||
|
||||
// ============ 扩展 ============
|
||||
|
||||
/// <summary>收集到的所有 IXuiavrgAttribute 列表</summary>
|
||||
public List<IXuiavrgAttribute> Attributes { get; set; } = new List<IXuiavrgAttribute>();
|
||||
|
||||
/// <summary>
|
||||
/// 从绑定目标获取值
|
||||
/// </summary>
|
||||
public object GetValue()
|
||||
{
|
||||
if (TargetInstance == null) return null;
|
||||
switch (MemberInfo)
|
||||
{
|
||||
case PropertyInfo pi:
|
||||
return pi.GetValue(TargetInstance);
|
||||
case FieldInfo fi:
|
||||
return fi.GetValue(TargetInstance);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// ============ ★ 核心变更 — 值访问器工厂 ============
|
||||
|
||||
/// <summary>
|
||||
/// 设置值到绑定目标
|
||||
/// 值访问器工厂
|
||||
/// 在元数据提取阶段设置(持有 MemberInfo 或 Shader 属性信息)
|
||||
/// 在 BuildMDVM 阶段调用 CreateAccessor(targetInstance) 创建真正的读写通道
|
||||
/// </summary>
|
||||
public void SetValue(object value)
|
||||
{
|
||||
if (TargetInstance == null) return;
|
||||
switch (MemberInfo)
|
||||
{
|
||||
case PropertyInfo pi:
|
||||
if (pi.CanWrite)
|
||||
pi.SetValue(TargetInstance, value);
|
||||
break;
|
||||
case FieldInfo fi:
|
||||
fi.SetValue(TargetInstance, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
public IAvrgValueAccessorFactory AccessorFactory { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 元数据集
|
||||
/// 包含从对象反射提取的完整元数据,以及用于生成 MVVM 绑定的 BuildMDVM() 方法
|
||||
/// 包含从对象/材质反射提取的完整元数据,以及用于生成 MVVM 绑定的 BuildMDVM() 方法
|
||||
/// </summary>
|
||||
public class AvrgMemberMetaDataSet
|
||||
{
|
||||
@@ -33,7 +33,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// </summary>
|
||||
internal void MergeGroupEntries()
|
||||
{
|
||||
// 提取编组和非编组
|
||||
var grouped = _metaDataList
|
||||
.Where(m => !string.IsNullOrEmpty(m.GroupPath))
|
||||
.GroupBy(m => m.GroupPath)
|
||||
@@ -47,9 +46,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
foreach (var gEntry in grouped)
|
||||
{
|
||||
// 编组内第一个成员作为主元数据
|
||||
var first = gEntry.First();
|
||||
// 收集组内所有成员的 SubMetaDataList(合并拆分属性)
|
||||
foreach (var member in gEntry)
|
||||
{
|
||||
if (member.SubMetaDataList?.Count > 0)
|
||||
@@ -62,9 +59,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
merged.Add(first);
|
||||
}
|
||||
|
||||
// 非编组直接追加
|
||||
merged.AddRange(nonGrouped);
|
||||
|
||||
_metaDataList = merged;
|
||||
}
|
||||
|
||||
@@ -82,9 +77,10 @@ namespace XericUI.ReflectionUIGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为元数据生成对应的 MVVM 值绑定模板
|
||||
/// 为元数据生成对应的 MVVM 值绑定
|
||||
/// 通过 AccessorFactory 创建 IAvrgValueAccessor 完成数据读写
|
||||
/// </summary>
|
||||
/// <param name="businessTarget">可选的业务目标对象(如果元数据中没有绑定目标,则在此传入)</param>
|
||||
/// <param name="businessTarget">目标实例(C# 对象、Material 等)</param>
|
||||
/// <param name="resolverConfig">可选的冲突解决配置</param>
|
||||
/// <returns>绑定集合</returns>
|
||||
public AvrgMvvmBindingCollection BuildMDVM(
|
||||
@@ -92,30 +88,46 @@ namespace XericUI.ReflectionUIGenerator
|
||||
AvrgMvvmResolverConfig resolverConfig = null)
|
||||
{
|
||||
var bindingCollection = new AvrgMvvmBindingCollection();
|
||||
bindingCollection.SourceMetaDataSet = this;
|
||||
var scheduler = new AvrgMvvmScheduler(resolverConfig);
|
||||
|
||||
foreach (var metaData in _metaDataList)
|
||||
{
|
||||
// 确定目标实例
|
||||
var target = metaData.TargetInstance ?? businessTarget;
|
||||
if (target == null)
|
||||
if (metaData.AccessorFactory == null) continue;
|
||||
if (businessTarget == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"[Xuiavrg] 元数据 '{metaData.MemberName}' 缺少 targetInstance,跳过绑定");
|
||||
continue;
|
||||
}
|
||||
|
||||
var accessor = metaData.AccessorFactory.CreateAccessor(businessTarget);
|
||||
if (accessor == null) continue;
|
||||
|
||||
// 创建主绑定
|
||||
var binding = new AvrgMvvmBinding
|
||||
{
|
||||
MetaData = metaData,
|
||||
ReadFunc = () => metaData.GetValue(),
|
||||
ReadFunc = () => accessor.GetValue(),
|
||||
Accessor = accessor,
|
||||
};
|
||||
|
||||
var capturedBinding = binding;
|
||||
binding.WriteAction = (val) =>
|
||||
{
|
||||
metaData.SetValue(val);
|
||||
scheduler.MarkDataDirty(capturedBinding);
|
||||
};
|
||||
|
||||
// 如果元数据有拆分子属性,创建子绑定
|
||||
if (accessor.IsReadOnly)
|
||||
{
|
||||
binding.WriteAction = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
binding.WriteAction = (val) =>
|
||||
{
|
||||
accessor.SetValue(val);
|
||||
scheduler.MarkDataDirty(capturedBinding);
|
||||
};
|
||||
}
|
||||
|
||||
// 拆分属性子绑定
|
||||
if (metaData.SubMetaDataList?.Count > 0)
|
||||
{
|
||||
binding.SubBindings = new List<AvrgMvvmBinding>();
|
||||
@@ -128,14 +140,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
var subBinding = new AvrgMvvmBinding
|
||||
{
|
||||
MetaData = subMetaData,
|
||||
// 从父值中读取子属性
|
||||
ReadFunc = () => ReadSubProperty(metaData.GetValue(), subPath),
|
||||
// 修改子属性并写回父值
|
||||
ReadFunc = () => ReadSubProperty(accessor.GetValue(), subPath),
|
||||
WriteAction = (val) =>
|
||||
{
|
||||
var parentVal = metaData.GetValue();
|
||||
var parentVal = accessor.GetValue();
|
||||
var modifiedParent = WriteSubProperty(parentVal, subPath, val);
|
||||
metaData.SetValue(modifiedParent);
|
||||
accessor.SetValue(modifiedParent);
|
||||
scheduler.MarkDataDirty(capturedBinding);
|
||||
},
|
||||
};
|
||||
@@ -155,16 +165,11 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
// ============ 子属性访问辅助方法 ============
|
||||
|
||||
/// <summary>
|
||||
/// 从父值中读取子属性(支持单层路径如 "x"、"y")
|
||||
/// 例如:ReadSubProperty(Vector2(1,2), "x") → 1f
|
||||
/// </summary>
|
||||
private static object ReadSubProperty(object parentValue, string propertyPath)
|
||||
{
|
||||
if (parentValue == null || string.IsNullOrEmpty(propertyPath))
|
||||
return null;
|
||||
|
||||
// 仅支持单层路径
|
||||
if (propertyPath.Contains('/'))
|
||||
{
|
||||
Debug.LogWarning($"[Xuiavrg] 暂不支持多层嵌套路径 '{propertyPath}'");
|
||||
@@ -182,10 +187,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 修改父值中的子属性并返回修改后的父值
|
||||
/// 适用于值类型(struct)和引用类型
|
||||
/// </summary>
|
||||
private static object WriteSubProperty(object parentValue, string propertyPath, object value)
|
||||
{
|
||||
if (parentValue == null || string.IsNullOrEmpty(propertyPath))
|
||||
@@ -221,8 +222,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>
|
||||
/// 对当前元数据集执行属性检查验证
|
||||
/// </summary>
|
||||
/// <param name="valueRangeConfig">值范围配置(可选,不传入则不执行范围检查)</param>
|
||||
/// <returns>验证结果集合</returns>
|
||||
public AvrgValidationResultSet Validate(AvrgValueRangeConfig valueRangeConfig = null)
|
||||
{
|
||||
var resultSet = new AvrgValidationResultSet();
|
||||
@@ -232,7 +231,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
if (metaData.Hide)
|
||||
continue;
|
||||
|
||||
// 只有标记了 PropertyCheck 的才验证
|
||||
if (!metaData.Required && !metaData.ValidateReasonable
|
||||
&& !metaData.ValidateWarning && !metaData.ValidateInvalid)
|
||||
{
|
||||
@@ -240,13 +238,13 @@ namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
MetaData = metaData,
|
||||
Result = AvrgMemberCheckResult.Skipped,
|
||||
CurrentValueString = metaData.GetValue()?.ToString(),
|
||||
CurrentValueString = metaData.DefaultValue?.ToString(),
|
||||
Message = "未标记属性检查",
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = metaData.GetValue();
|
||||
var value = metaData.DefaultValue;
|
||||
var checkResult = CheckSingleMember(metaData, value, valueRangeConfig);
|
||||
resultSet.Results.Add(checkResult);
|
||||
}
|
||||
@@ -254,9 +252,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证单个成员
|
||||
/// </summary>
|
||||
private AvrgMemberValidationResult CheckSingleMember(
|
||||
AvrgMemberMetaData metaData,
|
||||
object value,
|
||||
@@ -268,7 +263,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
CurrentValueString = value?.ToString() ?? "(null)",
|
||||
};
|
||||
|
||||
// 根据 CheckKey 查找值范围规则
|
||||
AvrgValueRangeEntry rangeEntry = null;
|
||||
string checkKey = !string.IsNullOrEmpty(metaData.PropertyCheckKey)
|
||||
? metaData.PropertyCheckKey
|
||||
@@ -279,22 +273,19 @@ namespace XericUI.ReflectionUIGenerator
|
||||
rangeEntry = rangeConfig.FindEntry(checkKey);
|
||||
}
|
||||
|
||||
// ——— 优先级 1: 空值检查 (Required) ———
|
||||
// 优先级 1: 空值检查 (Required)
|
||||
if (metaData.Required)
|
||||
{
|
||||
bool isEmpty = false;
|
||||
|
||||
// 引用类型 null
|
||||
if (value == null)
|
||||
{
|
||||
isEmpty = true;
|
||||
}
|
||||
// 字符串 null/空
|
||||
else if (value is string str)
|
||||
{
|
||||
isEmpty = string.IsNullOrEmpty(str);
|
||||
}
|
||||
// 使用 ValueRangeEntry 的 IsEmpty 判断
|
||||
else if (rangeEntry != null && !rangeEntry.skipEmptyCheck)
|
||||
{
|
||||
isEmpty = rangeEntry.IsEmpty(value);
|
||||
@@ -308,7 +299,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有范围配置,无法继续检查
|
||||
if (rangeEntry == null)
|
||||
{
|
||||
result.Result = AvrgMemberCheckResult.Valid;
|
||||
@@ -316,7 +306,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return result;
|
||||
}
|
||||
|
||||
// ——— 优先级 2: 合理值检查(最高优先级,命中即通过) ———
|
||||
// 优先级 2: 合理值检查
|
||||
if (metaData.ValidateReasonable && rangeEntry.IsReasonable(value))
|
||||
{
|
||||
result.Result = AvrgMemberCheckResult.Valid;
|
||||
@@ -324,7 +314,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return result;
|
||||
}
|
||||
|
||||
// ——— 优先级 3: 无效值检查 ———
|
||||
// 优先级 3: 无效值检查
|
||||
if (metaData.ValidateInvalid && rangeEntry.IsInvalid(value))
|
||||
{
|
||||
result.Result = AvrgMemberCheckResult.Invalid;
|
||||
@@ -332,7 +322,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return result;
|
||||
}
|
||||
|
||||
// ——— 优先级 4: 警告值检查 ———
|
||||
// 优先级 4: 警告值检查
|
||||
if (metaData.ValidateWarning && rangeEntry.IsWarning(value))
|
||||
{
|
||||
result.Result = AvrgMemberCheckResult.Warning;
|
||||
@@ -340,7 +330,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
return result;
|
||||
}
|
||||
|
||||
// ——— 最终: 通过 ———
|
||||
result.Result = AvrgMemberCheckResult.Valid;
|
||||
result.Message = "通过";
|
||||
return result;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
@@ -20,6 +19,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>写入值到数据源的委托</summary>
|
||||
public Action<object> WriteAction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值访问器引用(在 BuildMDVM 时创建,供 UI 组件使用)
|
||||
/// 提供 ValueType / IsReadOnly / FormatDisplay 等扩展能力
|
||||
/// </summary>
|
||||
public IAvrgValueAccessor Accessor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 拆分属性子绑定列表
|
||||
/// 由 [XuiavrgSplit] 特性展开生成,每个子绑定对应一个拆分后的子属性
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using System;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 预设选项合并模式
|
||||
/// 用于 Dropdown/ToggleGroup 的预设选项与已有选项(如 enum 名)的合并策略
|
||||
/// </summary>
|
||||
public enum PresetOptionsMergeMode
|
||||
{
|
||||
/// <summary>完全覆盖现有选项</summary>
|
||||
Replace,
|
||||
|
||||
/// <summary>保留两者交集</summary>
|
||||
Intersection,
|
||||
|
||||
/// <summary>保留两者并集(去重合并)</summary>
|
||||
Union,
|
||||
|
||||
/// <summary>从现有选项中移除预设选项(差集)</summary>
|
||||
Difference,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 值访问器 — 统一的数据读写通道
|
||||
/// 解耦"数据的物理存储方式"和"业务层的读写需求"
|
||||
///
|
||||
/// C# 反射对象和 Material Shader 属性通过各自实现此接口,
|
||||
/// 向 MVVM 绑定层暴露完全相同的数据交互约定。
|
||||
/// </summary>
|
||||
public interface IAvrgValueAccessor
|
||||
{
|
||||
/// <summary>值的 C# 类型</summary>
|
||||
Type ValueType { get; }
|
||||
|
||||
/// <summary>是否只读(如 Shader Keywords 不可运行时修改)</summary>
|
||||
bool IsReadOnly { get; }
|
||||
|
||||
/// <summary>从数据源读取当前值</summary>
|
||||
object GetValue();
|
||||
|
||||
/// <summary>将值写入数据源(IsReadOnly 时忽略)</summary>
|
||||
void SetValue(object value);
|
||||
|
||||
/// <summary>将值格式化为 UI 显示字符串</summary>
|
||||
string FormatDisplay(object value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 值访问器工厂 — 在"元数据模板"阶段创建,延迟到"绑定实例"时生产 Accessor
|
||||
///
|
||||
/// 设计意图:
|
||||
/// 1. 元数据提取阶段:只描述"如何访问数据",不绑定具体实例
|
||||
/// 2. BuildMDVM 阶段:传入实例,通过 Factory 生产 Accessor
|
||||
/// 3. 同一套元数据模板可复用于不同实例
|
||||
/// </summary>
|
||||
public interface IAvrgValueAccessorFactory
|
||||
{
|
||||
/// <summary>为给定目标实例创建值访问器</summary>
|
||||
/// <param name="targetInstance">目标实例(C# 对象、Material 等)</param>
|
||||
/// <returns>值访问器实例,targetInstance 类型不匹配时返回 null</returns>
|
||||
IAvrgValueAccessor CreateAccessor(object targetInstance);
|
||||
}
|
||||
}
|
||||
@@ -74,24 +74,33 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>
|
||||
/// 字段临时 Entry 实现类
|
||||
/// 作为数据对象和 UI 组件之间的中间缓存层
|
||||
///
|
||||
/// UI 交互事件的分配通过 XUIAvrgMDValue.BindToAccessor() 的多态机制完成,
|
||||
/// 各类 XUIAvrgMDValue 子类自行管理其专属 UI 组件的交互订阅。
|
||||
/// </summary>
|
||||
public class XuiFieldTempEntry : IFieldEntry
|
||||
{
|
||||
#region 对象池
|
||||
|
||||
internal static ObjectPool<XuiFieldTempEntry> Pool { get; private set; }
|
||||
|
||||
static XuiFieldTempEntry()
|
||||
private static ObjectPool<XuiFieldTempEntry> _pool;
|
||||
internal static ObjectPool<XuiFieldTempEntry> Pool
|
||||
{
|
||||
Pool = new ObjectPool<XuiFieldTempEntry>(
|
||||
createFunc: () => new XuiFieldTempEntry(),
|
||||
actionOnGet: entry => entry.OnEntryCreate(),
|
||||
actionOnRelease: entry => entry.OnEntryRelease(),
|
||||
actionOnDestroy: entry => entry.OnEntryDestroy(),
|
||||
collectionCheck: false,
|
||||
defaultCapacity: 32,
|
||||
maxSize: 512
|
||||
);
|
||||
get
|
||||
{
|
||||
if (_pool == null)
|
||||
{
|
||||
_pool = new ObjectPool<XuiFieldTempEntry>(
|
||||
createFunc: () => new XuiFieldTempEntry(),
|
||||
actionOnGet: entry => entry.OnEntryCreate(),
|
||||
actionOnRelease: entry => entry.OnEntryRelease(),
|
||||
actionOnDestroy: entry => entry.OnEntryDestroy(),
|
||||
collectionCheck: false,
|
||||
defaultCapacity: 32,
|
||||
maxSize: 512
|
||||
);
|
||||
}
|
||||
return _pool;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>从池中获取 Entry 实例</summary>
|
||||
@@ -113,7 +122,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
private GameObject _uiInstance;
|
||||
private GameObject _boundPrefab;
|
||||
private int _fieldOrder;
|
||||
private bool _uiBound; // 防止重复绑定
|
||||
private bool _uiBound;
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -166,9 +175,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
#region 初始化方法
|
||||
|
||||
/// <summary>
|
||||
/// 设置 Entry 基本属性
|
||||
/// </summary>
|
||||
public void Initialize(string entryName, XuiavrgFieldTag tag = XuiavrgFieldTag.None, int tagIndex = 0)
|
||||
{
|
||||
_entryName = entryName;
|
||||
@@ -179,18 +185,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
_uiBound = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置父 Entry(用于嵌套)
|
||||
/// </summary>
|
||||
public void SetParent(IFieldEntry parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_entryPath = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加子 Entry
|
||||
/// </summary>
|
||||
public void AddChild(XuiFieldTempEntry child)
|
||||
{
|
||||
if (child == null) return;
|
||||
@@ -198,9 +198,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
_children.Add(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加 MVVM 绑定到此 Entry
|
||||
/// </summary>
|
||||
public void AddBinding(AvrgMvvmBinding binding, AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
if (binding == null) return;
|
||||
@@ -253,7 +250,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
/// <summary>
|
||||
/// 将 XUIAvrgMDValue 与 MVVM 绑定连接
|
||||
/// 支持主绑定 + 拆分属性子绑定(通过 BindingSlots)
|
||||
/// 通过多态 BindToAccessor 让 UI 子类自行管理交互事件和显示更新
|
||||
/// </summary>
|
||||
private void BindComponentWithBinding(XUIAvrgMDValue mdValue)
|
||||
{
|
||||
@@ -261,39 +258,27 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
var primaryBinding = _bindings[0];
|
||||
|
||||
// ——— 1. 主绑定:数据 → UI ———
|
||||
primaryBinding.OnUIDataChanged += value => UpdateUIComponent(mdValue, value);
|
||||
// ★ 多态调用: UI 子类自行处理交互事件和显示更新
|
||||
mdValue.BindToAccessor(primaryBinding.Accessor, primaryBinding, _scheduler);
|
||||
|
||||
// ——— 主绑定:UI → 数据 ———
|
||||
WireUpUIEvents(mdValue, primaryBinding);
|
||||
|
||||
// ——— 2. 拆分属性子绑定匹配 ———
|
||||
// 拆分属性子绑定处理
|
||||
if (primaryBinding.SubBindings?.Count > 0 && mdValue.AvailableBindingSlots?.Count > 0)
|
||||
{
|
||||
// 收集所有子节点上的 XUIAvrgMDValue
|
||||
var childMDValues = new List<XUIAvrgMDValue>();
|
||||
mdValue.GetComponentsInChildren(true, childMDValues);
|
||||
|
||||
foreach (var slot in mdValue.AvailableBindingSlots)
|
||||
{
|
||||
// 按 propertyName 匹配子绑定
|
||||
var subBinding = primaryBinding.SubBindings.Find(
|
||||
sb => sb.MetaData?.PropertyPath == slot.propertyName);
|
||||
if (subBinding == null) continue;
|
||||
|
||||
// 在子节点中查找匹配 Tag 的 XUIAvrgMDValue
|
||||
foreach (var childMD in childMDValues)
|
||||
{
|
||||
if (childMD == mdValue) continue; // 跳过自身
|
||||
if (childMD == mdValue) continue;
|
||||
if (childMD.EntryTag == slot.tag || slot.tag == XuiavrgFieldTag.None)
|
||||
{
|
||||
// 子绑定:数据 → UI
|
||||
subBinding.OnUIDataChanged += value => UpdateUIComponent(childMD, value);
|
||||
|
||||
// 子绑定:UI → 数据(订阅子节点的交互事件)
|
||||
WireUpUIEvents(childMD, subBinding);
|
||||
|
||||
// 广播子元数据到子节点样式组件
|
||||
childMD.BindToAccessor(subBinding.Accessor, subBinding, _scheduler);
|
||||
childMD.InitializeWithMetaData(subBinding.MetaData);
|
||||
break;
|
||||
}
|
||||
@@ -301,104 +286,10 @@ namespace XericUI.ReflectionUIGenerator
|
||||
}
|
||||
}
|
||||
|
||||
// 广播主元数据到 XUIAvrgMDValue(触发 IXUIAvrgMDValueStyle 子组件)
|
||||
// 广播主元数据到样式组件
|
||||
mdValue.InitializeWithMetaData(primaryBinding.MetaData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅 UI 组件的交互事件 → 写入绑定
|
||||
/// </summary>
|
||||
private void WireUpUIEvents(XUIAvrgMDValue mdValue, AvrgMvvmBinding binding)
|
||||
{
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Button uiButton))
|
||||
{
|
||||
uiButton.onClick.AddListener(() =>
|
||||
{
|
||||
binding.WriteFromUI(true);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Toggle uiToggle))
|
||||
{
|
||||
uiToggle.onValueChanged.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Slider uiSlider))
|
||||
{
|
||||
uiSlider.onValueChanged.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.InputField uiInput))
|
||||
{
|
||||
uiInput.onEndEdit.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Dropdown uiDropdown))
|
||||
{
|
||||
uiDropdown.onValueChanged.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
_scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 UI 组件的显示值(通过 XUIAvrgMDValue 所在 GameObject 上的具体组件)
|
||||
/// </summary>
|
||||
private static void UpdateUIComponent(XUIAvrgMDValue mdValue, object value)
|
||||
{
|
||||
if (mdValue == null || value == null) return;
|
||||
|
||||
// Text
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.Text uiText))
|
||||
{
|
||||
uiText.text = value.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle
|
||||
if (value is bool boolVal && mdValue.TryGetComponent(out UnityEngine.UI.Toggle uiToggle))
|
||||
{
|
||||
uiToggle.isOn = boolVal;
|
||||
return;
|
||||
}
|
||||
|
||||
// Slider
|
||||
if (value is float floatVal && mdValue.TryGetComponent(out UnityEngine.UI.Slider uiSlider))
|
||||
{
|
||||
uiSlider.value = floatVal;
|
||||
return;
|
||||
}
|
||||
|
||||
// InputField
|
||||
if (mdValue.TryGetComponent(out UnityEngine.UI.InputField uiInput))
|
||||
{
|
||||
uiInput.text = value.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
// Dropdown
|
||||
if (value is int intVal && mdValue.TryGetComponent(out UnityEngine.UI.Dropdown uiDropdown))
|
||||
{
|
||||
uiDropdown.value = intVal;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 生命周期
|
||||
@@ -419,7 +310,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
var value = binding.ReadFromDataSource();
|
||||
binding.NotifyUIUpdate(value);
|
||||
|
||||
// 子绑定也做初始值推送
|
||||
if (binding.SubBindings?.Count > 0)
|
||||
{
|
||||
foreach (var subBinding in binding.SubBindings)
|
||||
@@ -446,7 +336,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
foreach (var binding in _bindings)
|
||||
{
|
||||
binding.ClearUIEvent();
|
||||
// 子绑定也清理
|
||||
if (binding.SubBindings?.Count > 0)
|
||||
{
|
||||
foreach (var sub in binding.SubBindings)
|
||||
|
||||
@@ -11,41 +11,56 @@ namespace XericUI.ReflectionUIGenerator
|
||||
public enum XuiavrgFieldTag
|
||||
{
|
||||
None = 0,
|
||||
|
||||
|
||||
/// <summary>标题文本 / Label</summary>
|
||||
Label = 1 << 0,
|
||||
|
||||
|
||||
/// <summary>内容值 / 数值上下文</summary>
|
||||
Context = 1 << 1,
|
||||
|
||||
|
||||
/// <summary>单位文本</summary>
|
||||
Unit = 1 << 2,
|
||||
|
||||
|
||||
/// <summary>布尔开关 / Toggle</summary>
|
||||
Toggle = 1 << 3,
|
||||
|
||||
|
||||
/// <summary>按钮触发器</summary>
|
||||
Button = 1 << 4,
|
||||
|
||||
|
||||
/// <summary>滑块 / 进度条</summary>
|
||||
Slider = 1 << 5,
|
||||
|
||||
|
||||
/// <summary>下拉选择</summary>
|
||||
Dropdown = 1 << 6,
|
||||
|
||||
|
||||
/// <summary>输入值 / InputField</summary>
|
||||
Input = 1 << 7,
|
||||
|
||||
|
||||
/// <summary>单选项 / Radio</summary>
|
||||
Select = 1 << 8,
|
||||
|
||||
|
||||
/// <summary>符号 / 图标</summary>
|
||||
Sign = 1 << 9,
|
||||
|
||||
|
||||
/// <summary>编辑区域 / 多行文本</summary>
|
||||
Edit = 1 << 10,
|
||||
|
||||
|
||||
/// <summary>输出区域 / 只读展示</summary>
|
||||
Output = 1 << 11,
|
||||
|
||||
/// <summary>颜色 / Color</summary>
|
||||
Color = 1 << 12,
|
||||
|
||||
/// <summary>纹理预览 / Texture</summary>
|
||||
Texture = 1 << 13,
|
||||
|
||||
/// <summary>向量值组 / Vector (Vector2/3/4)</summary>
|
||||
Vector = 1 << 14,
|
||||
|
||||
/// <summary>开关组 / ToggleGroup</summary>
|
||||
ToggleGroup = 1 << 15,
|
||||
|
||||
/// <summary>只读展示 / ReadOnly</summary>
|
||||
ReadOnly = 1 << 16,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
@@ -18,19 +20,20 @@ namespace XericUI.ReflectionUIGenerator
|
||||
yield return property;
|
||||
}
|
||||
|
||||
// ============ C# 对象: 元数据提取(纯元数据,不绑定实例)============
|
||||
|
||||
/// <summary>
|
||||
/// 从任意对象提取 AvrgMemberMetaData 元数据集
|
||||
/// 反射识别成员、识别 IXuiavrgAttribute 标记、执行标记绑定的完整流程
|
||||
/// 从类型提取 AvrgMemberMetaData 元数据集(不含实例绑定)
|
||||
/// 用于构建元数据模板,可复用于不同实例
|
||||
/// </summary>
|
||||
/// <param name="source">待提取的数据对象</param>
|
||||
/// <returns>提取完成的元数据集</returns>
|
||||
public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData(this object source)
|
||||
/// <param name="type">待提取的类型</param>
|
||||
/// <returns>纯元数据集(无实例引用,由 AccessorFactory 持有反射信息)</returns>
|
||||
public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData(this Type type)
|
||||
{
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
if (type == null)
|
||||
throw new ArgumentNullException(nameof(type));
|
||||
|
||||
var metaDataSet = new AvrgMemberMetaDataSet();
|
||||
var type = source.GetType();
|
||||
|
||||
foreach (var memberInfo in type.GetRuntimeMemberInfo())
|
||||
{
|
||||
@@ -39,52 +42,30 @@ namespace XericUI.ReflectionUIGenerator
|
||||
if (hideAttr != null && hideAttr.Hide)
|
||||
continue;
|
||||
|
||||
// 创建元数据
|
||||
// 识别值类型
|
||||
Type valueType = memberInfo switch
|
||||
{
|
||||
PropertyInfo pi => pi.PropertyType,
|
||||
FieldInfo fi => fi.FieldType,
|
||||
_ => typeof(object)
|
||||
};
|
||||
|
||||
// 创建元数据 — 不持有 MemberInfo 和 TargetInstance
|
||||
var metaData = new AvrgMemberMetaData
|
||||
{
|
||||
MemberName = memberInfo.Name,
|
||||
MemberInfo = memberInfo,
|
||||
TargetInstance = source,
|
||||
ValueType = valueType,
|
||||
AccessorFactory = new ReflectionAccessorFactory(memberInfo),
|
||||
PropertyCheckKey = valueType?.FullName,
|
||||
};
|
||||
|
||||
// 识别值类型和 IsProperty
|
||||
switch (memberInfo)
|
||||
{
|
||||
case PropertyInfo pi:
|
||||
metaData.IsProperty = true;
|
||||
metaData.ValueType = pi.PropertyType;
|
||||
break;
|
||||
case FieldInfo fi:
|
||||
metaData.IsProperty = false;
|
||||
metaData.ValueType = fi.FieldType;
|
||||
break;
|
||||
}
|
||||
|
||||
// 提取字段默认值(类初始值)
|
||||
// Unity 序列化字段可以通过反射读取实例上的当前值作为"默认"参考
|
||||
// 但更准确的在类声明阶段的初始值需要通过运行时观察或 Fallback 判断
|
||||
// 此处策略:如果 TargetInstance 非 null,直接 ReadValue 作为 DefaultValue
|
||||
if (source != null && memberInfo is FieldInfo fi2)
|
||||
{
|
||||
metaData.DefaultValue = fi2.GetValue(source);
|
||||
}
|
||||
else if (source != null && memberInfo is PropertyInfo pi2 && pi2.CanRead)
|
||||
{
|
||||
metaData.DefaultValue = pi2.GetValue(source);
|
||||
}
|
||||
|
||||
// 设置 PropertyCheckKey 默认值(用类型全称)
|
||||
if (metaData.ValueType != null)
|
||||
metaData.PropertyCheckKey = metaData.ValueType.FullName;
|
||||
|
||||
// 收集所有 IXuiavrgAttribute
|
||||
// 收集并执行所有 IXuiavrgAttribute
|
||||
var attributes = memberInfo.GetCustomAttributes()
|
||||
.OfType<IXuiavrgAttribute>()
|
||||
.ToList();
|
||||
|
||||
metaData.Attributes = attributes;
|
||||
|
||||
// 按序执行 BindMetaData
|
||||
foreach (var attr in attributes)
|
||||
{
|
||||
attr.BindMetaData(metaData);
|
||||
@@ -103,11 +84,147 @@ namespace XericUI.ReflectionUIGenerator
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从任意对象提取 AvrgMemberMetaData 元数据集的泛型版本
|
||||
/// 从对象实例提取元数据并一步生成绑定(便捷方法)
|
||||
/// 内部调用 Type.ExtractAvrgMemberMetaData() + BuildMDVM()
|
||||
/// </summary>
|
||||
public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData<T>(this T source)
|
||||
public static AvrgMvvmBindingCollection BuildBindingsFromObject(
|
||||
this object source,
|
||||
AvrgMvvmResolverConfig resolverConfig = null)
|
||||
{
|
||||
return ExtractAvrgMemberMetaData((object)source);
|
||||
if (source == null)
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
|
||||
var metaDataSet = source.GetType().ExtractAvrgMemberMetaData();
|
||||
return metaDataSet.BuildMDVM(source, resolverConfig);
|
||||
}
|
||||
|
||||
// ============ Material: 元数据提取 ============
|
||||
|
||||
/// <summary>
|
||||
/// 从 Material 的 Shader 属性中提取元数据集
|
||||
/// </summary>
|
||||
/// <param name="material">目标 Material</param>
|
||||
/// <returns>纯元数据集(AccessorFactory 持有 MaterialPropertyAccessorFactory)</returns>
|
||||
public static AvrgMemberMetaDataSet ExtractFromMaterial(this Material material)
|
||||
{
|
||||
if (material == null)
|
||||
throw new ArgumentNullException(nameof(material));
|
||||
|
||||
var shader = material.shader;
|
||||
if (shader == null)
|
||||
{
|
||||
Debug.LogError("[Xuiavrg] Material 没有关联的 Shader");
|
||||
return null;
|
||||
}
|
||||
|
||||
var metaDataSet = new AvrgMemberMetaDataSet();
|
||||
int propCount = shader.GetPropertyCount();
|
||||
|
||||
for (int i = 0; i < propCount; i++)
|
||||
{
|
||||
var propName = shader.GetPropertyName(i);
|
||||
var shaderType = shader.GetPropertyType(i);
|
||||
var desc = shader.GetPropertyDescription(i);
|
||||
var flags = shader.GetPropertyFlags(i);
|
||||
|
||||
// 跳过隐藏属性
|
||||
if ((flags & UnityEngine.Rendering.ShaderPropertyFlags.HideInInspector) != 0)
|
||||
continue;
|
||||
|
||||
// ShaderPropertyType → C# Type + Tag
|
||||
(Type valueType, XuiavrgFieldTag tag) = MapShaderPropertyType(shaderType, propName);
|
||||
|
||||
var metaData = new AvrgMemberMetaData
|
||||
{
|
||||
MemberName = propName,
|
||||
ValueType = valueType,
|
||||
LabelName = !string.IsNullOrEmpty(desc) ? desc : FormatPropertyName(propName),
|
||||
Tag = tag,
|
||||
AccessorFactory = new MaterialPropertyAccessorFactory(propName, shaderType, valueType),
|
||||
};
|
||||
|
||||
// 读取当前值作为 DefaultValue
|
||||
metaData.DefaultValue = ReadMaterialProperty(material, propName, shaderType);
|
||||
|
||||
// Range 属性:提取范围限制
|
||||
if (shaderType == ShaderPropertyType.Range)
|
||||
{
|
||||
var limits = shader.GetPropertyRangeLimits(i);
|
||||
metaData.RangeMin = limits.x;
|
||||
metaData.RangeMax = limits.y;
|
||||
}
|
||||
|
||||
metaDataSet.Add(metaData);
|
||||
}
|
||||
|
||||
// ===== Shader Keywords 提取(只读)=====
|
||||
try
|
||||
{
|
||||
var keywordSpace = shader.keywordSpace;
|
||||
foreach (var kw in keywordSpace.keywords)
|
||||
{
|
||||
bool isEnabled = material.IsKeywordEnabled(kw.name);
|
||||
var kwMetaData = new AvrgMemberMetaData
|
||||
{
|
||||
MemberName = $"kwd_{kw.name}",
|
||||
ValueType = typeof(bool),
|
||||
LabelName = $"[Keyword] {kw.name}",
|
||||
Tag = XuiavrgFieldTag.Toggle,
|
||||
DefaultValue = isEnabled,
|
||||
AccessorFactory = new MaterialPropertyAccessorFactory(
|
||||
kw.name,
|
||||
ShaderPropertyType.Float, // Keywords 不是标准属性,用 Float 占位
|
||||
typeof(bool),
|
||||
isReadOnly: true),
|
||||
};
|
||||
metaDataSet.Add(kwMetaData);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"[Xuiavrg] 提取 Shader Keywords 失败: {e.Message}");
|
||||
}
|
||||
|
||||
metaDataSet.SortByFieldOrder();
|
||||
return metaDataSet;
|
||||
}
|
||||
|
||||
private static (Type valueType, XuiavrgFieldTag tag) MapShaderPropertyType(
|
||||
ShaderPropertyType shaderType, string propName)
|
||||
{
|
||||
return shaderType switch
|
||||
{
|
||||
ShaderPropertyType.Float => (typeof(float), XuiavrgFieldTag.Input),
|
||||
ShaderPropertyType.Range => (typeof(float), XuiavrgFieldTag.Slider),
|
||||
ShaderPropertyType.Int => (typeof(int), XuiavrgFieldTag.Input),
|
||||
ShaderPropertyType.Color => (typeof(Color), XuiavrgFieldTag.Color),
|
||||
ShaderPropertyType.Vector => (typeof(Vector4), XuiavrgFieldTag.Vector),
|
||||
ShaderPropertyType.Texture => (typeof(Texture), XuiavrgFieldTag.Texture),
|
||||
_ => (typeof(object), XuiavrgFieldTag.None),
|
||||
};
|
||||
}
|
||||
|
||||
private static object ReadMaterialProperty(Material material, string propName,
|
||||
ShaderPropertyType shaderType)
|
||||
{
|
||||
return shaderType switch
|
||||
{
|
||||
ShaderPropertyType.Float or ShaderPropertyType.Range => material.GetFloat(propName),
|
||||
ShaderPropertyType.Int => material.GetInt(propName),
|
||||
ShaderPropertyType.Color => material.GetColor(propName),
|
||||
ShaderPropertyType.Vector => material.GetVector(propName),
|
||||
ShaderPropertyType.Texture => material.GetTexture(propName),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 属性名格式化(_MainTex → MainTex, _ShadowColor → Shadow Color)
|
||||
/// </summary>
|
||||
private static string FormatPropertyName(string propName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(propName)) return propName;
|
||||
return propName.TrimStart('_').Replace('_', ' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 颜色值组件 — 处理 Color 类型的显示
|
||||
/// 通过 Image.color 展示当前颜色值
|
||||
/// 颜色修改可通过 RGB 子节点 InputField 或额外的颜色选择逻辑完成
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDColorValue")]
|
||||
public class XUIAvrgMDColorValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("用于展示颜色的 Image 组件(未设置则自动查找)")]
|
||||
private Image _colorDisplay;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_colorDisplay == null) _colorDisplay = GetComponent<Image>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
if (value is Color color && _colorDisplay != null)
|
||||
{
|
||||
_colorDisplay.color = color;
|
||||
}
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
if (initialValue is Color initColor && _colorDisplay != null)
|
||||
{
|
||||
_colorDisplay.color = initColor;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateDisplayValue(object value)
|
||||
{
|
||||
if (value is Color color && _colorDisplay != null)
|
||||
{
|
||||
_colorDisplay.color = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Dropdown 值组件 — 处理 string / int / enum 类型的下拉选择交互
|
||||
/// 预设选项来源:元数据的 PresetOptions 或 enum 类型的枚举名
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDDropdownValue")]
|
||||
public class XUIAvrgMDDropdownValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 Dropdown 组件(未设置则自动查找)")]
|
||||
private Dropdown _dropdown;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_dropdown == null) _dropdown = GetComponent<Dropdown>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _dropdown == null) return;
|
||||
|
||||
// 构建选项列表
|
||||
var options = BuildOptions(accessor.ValueType);
|
||||
_dropdown.ClearOptions();
|
||||
_dropdown.AddOptions(options);
|
||||
|
||||
// UI → 数据
|
||||
_dropdown.onValueChanged.AddListener(index =>
|
||||
{
|
||||
if (index >= 0 && index < options.Count)
|
||||
{
|
||||
object convertedVal = ConvertOptionToValue(options[index], accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
}
|
||||
});
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
int idx = FindOptionIndex(value, options);
|
||||
if (idx >= 0)
|
||||
_dropdown.SetValueWithoutNotify(idx);
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
int initialIdx = FindOptionIndex(initialValue, options);
|
||||
if (initialIdx >= 0)
|
||||
_dropdown.SetValueWithoutNotify(initialIdx);
|
||||
}
|
||||
|
||||
private List<string> BuildOptions(System.Type valueType)
|
||||
{
|
||||
// 1. 从元数据 PresetOptions 获取
|
||||
if (BoundMetaData?.PresetOptions != null && BoundMetaData.PresetOptions.Count > 0)
|
||||
return BoundMetaData.PresetOptions.ToList();
|
||||
|
||||
// 2. enum 类型:自动提取枚举名
|
||||
if (valueType != null && valueType.IsEnum)
|
||||
return System.Enum.GetNames(valueType).ToList();
|
||||
|
||||
// 3. 回退
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
private int FindOptionIndex(object value, List<string> options)
|
||||
{
|
||||
if (value == null || options.Count == 0) return -1;
|
||||
|
||||
string valueStr = value.ToString();
|
||||
for (int i = 0; i < options.Count; i++)
|
||||
{
|
||||
if (options[i] == valueStr) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static object ConvertOptionToValue(string option, System.Type targetType)
|
||||
{
|
||||
if (targetType == typeof(string)) return option;
|
||||
if (targetType == typeof(int) && int.TryParse(option, out int intVal)) return intVal;
|
||||
if (targetType != null && targetType.IsEnum)
|
||||
return System.Enum.Parse(targetType, option);
|
||||
return option;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// InputField 值组件 — 处理 string / float / int 类型的文本输入交互
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDInputFieldValue")]
|
||||
public class XUIAvrgMDInputFieldValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 InputField 组件(未设置则自动查找)")]
|
||||
private InputField _inputField;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_inputField == null) _inputField = GetComponent<InputField>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _inputField == null) return;
|
||||
|
||||
// 设置输入类型
|
||||
if (accessor.ValueType == typeof(float) || accessor.ValueType == typeof(int)
|
||||
|| accessor.ValueType == typeof(double))
|
||||
{
|
||||
_inputField.contentType = InputField.ContentType.DecimalNumber;
|
||||
}
|
||||
|
||||
// UI → 数据
|
||||
_inputField.onEndEdit.AddListener(val =>
|
||||
{
|
||||
object convertedVal = ConvertStringToValue(val, accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
_inputField.SetTextWithoutNotify(accessor.FormatDisplay(value));
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
_inputField.SetTextWithoutNotify(accessor.FormatDisplay(initialValue));
|
||||
}
|
||||
|
||||
private static object ConvertStringToValue(string s, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(string)) return s;
|
||||
if (targetType == typeof(float) && float.TryParse(s, out float fVal)) return fVal;
|
||||
if (targetType == typeof(int) && int.TryParse(s, out int iVal)) return iVal;
|
||||
if (targetType == typeof(double) && double.TryParse(s, out double dVal)) return dVal;
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 标签值组件 — 处理只读文本的纯展示
|
||||
/// 适用于 Label / Context / Unit / ReadOnly 等展示型 Tag
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDLabelValue")]
|
||||
public class XUIAvrgMDLabelValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 Text 组件(未设置则自动查找)")]
|
||||
private UnityEngine.UI.Text _uiText;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 TMP_Text 组件(未设置则自动查找)")]
|
||||
private TMPro.TMP_Text _tmpText;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_uiText == null) _uiText = GetComponent<UnityEngine.UI.Text>();
|
||||
if (_tmpText == null) _tmpText = GetComponent<TMPro.TMP_Text>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
SetText(value);
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
SetText(initialValue);
|
||||
}
|
||||
|
||||
private void SetText(object value)
|
||||
{
|
||||
string displayStr = value?.ToString() ?? "";
|
||||
|
||||
if (_tmpText != null)
|
||||
_tmpText.text = displayStr;
|
||||
else if (_uiText != null)
|
||||
_uiText.text = displayStr;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Slider 值组件 — 处理 float / int / double 类型的滑块交互
|
||||
/// 适用于 Shader Range 属性或标记了 [XuiavrgFieldTag(Slider)] 的字段
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDSliderValue")]
|
||||
public class XUIAvrgMDSliderValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 Slider 组件(未设置则自动查找)")]
|
||||
private Slider _slider;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("值显示标签(可选,未设置则不更新)")]
|
||||
private TMPro.TMP_Text _valueLabel;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_slider == null) _slider = GetComponent<Slider>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _slider == null) return;
|
||||
|
||||
// 设置 Slider 参数
|
||||
_slider.wholeNumbers = accessor.ValueType == typeof(int);
|
||||
|
||||
if (BoundMetaData != null)
|
||||
{
|
||||
if (BoundMetaData.RangeMin.HasValue)
|
||||
_slider.minValue = BoundMetaData.RangeMin.Value;
|
||||
if (BoundMetaData.RangeMax.HasValue)
|
||||
_slider.maxValue = BoundMetaData.RangeMax.Value;
|
||||
}
|
||||
|
||||
// UI → 数据
|
||||
_slider.onValueChanged.AddListener(val =>
|
||||
{
|
||||
object convertedVal = ConvertValueForAccessor(val, accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
|
||||
if (_valueLabel != null)
|
||||
_valueLabel.text = accessor.FormatDisplay(convertedVal);
|
||||
});
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
float sliderVal = Convert.ToSingle(value);
|
||||
_slider.SetValueWithoutNotify(sliderVal);
|
||||
|
||||
if (_valueLabel != null)
|
||||
_valueLabel.text = accessor.FormatDisplay(value);
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
if (initialValue != null)
|
||||
{
|
||||
_slider.SetValueWithoutNotify(Convert.ToSingle(initialValue));
|
||||
if (_valueLabel != null)
|
||||
_valueLabel.text = accessor.FormatDisplay(initialValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertValueForAccessor(float val, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(int)) return (int)val;
|
||||
if (targetType == typeof(double)) return (double)val;
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 纹理值组件 — 处理 Texture 类型的预览显示
|
||||
/// 通过 RawImage.texture 展示当前纹理
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDTextureValue")]
|
||||
public class XUIAvrgMDTextureValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("用于展示纹理的 RawImage 组件(未设置则自动查找)")]
|
||||
private RawImage _textureDisplay;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("纹理名称标签(可选,用于显示纹理资源名)")]
|
||||
private TMPro.TMP_Text _nameLabel;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_textureDisplay == null) _textureDisplay = GetComponent<RawImage>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
if (value is Texture tex && _textureDisplay != null)
|
||||
{
|
||||
_textureDisplay.texture = tex;
|
||||
}
|
||||
|
||||
if (_nameLabel != null)
|
||||
{
|
||||
_nameLabel.text = value is Texture t && t != null ? t.name : "None";
|
||||
}
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
if (initialValue is Texture initTex && _textureDisplay != null)
|
||||
{
|
||||
_textureDisplay.texture = initTex;
|
||||
}
|
||||
|
||||
if (_nameLabel != null)
|
||||
{
|
||||
_nameLabel.text = initialValue is Texture t && t != null ? t.name : "None";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// ToggleGroup 值组件 — 处理 string / int / enum 类型的单选组交互
|
||||
/// 适用于标记了 [XuiavrgFieldToggleGroupValue] 的字段
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDToggleGroupValue")]
|
||||
public class XUIAvrgMDToggleGroupValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 ToggleGroup 组件(未设置则自动查找)")]
|
||||
private ToggleGroup _toggleGroup;
|
||||
|
||||
private List<Toggle> _toggles = new List<Toggle>();
|
||||
private List<string> _optionLabels = new List<string>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_toggleGroup == null) _toggleGroup = GetComponent<ToggleGroup>();
|
||||
if (_toggleGroup != null)
|
||||
{
|
||||
_toggles = _toggleGroup.GetComponentsInChildren<Toggle>(true).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _toggleGroup == null) return;
|
||||
|
||||
// 构建选项
|
||||
BuildOptions(accessor.ValueType);
|
||||
|
||||
// 为每个 Toggle 注册事件
|
||||
for (int i = 0; i < _toggles.Count && i < _optionLabels.Count; i++)
|
||||
{
|
||||
int idx = i;
|
||||
_toggles[i].onValueChanged.AddListener(isOn =>
|
||||
{
|
||||
if (isOn)
|
||||
{
|
||||
object convertedVal = ConvertOptionToValue(_optionLabels[idx], accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
string valueStr = value?.ToString();
|
||||
for (int i = 0; i < _toggles.Count && i < _optionLabels.Count; i++)
|
||||
{
|
||||
if (_optionLabels[i] == valueStr)
|
||||
{
|
||||
_toggles[i].SetIsOnWithoutNotify(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
string initStr = initialValue?.ToString();
|
||||
for (int i = 0; i < _toggles.Count && i < _optionLabels.Count; i++)
|
||||
{
|
||||
if (_optionLabels[i] == initStr)
|
||||
{
|
||||
_toggles[i].SetIsOnWithoutNotify(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BuildOptions(System.Type valueType)
|
||||
{
|
||||
_optionLabels.Clear();
|
||||
|
||||
// 1. 从元数据 PresetOptions
|
||||
if (BoundMetaData?.PresetOptions != null && BoundMetaData.PresetOptions.Count > 0)
|
||||
{
|
||||
_optionLabels.AddRange(BoundMetaData.PresetOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. enum 类型
|
||||
if (valueType != null && valueType.IsEnum)
|
||||
{
|
||||
_optionLabels.AddRange(System.Enum.GetNames(valueType));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertOptionToValue(string option, System.Type targetType)
|
||||
{
|
||||
if (targetType == typeof(string)) return option;
|
||||
if (targetType == typeof(int) && int.TryParse(option, out int intVal)) return intVal;
|
||||
if (targetType != null && targetType.IsEnum)
|
||||
return System.Enum.Parse(targetType, option);
|
||||
return option;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggle 值组件 — 处理 bool 类型的开关交互
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDToggleValue")]
|
||||
public class XUIAvrgMDToggleValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 Toggle 组件(未设置则自动查找)")]
|
||||
private Toggle _toggle;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_toggle == null) _toggle = GetComponent<Toggle>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null || _toggle == null) return;
|
||||
|
||||
// 只读模式下禁用交互
|
||||
_toggle.interactable = !accessor.IsReadOnly;
|
||||
|
||||
// UI → 数据
|
||||
_toggle.onValueChanged.AddListener(val =>
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
_toggle.SetIsOnWithoutNotify(value is bool b && b);
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
_toggle.SetIsOnWithoutNotify(initialValue is bool bv && bv);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 向量值组件 — 处理 Vector2 / Vector3 / Vector4 类型的多字段输入
|
||||
/// 子节点按 BindingSlots 绑定各分量(如 "x", "y", "z", "w")
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDVectorValue")]
|
||||
public class XUIAvrgMDVectorValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("X 分量 InputField(可选,也可通过 BindingSlots 的子节点绑定)")]
|
||||
private InputField _inputX;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Y 分量 InputField")]
|
||||
private InputField _inputY;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Z 分量 InputField(Vector3/Vector4 时使用)")]
|
||||
private InputField _inputZ;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("W 分量 InputField(Vector4 时使用)")]
|
||||
private InputField _inputW;
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 根据向量类型启用/禁用对应分量
|
||||
int componentCount = GetComponentCount(accessor.ValueType);
|
||||
SetInputActive(_inputX, 0, componentCount);
|
||||
SetInputActive(_inputY, 1, componentCount);
|
||||
SetInputActive(_inputZ, 2, componentCount);
|
||||
SetInputActive(_inputW, 3, componentCount);
|
||||
|
||||
// 为每个输入注册事件
|
||||
BindInputField(_inputX, 0, accessor, binding, scheduler);
|
||||
BindInputField(_inputY, 1, accessor, binding, scheduler);
|
||||
BindInputField(_inputZ, 2, accessor, binding, scheduler);
|
||||
BindInputField(_inputW, 3, accessor, binding, scheduler);
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
UpdateAllInputs(value);
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateAllInputs(initialValue);
|
||||
}
|
||||
|
||||
private void BindInputField(InputField input, int componentIndex,
|
||||
IAvrgValueAccessor accessor, AvrgMvvmBinding binding, AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
if (input == null) return;
|
||||
|
||||
input.onEndEdit.AddListener(val =>
|
||||
{
|
||||
if (float.TryParse(val, out float num))
|
||||
{
|
||||
var currentVal = accessor.GetValue();
|
||||
var modified = SetVectorComponent(currentVal, componentIndex, num);
|
||||
binding.WriteFromUI(modified);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateAllInputs(object value)
|
||||
{
|
||||
if (value == null) return;
|
||||
|
||||
if (_inputX != null && _inputX.gameObject.activeSelf)
|
||||
_inputX.SetTextWithoutNotify(GetVectorComponent(value, 0).ToString("F2"));
|
||||
|
||||
if (_inputY != null && _inputY.gameObject.activeSelf)
|
||||
_inputY.SetTextWithoutNotify(GetVectorComponent(value, 1).ToString("F2"));
|
||||
|
||||
if (_inputZ != null && _inputZ.gameObject.activeSelf)
|
||||
_inputZ.SetTextWithoutNotify(GetVectorComponent(value, 2).ToString("F2"));
|
||||
|
||||
if (_inputW != null && _inputW.gameObject.activeSelf)
|
||||
_inputW.SetTextWithoutNotify(GetVectorComponent(value, 3).ToString("F2"));
|
||||
}
|
||||
|
||||
private static int GetComponentCount(System.Type type)
|
||||
{
|
||||
if (type == typeof(Vector2)) return 2;
|
||||
if (type == typeof(Vector3)) return 3;
|
||||
if (type == typeof(Vector4)) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static float GetVectorComponent(object vec, int index)
|
||||
{
|
||||
return vec switch
|
||||
{
|
||||
Vector2 v2 => index == 0 ? v2.x : v2.y,
|
||||
Vector3 v3 => index == 0 ? v3.x : index == 1 ? v3.y : v3.z,
|
||||
Vector4 v4 => index == 0 ? v4.x : index == 1 ? v4.y : index == 2 ? v4.z : v4.w,
|
||||
_ => 0f
|
||||
};
|
||||
}
|
||||
|
||||
private static object SetVectorComponent(object vec, int index, float value)
|
||||
{
|
||||
return vec switch
|
||||
{
|
||||
Vector2 v2 => index == 0 ? new Vector2(value, v2.y) : new Vector2(v2.x, value),
|
||||
Vector3 v3 => index == 0 ? new Vector3(value, v3.y, v3.z)
|
||||
: index == 1 ? new Vector3(v3.x, value, v3.z) : new Vector3(v3.x, v3.y, value),
|
||||
Vector4 v4 => index == 0 ? new Vector4(value, v4.y, v4.z, v4.w)
|
||||
: index == 1 ? new Vector4(v4.x, value, v4.z, v4.w)
|
||||
: index == 2 ? new Vector4(v4.x, v4.y, value, v4.w)
|
||||
: new Vector4(v4.x, v4.y, v4.z, value),
|
||||
_ => vec
|
||||
};
|
||||
}
|
||||
|
||||
private static void SetInputActive(InputField input, int index, int componentCount)
|
||||
{
|
||||
if (input != null)
|
||||
input.gameObject.SetActive(index < componentCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,14 @@ using UnityEngine;
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// MDVM 值组件 — 统一 UI 脚本组件
|
||||
/// 替换所有 XericUIrAttributeXXX 作为预制体上的标记组件
|
||||
/// MDVM 值组件基类 — 预制体上的标记组件
|
||||
///
|
||||
/// 职责:
|
||||
/// 1. 提供 EntryTag/TagIndex 供 XuiFieldTempEntry 匹配
|
||||
/// 2. 持有绑定的 AvrgMemberMetaData 引用
|
||||
/// 3. 初始化时广播元数据到所有子节点上的 IXUIAvrgMDValueStyle
|
||||
/// 4. 定义绑定槽(BindingSlots),供拆分属性子绑定匹配
|
||||
/// 2. 持有绑定的元数据引用
|
||||
/// 3. 通过 virtual BindToAccessor() 让子类自行管理 UI 交互事件和显示更新
|
||||
/// 4. 初始化时广播元数据到所有子节点上的 IXUIAvrgMDValueStyle
|
||||
/// 5. 定义绑定槽(BindingSlots),供拆分属性子绑定匹配
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/XUIAvrgMDValue")]
|
||||
public class XUIAvrgMDValue : MonoBehaviour
|
||||
@@ -44,7 +44,13 @@ namespace XericUI.ReflectionUIGenerator
|
||||
private List<AvrgBindingSlot> _bindingSlots = new List<AvrgBindingSlot>();
|
||||
|
||||
/// <summary>绑定的元数据引用</summary>
|
||||
public AvrgMemberMetaData BoundMetaData { get; private set; }
|
||||
public AvrgMemberMetaData BoundMetaData { get; protected set; }
|
||||
|
||||
/// <summary>当前关联的值访问器</summary>
|
||||
public IAvrgValueAccessor CurrentAccessor { get; protected set; }
|
||||
|
||||
/// <summary>当前关联的 MVVM 绑定</summary>
|
||||
public AvrgMvvmBinding CurrentBinding { get; protected set; }
|
||||
|
||||
public XuiavrgFieldTag EntryTag => _entryTag;
|
||||
public int TagIndex => _tagIndex;
|
||||
@@ -52,6 +58,60 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>只读访问绑定槽列表</summary>
|
||||
public IReadOnlyList<AvrgBindingSlot> AvailableBindingSlots => _bindingSlots;
|
||||
|
||||
/// <summary>
|
||||
/// 由 XuiFieldTempEntry 调用,将值访问器和 MVVM 绑定连接到此 UI 组件
|
||||
///
|
||||
/// 基类实现提供一个默认的通用绑定逻辑(适用于 Label/Context/Unit 等简单展示型 Tag)。
|
||||
/// 子类(如 SliderValue, DropdownValue)应覆写此方法以处理其专属 UI 交互。
|
||||
/// </summary>
|
||||
/// <param name="accessor">值访问器 — 提供统一的数据读写</param>
|
||||
/// <param name="binding">MVVM 绑定 — 提供脏标记和 UI→数据通信通道</param>
|
||||
/// <param name="scheduler">MVVM 调度器 — 可空,用于标记脏状态</param>
|
||||
public virtual void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
AvrgMvvmScheduler scheduler)
|
||||
{
|
||||
CurrentAccessor = accessor;
|
||||
CurrentBinding = binding;
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 默认:数据 → UI 单向同步
|
||||
binding.OnUIDataChanged += value =>
|
||||
{
|
||||
UpdateDisplayValue(value);
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateDisplayValue(initialValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 UI 显示值(基类默认仅查找 Text/TMP_Text 设置文本)
|
||||
/// 子类可覆写以实现专属更新逻辑(如 Slider.value, Image.color 等)
|
||||
/// </summary>
|
||||
protected virtual void UpdateDisplayValue(object value)
|
||||
{
|
||||
if (value == null) return;
|
||||
|
||||
// 尝试 Text
|
||||
var uiText = GetComponent<UnityEngine.UI.Text>();
|
||||
if (uiText != null)
|
||||
{
|
||||
uiText.text = CurrentAccessor?.FormatDisplay(value) ?? value.ToString();
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试 TMP_Text
|
||||
var tmpText = GetComponent<TMPro.TMP_Text>();
|
||||
if (tmpText != null)
|
||||
{
|
||||
tmpText.text = CurrentAccessor?.FormatDisplay(value) ?? value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 由绑定系统调用,传递元数据并广播到 IXUIAvrgMDValueStyle 子组件
|
||||
/// </summary>
|
||||
|
||||
Reference in New Issue
Block a user