Merge remote-tracking branch 'gitea/master' into thr_dev/dev2

# Conflicts:
#	Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaDataSet.cs
This commit is contained in:
2026-07-27 08:38:06 +08:00
165 changed files with 12346 additions and 495 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a12c0014da358274285d451595dcf212
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 174ea317a2a5e8e44a0d96c6d7e43bd5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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 pi2 && !pi2.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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 74ef75bd607ec5242a6bb9bb985bf436
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,44 @@
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 XuiavrgFieldDropdownValueAttribute(PresetOptionsMergeMode mergeMode, params string[] options)
{
MergeMode = mergeMode;
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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 917ba2bd8d59cce469d2c90b2201c529
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,44 @@
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 XuiavrgFieldToggleGroupValueAttribute(PresetOptionsMergeMode mergeMode, params string[] options)
{
MergeMode = mergeMode;
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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6dac053ad1c4ab043a815cb9b3c64d06
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
@@ -0,0 +1,33 @@
using System;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 样式命名空间特性 — 为元数据指定 SuperStyleSheet 查询时的命名空间。
///
/// 可标记在类上(作为该类型所有字段的默认命名空间),
/// 也可标记在字段/属性上(覆盖单个字段的命名空间)。
///
/// 类级标记先应用(设置默认值),字段级标记后应用(可覆盖),
/// 因此字段级 [XuiavrgStyleNamespace] 优先于类级。
///
/// 默认命名空间为 "default"。
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class XuiavrgStyleNamespaceAttribute : Attribute, IXuiavrgAttribute
{
public readonly string StyleNamespace;
/// <param name="styleNamespace">SuperStyleSheet 命名空间名称(如 "my-theme")</param>
public XuiavrgStyleNamespaceAttribute(string styleNamespace)
{
StyleNamespace = styleNamespace;
}
public void BindMetaData(AvrgMemberMetaData metaData)
{
if (metaData != null)
metaData.StyleNamespace = StyleNamespace;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee38b82f1ec947d4da1981bac698fe96
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,77 @@
using System.Collections.Generic;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 绑定名称前缀树(Trie),支持通过完整路径或成员名后缀快速查找 AvrgMvvmBinding。
///
/// 使用场景:
/// - Find("XericUI.MyData.speed") → 完整路径精确命中
/// - FindByMemberName("speed") → 沿 Trie 叶节点查找末段匹配
///
/// 在 BuildMDVM 时构建,绑定到 AvrgMvvmBindingCollection。
/// </summary>
internal class AvrgBindingTrie
{
private class Node
{
public AvrgMvvmBinding Binding;
public readonly Dictionary<string, Node> Children = new();
}
private readonly Node _root = new();
private readonly Dictionary<string, AvrgMvvmBinding> _memberNameIndex = new();
public void Insert(string fullPath, AvrgMvvmBinding binding)
{
if (string.IsNullOrEmpty(fullPath) || binding == null) return;
var parts = fullPath.Split('.');
var current = _root;
foreach (var part in parts)
{
if (!current.Children.TryGetValue(part, out var child))
{
child = new Node();
current.Children[part] = child;
}
current = child;
}
current.Binding = binding;
// 同时建成员名索引(末段)
string memberName = parts[parts.Length - 1];
if (!_memberNameIndex.ContainsKey(memberName))
_memberNameIndex[memberName] = binding;
}
/// <summary>通过完整路径精确查找(如 "XericUI.MyData.speed")</summary>
public AvrgMvvmBinding Find(string fullPath)
{
if (string.IsNullOrEmpty(fullPath)) return null;
var parts = fullPath.Split('.');
var current = _root;
foreach (var part in parts)
{
if (!current.Children.TryGetValue(part, out current))
return null;
}
return current?.Binding;
}
/// <summary>通过成员名查找(如 "speed")— O(1) 直接索引</summary>
public AvrgMvvmBinding FindByMemberName(string memberName)
{
_memberNameIndex.TryGetValue(memberName, out var binding);
return binding;
}
/// <summary>清除所有索引</summary>
public void Clear()
{
_root.Children.Clear();
_memberNameIndex.Clear();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0fbb9c5ea2b8b5644a49559e7b6260e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -1,57 +1,42 @@
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; }
/// <summary>
/// 样式表命名空间(用于 SuperStyleSheet 样式查询)
/// 默认 "default"。可由类级或字段级 [XuiavrgStyleNamespace] 覆写
/// </summary>
public string StyleNamespace { get; set; } = "default";
// ============ UI 映射 ============
/// <summary>功能标记</summary>
public XuiavrgFieldTag Tag { get; set; } = XuiavrgFieldTag.None;
@@ -67,21 +52,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 +75,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,27 +46,20 @@ namespace XericUI.ReflectionUIGenerator
foreach (var gEntry in grouped)
{
// 编组内第一个成员作为主元数据
var first = gEntry.First();
first.SubMetaDataList ??= new List<AvrgMemberMetaData>();
foreach (var member in gEntry)
{
if (member == first) continue;
// 成员自身的 Split 子属性合并到父级
if (member.SubMetaDataList?.Count > 0)
{
if (first.SubMetaDataList == null)
first.SubMetaDataList = new List<AvrgMemberMetaData>();
first.SubMetaDataList.AddRange(member.SubMetaDataList);
// Merge: 成员自身作为子属性加入,保留独立 MemberInfo/TargetInstance
first.SubMetaDataList.Add(member);
}
}
merged.Add(first);
}
// 非编组直接追加
merged.AddRange(nonGrouped);
_metaDataList = merged;
}
@@ -85,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(
@@ -95,69 +88,78 @@ namespace XericUI.ReflectionUIGenerator
AvrgMvvmResolverConfig resolverConfig = null)
{
var bindingCollection = new AvrgMvvmBindingCollection();
bindingCollection.SourceMetaDataSet = this;
var scheduler = new AvrgMvvmScheduler(resolverConfig);
// ★ 构建前缀树索引
var trie = new AvrgBindingTrie();
string typePrefix = businessTarget != null
? businessTarget.GetType().FullName + "."
: "";
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>();
foreach (var subMetaData in metaData.SubMetaDataList)
{
string subPath = subMetaData.PropertyPath;
if (string.IsNullOrEmpty(subPath)) continue;
// 创建子属性访问器
var subAccessor = new AvrgSubPropertyAccessor(
accessor, subPath, subMetaData);
var subBinding = new AvrgMvvmBinding
{
MetaData = subMetaData,
};
// Merge 路径:子字段有自己的 MemberInfo/TargetInstance(来自编组成员)
if (subMetaData.MemberInfo != null && subMetaData.TargetInstance != null)
{
var capturedSub = subMetaData;
subBinding.ReadFunc = () => capturedSub.GetValue();
subBinding.WriteAction = (val) =>
Accessor = subAccessor,
ReadFunc = () => ReadSubProperty(accessor.GetValue(), subPath),
WriteAction = (val) =>
{
capturedSub.SetValue(val);
scheduler.MarkDataDirty(capturedBinding);
};
}
// Split 路径:通过 PropertyPath 从父值访问(来自 XuiavrgSplit)
else if (!string.IsNullOrEmpty(subMetaData.PropertyPath))
{
string subPath = subMetaData.PropertyPath;
subBinding.ReadFunc = () => ReadSubProperty(metaData.GetValue(), subPath);
subBinding.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);
};
}
else
{
continue; // 无法创建绑定,跳过
}
},
};
binding.SubBindings.Add(subBinding);
}
@@ -166,24 +168,24 @@ namespace XericUI.ReflectionUIGenerator
// 注册到调度器
scheduler.Register(binding);
bindingCollection.Add(binding);
// ★ 插入前缀树(完整路径: Type.FullName.MemberName)
string fullPath = typePrefix + metaData.MemberName;
trie.Insert(fullPath, binding);
}
bindingCollection.SetTrie(trie);
bindingCollection.AttachScheduler(scheduler);
return bindingCollection;
}
// ============ 子属性访问辅助方法 ============
/// <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}'");
@@ -201,10 +203,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))
@@ -240,8 +238,6 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>
/// 对当前元数据集执行属性检查验证
/// </summary>
/// <param name="valueRangeConfig">值范围配置(可选,不传入则不执行范围检查)</param>
/// <returns>验证结果集合</returns>
public AvrgValidationResultSet Validate(AvrgValueRangeConfig valueRangeConfig = null)
{
var resultSet = new AvrgValidationResultSet();
@@ -251,7 +247,6 @@ namespace XericUI.ReflectionUIGenerator
if (metaData.Hide)
continue;
// 只有标记了 PropertyCheck 的才验证
if (!metaData.Required && !metaData.ValidateReasonable
&& !metaData.ValidateWarning && !metaData.ValidateInvalid)
{
@@ -259,13 +254,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);
}
@@ -273,9 +268,6 @@ namespace XericUI.ReflectionUIGenerator
return resultSet;
}
/// <summary>
/// 验证单个成员
/// </summary>
private AvrgMemberValidationResult CheckSingleMember(
AvrgMemberMetaData metaData,
object value,
@@ -287,7 +279,6 @@ namespace XericUI.ReflectionUIGenerator
CurrentValueString = value?.ToString() ?? "(null)",
};
// 根据 CheckKey 查找值范围规则
AvrgValueRangeEntry rangeEntry = null;
string checkKey = !string.IsNullOrEmpty(metaData.PropertyCheckKey)
? metaData.PropertyCheckKey
@@ -298,22 +289,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);
@@ -327,7 +315,6 @@ namespace XericUI.ReflectionUIGenerator
}
}
// 如果没有范围配置,无法继续检查
if (rangeEntry == null)
{
result.Result = AvrgMemberCheckResult.Valid;
@@ -335,7 +322,7 @@ namespace XericUI.ReflectionUIGenerator
return result;
}
// ——— 优先级 2: 合理值检查(最高优先级,命中即通过) ———
// 优先级 2: 合理值检查
if (metaData.ValidateReasonable && rangeEntry.IsReasonable(value))
{
result.Result = AvrgMemberCheckResult.Valid;
@@ -343,7 +330,7 @@ namespace XericUI.ReflectionUIGenerator
return result;
}
// ——— 优先级 3: 无效值检查 ———
// 优先级 3: 无效值检查
if (metaData.ValidateInvalid && rangeEntry.IsInvalid(value))
{
result.Result = AvrgMemberCheckResult.Invalid;
@@ -351,7 +338,7 @@ namespace XericUI.ReflectionUIGenerator
return result;
}
// ——— 优先级 4: 警告值检查 ———
// 优先级 4: 警告值检查
if (metaData.ValidateWarning && rangeEntry.IsWarning(value))
{
result.Result = AvrgMemberCheckResult.Warning;
@@ -359,7 +346,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] 特性展开生成,每个子绑定对应一个拆分后的子属性
@@ -40,21 +45,75 @@ namespace XericUI.ReflectionUIGenerator
/// <summary>数据侧写入的缓存值</summary>
public object DataCachedValue { get; set; }
// ============ 验证状态 ============
private ValidationState _validationState = ValidationState.Normal;
/// <summary>当前验证状态</summary>
public ValidationState CurrentValidationState => _validationState;
/// <summary>验证状态变更事件 — UI 组件订阅以切换样式</summary>
public event Action<ValidationState> OnValidationStateChanged;
/// <summary>
/// 校验值是否满足元数据约束
/// 返回:Normal(通过)、Warning(放行但警告)、Invalid(驳回)
/// </summary>
public ValidationState ValidateValue(object value)
{
if (MetaData == null) return ValidationState.Normal;
// 1. 空值检查(Required)
if (MetaData.Required && (value == null || (value is string s && string.IsNullOrEmpty(s))))
return ValidationState.Invalid;
// 2. 范围检查(RangeMin/RangeMax)
if (value is IConvertible)
{
float num = Convert.ToSingle(value);
if (MetaData.RangeMin.HasValue && num < MetaData.RangeMin.Value)
return ValidationState.Invalid;
if (MetaData.RangeMax.HasValue && num > MetaData.RangeMax.Value)
return ValidationState.Invalid;
}
return ValidationState.Normal;
}
/// <summary>更新验证状态并触发回调(仅状态变化时触发)</summary>
internal void SetValidationState(ValidationState newState)
{
if (_validationState != newState)
{
_validationState = newState;
OnValidationStateChanged?.Invoke(_validationState);
}
}
/// <summary>重置验证状态为 Normal(不触发事件)</summary>
internal void ResetValidationState()
{
_validationState = ValidationState.Normal;
}
// ============ UI 绑定 ============
/// <summary>UI 值变更回调委托 — UI 组件订阅此回调以更新显示</summary>
public Action<object> OnUIDataChanged;
/// <summary>UI 值变更回调委托 — UI 组件订阅此回调以更新显示(附带验证状态)</summary>
public Action<object, ValidationState> OnUIDataChanged;
/// <summary>通知 UI 更新值</summary>
public void NotifyUIUpdate(object value)
/// <summary>通知 UI 更新值(带验证状态)</summary>
public void NotifyUIUpdate(object value, ValidationState state)
{
OnUIDataChanged?.Invoke(value);
OnUIDataChanged?.Invoke(value, state);
}
/// <summary>清除所有 UI 变更订阅</summary>
public void ClearUIEvent()
{
OnUIDataChanged = null;
OnValidationStateChanged = null;
}
// ============ 便捷方法 ============
@@ -4,12 +4,13 @@ using System.Collections.Generic;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 绑定集合 — 统一管理所有 AvrgMvvmBinding
/// 绑定集合 — 统一管理所有 AvrgMvvmBinding,提供字符串索引器和前缀树快速查找
/// </summary>
public class AvrgMvvmBindingCollection : IEnumerable<AvrgMvvmBinding>
{
private List<AvrgMvvmBinding> _bindings = new List<AvrgMvvmBinding>();
private AvrgMvvmScheduler _scheduler;
private AvrgBindingTrie _trie;
/// <summary>
/// 绑定的元数据集引用(可选)
@@ -21,6 +22,11 @@ namespace XericUI.ReflectionUIGenerator
/// </summary>
public int Count => _bindings.Count;
/// <summary>
/// 调度器引用(在 BuildMDVM 时设置)
/// </summary>
public AvrgMvvmScheduler Scheduler => _scheduler;
/// <summary>
/// 添加绑定
/// </summary>
@@ -29,16 +35,53 @@ namespace XericUI.ReflectionUIGenerator
_bindings.Add(binding);
}
/// <summary>
/// 设置前缀树索引(由 BuildMDVM 构建后注入)
/// </summary>
internal void SetTrie(AvrgBindingTrie trie)
{
_trie = trie;
}
/// <summary>
/// 获取指定索引的绑定
/// </summary>
public AvrgMvvmBinding this[int index] => _bindings[index];
/// <summary>
/// 获取指定成员名称的绑定
/// 通过成员名访问绑定(如 bindingCollection["speed"])
/// get: 从数据源读取当前值
/// set: 走完整的 MVVM 管线:WriteFromData → MarkDataDirty → Flush → UI 同步
/// </summary>
public object this[string memberName]
{
get
{
var binding = FindByMemberName(memberName);
return binding?.ReadFromDataSource();
}
set
{
var binding = FindByMemberName(memberName);
if (binding != null && _scheduler != null)
{
binding.WriteFromData(value);
_scheduler.MarkDataDirty(binding);
_scheduler.Flush();
}
}
}
/// <summary>
/// 获取指定成员名称的绑定(优先查 Trie,回退线性查找)
/// </summary>
public AvrgMvvmBinding FindByMemberName(string memberName)
{
if (_trie != null)
{
var found = _trie.Find(memberName) ?? _trie.FindByMemberName(memberName);
if (found != null) return found;
}
return _bindings.Find(b => b.MetaData?.MemberName == memberName);
}
@@ -72,6 +115,7 @@ namespace XericUI.ReflectionUIGenerator
public void Clear()
{
_bindings.Clear();
_trie?.Clear();
_scheduler?.Clear();
}
@@ -9,8 +9,11 @@ namespace XericUI.ReflectionUIGenerator
///
/// 冲突解决策略:
/// - 仅数据脏 → 通知 UI 更新
/// - 仅 UI 脏 → 写入数据源
/// - 仅 UI 脏 → 写入数据源(写入前校验,Invalid 驳回)
/// - 双方都脏 → 走配置的冲突解决策略
///
/// 验证拦截:在 CommitToDataSource 前通过 Binding.ValidateValue() 校验。
/// Invalid 值被驳回不写入,仅通知 UI 进入无效状态。
/// </summary>
public class AvrgMvvmScheduler
{
@@ -79,20 +82,36 @@ namespace XericUI.ReflectionUIGenerator
if (uiDirty && dataDirty)
{
// 双方都脏 — 走冲突解决
// 双方都脏 — 走冲突解决(内部含验证)
ResolveConflict(binding);
}
else if (dataDirty)
{
// 仅数据侧更新 → 通知 UI
// 仅数据侧更新 → 验证后通知 UI
var value = binding.DataCachedValue ?? binding.ReadFromDataSource();
var state = binding.ValidateValue(value);
binding.SetValidationState(state);
binding.CommitToDataSource(value);
binding.NotifyUIUpdate(value);
binding.NotifyUIUpdate(value, state);
}
else if (uiDirty)
{
// 仅 UI 侧更新 → 写入数据源
binding.CommitToDataSource(binding.UICachedValue);
// 仅 UI 侧更新 → 验证后写入数据源
var value = binding.UICachedValue;
var state = binding.ValidateValue(value);
if (state == ValidationState.Invalid)
{
// ★ 无效值:驳回不写入,仅通知 UI 进入无效态
binding.SetValidationState(state);
binding.NotifyUIUpdate(value, state);
return;
}
binding.SetValidationState(state);
binding.CommitToDataSource(value);
// UI 是发起方,不再通知自身(但验证状态变更需通知)
}
}
@@ -106,8 +125,10 @@ namespace XericUI.ReflectionUIGenerator
var resolvedValue = OnConflictDetected.Invoke(binding, DefaultStrategy);
if (resolvedValue != null)
{
var state = binding.ValidateValue(resolvedValue);
binding.SetValidationState(state);
binding.CommitToDataSource(resolvedValue);
binding.NotifyUIUpdate(resolvedValue);
binding.NotifyUIUpdate(resolvedValue, state);
return;
}
}
@@ -120,8 +141,10 @@ namespace XericUI.ReflectionUIGenerator
// 2. 检查 ResolverConfig 中的自定义策略
if (_resolverConfig != null && _resolverConfig.TryResolve(binding, out object configResolvedValue))
{
var state = binding.ValidateValue(configResolvedValue);
binding.SetValidationState(state);
binding.CommitToDataSource(configResolvedValue);
binding.NotifyUIUpdate(configResolvedValue);
binding.NotifyUIUpdate(configResolvedValue, state);
return;
}
@@ -129,38 +152,70 @@ namespace XericUI.ReflectionUIGenerator
switch (DefaultStrategy)
{
case ConflictResolutionStrategy.PreferUI:
binding.CommitToDataSource(binding.UICachedValue);
binding.NotifyUIUpdate(binding.UICachedValue);
break;
case ConflictResolutionStrategy.PreferData:
var dataValue = binding.DataCachedValue ?? binding.ReadFromDataSource();
binding.CommitToDataSource(dataValue);
binding.NotifyUIUpdate(dataValue);
break;
case ConflictResolutionStrategy.CompareValues:
// 比较UI值和数据值
object uiVal = binding.UICachedValue;
object dataVal = binding.DataCachedValue ?? binding.ReadFromDataSource();
if (Equals(uiVal, dataVal))
{
// 值相同,忽略冲突
binding.NotifyUIUpdate(dataVal);
var val = binding.UICachedValue;
var state = binding.ValidateValue(val);
if (state == ValidationState.Invalid)
{
binding.SetValidationState(state);
binding.NotifyUIUpdate(val, state);
return;
}
binding.SetValidationState(state);
binding.CommitToDataSource(val);
binding.NotifyUIUpdate(val, state);
break;
}
// 值不同,回退到 PreferUI
binding.CommitToDataSource(binding.UICachedValue);
binding.NotifyUIUpdate(binding.UICachedValue);
Debug.LogWarning($"[AvrgMvvmScheduler] 值冲突(CompareValues回退PreferUI): {binding.MetaData?.MemberName}");
break;
case ConflictResolutionStrategy.PreferData:
{
var dataValue = binding.DataCachedValue ?? binding.ReadFromDataSource();
var state = binding.ValidateValue(dataValue);
binding.SetValidationState(state);
binding.CommitToDataSource(dataValue);
binding.NotifyUIUpdate(dataValue, state);
break;
}
case ConflictResolutionStrategy.CompareValues:
{
object uiVal = binding.UICachedValue;
object dataVal = binding.DataCachedValue ?? binding.ReadFromDataSource();
if (Equals(uiVal, dataVal))
{
// 值相同,忽略冲突
var state2 = binding.ValidateValue(dataVal);
binding.SetValidationState(state2);
binding.NotifyUIUpdate(dataVal, state2);
break;
}
// 值不同,回退到 PreferUI
var val = binding.UICachedValue;
var state = binding.ValidateValue(val);
if (state == ValidationState.Invalid)
{
binding.SetValidationState(state);
binding.NotifyUIUpdate(val, state);
return;
}
binding.SetValidationState(state);
binding.CommitToDataSource(val);
binding.NotifyUIUpdate(val, state);
Debug.LogWarning($"[AvrgMvvmScheduler] 值冲突(CompareValues回退PreferUI): {binding.MetaData?.MemberName}");
break;
}
default:
binding.CommitToDataSource(binding.UICachedValue);
binding.NotifyUIUpdate(binding.UICachedValue);
break;
{
var val = binding.UICachedValue;
var state = binding.ValidateValue(val);
binding.SetValidationState(state);
binding.CommitToDataSource(val);
binding.NotifyUIUpdate(val, state);
break;
}
}
}
@@ -0,0 +1,79 @@
using System;
using System.Reflection;
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 子属性值访问器 — 通过父访问器 + PropertyPath 访问结构的子字段
///
/// 用于拆分属性([XuiavrgSplit])场景,如 Vector3 的 x/y/z 分量。
/// GetValue 通过反射读取父值的子字段/属性;SetValue 由绑定层的 WriteAction 处理。
/// </summary>
internal class AvrgSubPropertyAccessor : IAvrgValueAccessor
{
private readonly IAvrgValueAccessor _parentAccessor;
private readonly string _propertyPath;
private readonly AvrgMemberMetaData _metaData;
public AvrgSubPropertyAccessor(
IAvrgValueAccessor parentAccessor,
string propertyPath,
AvrgMemberMetaData metaData)
{
_parentAccessor = parentAccessor ?? throw new ArgumentNullException(nameof(parentAccessor));
_propertyPath = propertyPath ?? throw new ArgumentNullException(nameof(propertyPath));
_metaData = metaData;
}
public Type ValueType => _metaData?.ValueType ?? typeof(float);
public bool IsReadOnly => _parentAccessor.IsReadOnly;
public object GetValue()
{
var parentValue = _parentAccessor.GetValue();
if (parentValue == null || string.IsNullOrEmpty(_propertyPath))
return null;
var type = parentValue.GetType();
var field = type.GetRuntimeField(_propertyPath);
if (field != null) return field.GetValue(parentValue);
var prop = type.GetRuntimeProperty(_propertyPath);
return prop?.GetValue(parentValue);
}
/// <summary>
/// 子属性设置由绑定层的 WriteAction 统一处理(ReadSubProperty/WriteSubProperty),
/// 此方法不应被直接调用。
/// </summary>
public void SetValue(object value)
{
var parentValue = _parentAccessor.GetValue();
if (parentValue == null) return;
var type = parentValue.GetType();
var field = type.GetRuntimeField(_propertyPath);
if (field != null)
{
field.SetValue(parentValue, value);
_parentAccessor.SetValue(parentValue);
return;
}
var prop = type.GetRuntimeProperty(_propertyPath);
if (prop != null && prop.CanWrite)
{
prop.SetValue(parentValue, value);
_parentAccessor.SetValue(parentValue);
}
}
public string FormatDisplay(object value)
{
if (_metaData?.FormatText != null && value is IFormattable formattable)
return formattable.ToString(_metaData.FormatText, null);
return value?.ToString() ?? "";
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c0ab49f7e492a8d448cdcc9f214325f6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3a2aac04058602749be2f5153db24b69
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b6a7d2f0806496e45b383e7d26eca214
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,13 @@
namespace XericUI.ReflectionUIGenerator
{
/// <summary>
/// 值校验状态枚举
/// Normal = 正常(通过校验),Warning = 警告(值在警告范围但放行),Invalid = 无效(值被驳回不写入)
/// </summary>
public enum ValidationState
{
Normal = 0,
Warning = 1,
Invalid = 2
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e20b793a32d670a4d95e20189dd27694
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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 生命周期
@@ -417,15 +308,14 @@ namespace XericUI.ReflectionUIGenerator
foreach (var binding in _bindings)
{
var value = binding.ReadFromDataSource();
binding.NotifyUIUpdate(value);
binding.NotifyUIUpdate(value, ValidationState.Normal);
// 子绑定也做初始值推送
if (binding.SubBindings?.Count > 0)
{
foreach (var subBinding in binding.SubBindings)
{
var subValue = subBinding.ReadFromDataSource();
subBinding.NotifyUIUpdate(subValue);
subBinding.NotifyUIUpdate(subValue, ValidationState.Normal);
}
}
}
@@ -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,
}
}
@@ -85,6 +85,20 @@ namespace XericUI.ReflectionUIGenerator
#endregion
#region Unity 生命周期
private void OnEnable()
{
XuiavrgStyleDefaults.EnsureDefaults();
}
private void OnDestroy()
{
ClearAll();
}
#endregion
#region 核心生命周期
public void BuildFromMetaDataSet(AvrgMemberMetaDataSet metaDataSet,
@@ -113,16 +127,17 @@ namespace XericUI.ReflectionUIGenerator
if (bindingCollection != null)
{
var matchedBindings = bindingCollection.FindByMemberName(metaData.MemberName);
if (matchedBindings != null)
var scheduler = bindingCollection.Scheduler;
var matchedBinding = bindingCollection.FindByMemberName(metaData.MemberName);
if (matchedBinding != null)
{
entry.AddBinding(matchedBindings, null);
entry.AddBinding(matchedBinding, scheduler);
}
else
{
foreach (var binding in bindingCollection)
{
entry.AddBinding(binding, null);
entry.AddBinding(binding, scheduler);
break;
}
}
@@ -142,13 +157,14 @@ namespace XericUI.ReflectionUIGenerator
if (CurrentEntries != null && bindingCollection != null)
{
var scheduler = bindingCollection.Scheduler;
foreach (var entry in CurrentEntries)
{
if (entry is XuiFieldTempEntry tempEntry)
{
var matched = bindingCollection.FindByMemberName(entry.EntryName);
if (matched != null)
tempEntry.AddBinding(matched, null);
tempEntry.AddBinding(matched, scheduler);
}
}
}
@@ -443,12 +459,7 @@ namespace XericUI.ReflectionUIGenerator
_leafGroupMap.Clear();
}
private void OnDestroy()
{
ClearAll();
}
#endregion
}
#endregion
}
@@ -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,25 @@ 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();
// ★ 收集类级别 IXuiavrgAttribute — 作为所有字段的基础模板
var classAttributes = type.GetCustomAttributes()
.OfType<IXuiavrgAttribute>()
.ToList();
foreach (var memberInfo in type.GetRuntimeMemberInfo())
{
@@ -39,55 +47,39 @@ 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)
// ★ 第1步: 先应用类级特性(设置默认值)
foreach (var classAttr in classAttributes)
{
case PropertyInfo pi:
metaData.IsProperty = true;
metaData.ValueType = pi.PropertyType;
break;
case FieldInfo fi:
metaData.IsProperty = false;
metaData.ValueType = fi.FieldType;
break;
classAttr.BindMetaData(metaData);
}
// 提取字段默认值(类初始值)
// 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
var attributes = memberInfo.GetCustomAttributes()
// ★ 第2步: 再应用字段级特性(可覆盖类级设置)
var fieldAttributes = memberInfo.GetCustomAttributes()
.OfType<IXuiavrgAttribute>()
.ToList();
metaData.Attributes = attributes;
metaData.Attributes = fieldAttributes;
// 按序执行 BindMetaData
foreach (var attr in attributes)
foreach (var fieldAttr in fieldAttributes)
{
attr.BindMetaData(metaData);
fieldAttr.BindMetaData(metaData);
}
metaDataSet.Add(metaData);
@@ -103,11 +95,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('_', ' ');
}
}
}