优化ui绑定侧逻辑
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -91,6 +91,12 @@ namespace XericUI.ReflectionUIGenerator
|
||||
bindingCollection.SourceMetaDataSet = this;
|
||||
var scheduler = new AvrgMvvmScheduler(resolverConfig);
|
||||
|
||||
// ★ 构建前缀树索引
|
||||
var trie = new AvrgBindingTrie();
|
||||
string typePrefix = businessTarget != null
|
||||
? businessTarget.GetType().FullName + "."
|
||||
: "";
|
||||
|
||||
foreach (var metaData in _metaDataList)
|
||||
{
|
||||
if (metaData.AccessorFactory == null) continue;
|
||||
@@ -137,9 +143,14 @@ namespace XericUI.ReflectionUIGenerator
|
||||
string subPath = subMetaData.PropertyPath;
|
||||
if (string.IsNullOrEmpty(subPath)) continue;
|
||||
|
||||
// 创建子属性访问器
|
||||
var subAccessor = new AvrgSubPropertyAccessor(
|
||||
accessor, subPath, subMetaData);
|
||||
|
||||
var subBinding = new AvrgMvvmBinding
|
||||
{
|
||||
MetaData = subMetaData,
|
||||
Accessor = subAccessor,
|
||||
ReadFunc = () => ReadSubProperty(accessor.GetValue(), subPath),
|
||||
WriteAction = (val) =>
|
||||
{
|
||||
@@ -157,8 +168,13 @@ 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;
|
||||
}
|
||||
|
||||
@@ -45,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,13 @@
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 值校验状态枚举
|
||||
/// Normal = 正常(通过校验),Warning = 警告(值在警告范围但放行),Invalid = 无效(值被驳回不写入)
|
||||
/// </summary>
|
||||
public enum ValidationState
|
||||
{
|
||||
Normal = 0,
|
||||
Warning = 1,
|
||||
Invalid = 2
|
||||
}
|
||||
}
|
||||
@@ -308,14 +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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,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;
|
||||
}
|
||||
}
|
||||
@@ -156,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -457,5 +459,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
_leafGroupMap.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,17 @@ using UnityEngine.UI;
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 颜色值组件 — 处理 Color 类型的显示
|
||||
/// 通过 Image.color 展示当前颜色值
|
||||
/// 颜色修改可通过 RGB 子节点 InputField 或额外的颜色选择逻辑完成
|
||||
/// Color 值组件 — 通过 Image 显示颜色,可选点击弹出颜色选择器
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDColorValue")]
|
||||
public class XUIAvrgMDColorValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("用于展示颜色的 Image 组件(未设置则自动查找)")]
|
||||
private Image _colorDisplay;
|
||||
[SerializeField, Tooltip("关联的 Image 组件(未设置则自动查找)")]
|
||||
private Image _colorImage;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_colorDisplay == null) _colorDisplay = GetComponent<Image>();
|
||||
if (_colorImage == null) _colorImage = GetComponent<Image>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
@@ -30,29 +27,23 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
if (value is Color color && _colorDisplay != null)
|
||||
{
|
||||
_colorDisplay.color = color;
|
||||
}
|
||||
UpdateValueDisplay(value, state);
|
||||
};
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
if (initialValue is Color initColor && _colorDisplay != null)
|
||||
{
|
||||
_colorDisplay.color = initColor;
|
||||
}
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
protected override void UpdateDisplayValue(object value)
|
||||
protected override void UpdateValueDisplay(object value, ValidationState state)
|
||||
{
|
||||
if (value is Color color && _colorDisplay != null)
|
||||
{
|
||||
_colorDisplay.color = color;
|
||||
}
|
||||
// 值显示:刷新 Image.color
|
||||
if (_colorImage != null && value is Color c)
|
||||
_colorImage.color = c;
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
@@ -20,6 +20,8 @@ namespace XericUI.ReflectionUIGenerator
|
||||
private Dropdown _dropdown;
|
||||
#endif
|
||||
|
||||
private List<string> _options;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
#if dUI_TextMeshPro
|
||||
@@ -29,6 +31,24 @@ namespace XericUI.ReflectionUIGenerator
|
||||
#endif
|
||||
}
|
||||
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
public override void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
base.OnPoolGet(metaData);
|
||||
_options = BuildOptions(metaData?.ValueType);
|
||||
}
|
||||
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
if (_dropdown != null)
|
||||
_dropdown.onValueChanged.RemoveAllListeners();
|
||||
_options = null;
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
@@ -39,66 +59,71 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (binding == null || accessor == null || _dropdown == null) return;
|
||||
|
||||
// 构建选项列表
|
||||
var options = BuildOptions(accessor.ValueType);
|
||||
// 填充选项
|
||||
_dropdown.ClearOptions();
|
||||
_dropdown.AddOptions(options);
|
||||
_dropdown.AddOptions(_options);
|
||||
|
||||
// UI → 数据
|
||||
_dropdown.onValueChanged.AddListener(index =>
|
||||
{
|
||||
if (index >= 0 && index < options.Count)
|
||||
if (index >= 0 && index < _options.Count)
|
||||
{
|
||||
object convertedVal = ConvertOptionToValue(options[index], accessor.ValueType);
|
||||
object convertedVal = ConvertOptionToValue(_options[index], accessor.ValueType);
|
||||
binding.WriteFromUI(convertedVal);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
}
|
||||
});
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
int idx = FindOptionIndex(value, options);
|
||||
int idx = FindOptionIndex(value);
|
||||
if (idx >= 0)
|
||||
_dropdown.SetValueWithoutNotify(idx);
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
int initialIdx = FindOptionIndex(initialValue, options);
|
||||
int initialIdx = FindOptionIndex(initialValue);
|
||||
if (initialIdx >= 0)
|
||||
_dropdown.SetValueWithoutNotify(initialIdx);
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
private List<string> BuildOptions(System.Type valueType)
|
||||
private List<string> BuildOptions(Type valueType)
|
||||
{
|
||||
if (BoundMetaData?.PresetOptions != null && BoundMetaData.PresetOptions.Count > 0)
|
||||
return BoundMetaData.PresetOptions.ToList();
|
||||
|
||||
if (valueType != null && valueType.IsEnum)
|
||||
return System.Enum.GetNames(valueType).ToList();
|
||||
return Enum.GetNames(valueType).ToList();
|
||||
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
private int FindOptionIndex(object value, List<string> options)
|
||||
private int FindOptionIndex(object value)
|
||||
{
|
||||
if (value == null || options.Count == 0) return -1;
|
||||
if (value == null || _options == null || _options.Count == 0) return -1;
|
||||
|
||||
string valueStr = value.ToString();
|
||||
for (int i = 0; i < options.Count; i++)
|
||||
for (int i = 0; i < _options.Count; i++)
|
||||
{
|
||||
if (options[i] == valueStr) return i;
|
||||
if (_options[i] == valueStr) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static object ConvertOptionToValue(string option, System.Type targetType)
|
||||
private static object ConvertOptionToValue(string option, 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 Enum.Parse(targetType, option);
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// InputField 值组件 — 处理 string / float / int 类型的文本输入交互
|
||||
/// InputField 值组件 — 处理 string / float / int / double 类型的文本输入交互
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDInputFieldValue")]
|
||||
public class XUIAvrgMDInputFieldValue : XUIAvrgMDValue
|
||||
@@ -26,10 +25,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
[SerializeField, Tooltip("减少按钮(点击按步进值 -)")]
|
||||
private Button _decrementButton;
|
||||
|
||||
// ============ 样式表参数 ============
|
||||
private readonly StyleMember _styleStepValue = new() { StylePath = "xuiavrg/input/stepValue" };
|
||||
|
||||
// ============ 生命周期 ============
|
||||
private const float DefaultStep = 1f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
@@ -40,16 +36,18 @@ namespace XericUI.ReflectionUIGenerator
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
_styleStepValue.Register();
|
||||
}
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
protected override void OnDisable()
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
base.OnDisable();
|
||||
_styleStepValue.Unregister();
|
||||
if (_incrementButton != null)
|
||||
_incrementButton.onClick.RemoveAllListeners();
|
||||
if (_decrementButton != null)
|
||||
_decrementButton.onClick.RemoveAllListeners();
|
||||
if (_inputField != null)
|
||||
_inputField.onEndEdit.RemoveAllListeners();
|
||||
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
@@ -64,6 +62,7 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (binding == null || accessor == null || _inputField == null) return;
|
||||
|
||||
// 根据类型设置输入模式
|
||||
#if dUI_TextMeshPro
|
||||
if (accessor.ValueType == typeof(float) || accessor.ValueType == typeof(int)
|
||||
|| accessor.ValueType == typeof(double))
|
||||
@@ -92,33 +91,29 @@ namespace XericUI.ReflectionUIGenerator
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
});
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
_inputField.SetTextWithoutNotify(accessor.FormatDisplay(value));
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
_inputField.SetTextWithoutNotify(accessor.FormatDisplay(initialValue));
|
||||
|
||||
RefreshCommonNodes();
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ 步进 ============
|
||||
|
||||
private float GetStepValue()
|
||||
{
|
||||
var val = GetStyleValue(_styleStepValue);
|
||||
return val?.GetValueAsFloat() ?? 1f;
|
||||
}
|
||||
|
||||
private void OnIncrementClicked()
|
||||
{
|
||||
if (CurrentAccessor?.IsReadOnly == true || _inputField == null) return;
|
||||
if (!float.TryParse(_inputField.text, out float current)) return;
|
||||
float step = GetStepValue();
|
||||
float newVal = current + step;
|
||||
float newVal = current + DefaultStep;
|
||||
_inputField.text = CurrentAccessor.FormatDisplay(
|
||||
ConvertValueForAccessor(newVal, CurrentAccessor.ValueType));
|
||||
}
|
||||
@@ -127,12 +122,32 @@ namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
if (CurrentAccessor?.IsReadOnly == true || _inputField == null) return;
|
||||
if (!float.TryParse(_inputField.text, out float current)) return;
|
||||
float step = GetStepValue();
|
||||
float newVal = current - step;
|
||||
float newVal = current - DefaultStep;
|
||||
_inputField.text = CurrentAccessor.FormatDisplay(
|
||||
ConvertValueForAccessor(newVal, CurrentAccessor.ValueType));
|
||||
}
|
||||
|
||||
// ============ 验证状态回调 ============
|
||||
|
||||
protected override void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
base.OnValidationStateChanged(state);
|
||||
|
||||
// InputField 文字颜色随验证状态变化(通过 Text 组件)
|
||||
if (_inputField != null)
|
||||
{
|
||||
var textComponent = _inputField.textComponent;
|
||||
if (textComponent != null)
|
||||
{
|
||||
textComponent.color = state == ValidationState.Invalid
|
||||
? Color.red
|
||||
: state == ValidationState.Warning
|
||||
? new Color(1f, 0.92f, 0.016f)
|
||||
: Color.white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertValueForAccessor(float val, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(int)) return (int)val;
|
||||
|
||||
@@ -1,32 +1,19 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 标签值组件 — 处理只读文本的纯展示
|
||||
/// 适用于 Label / Context / Unit / ReadOnly 等展示型 Tag
|
||||
/// Label 只读值组件 — 纯文本展示,无交互控件
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDLabelValue")]
|
||||
public class XUIAvrgMDLabelValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 Text 组件(未设置则自动查找)")]
|
||||
private UnityEngine.UI.Text _uiText;
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 TMP_Text 组件(未设置则自动查找)")]
|
||||
[SerializeField, Tooltip("关联的 TMP_Text 组件(标签文本 + 值文本共用一个 Text 时使用)")]
|
||||
private TMPro.TMP_Text _tmpText;
|
||||
#endif
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_uiText == null) _uiText = GetComponent<UnityEngine.UI.Text>();
|
||||
#if dUI_TextMeshPro
|
||||
if (_tmpText == null) _tmpText = GetComponent<TMPro.TMP_Text>();
|
||||
#endif
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
IAvrgValueAccessor accessor,
|
||||
AvrgMvvmBinding binding,
|
||||
@@ -37,31 +24,14 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
SetText(value);
|
||||
UpdateValueDisplay(value, state);
|
||||
};
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
SetText(initialValue);
|
||||
}
|
||||
|
||||
private void SetText(object value)
|
||||
{
|
||||
string displayStr = value?.ToString() ?? "";
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
if (_tmpText != null)
|
||||
{
|
||||
_tmpText.text = displayStr;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_uiText != null)
|
||||
_uiText.text = displayStr;
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Slider 值组件 — 处理 float / int / double 类型的滑块交互
|
||||
/// 适用于 Shader Range 属性或标记了 [XuiavrgFieldTag(Slider)] 的字段
|
||||
/// 适用 Shader Range 属性或标记了 [XuiavrgFieldTag(Slider)] 的字段
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDSliderValue")]
|
||||
public class XUIAvrgMDSliderValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 Slider 组件(未设置则自动查找)")]
|
||||
[SerializeField, Tooltip("关联的 Slider 组件(未设置则自动查找)")]
|
||||
private Slider _slider;
|
||||
|
||||
[Header("Slider 专属插槽")]
|
||||
@@ -24,37 +22,44 @@ namespace XericUI.ReflectionUIGenerator
|
||||
private Button _decrementButton;
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
[SerializeField]
|
||||
[Tooltip("值显示标签(可选,未设置则不更新)")]
|
||||
[SerializeField, Tooltip("值显示标签(可选,未设置则不更新)")]
|
||||
private TMPro.TMP_Text _valueLabel;
|
||||
#endif
|
||||
|
||||
// ============ 样式表参数 ============
|
||||
private readonly StyleMember _styleDefaultStep = new() { StylePath = "xuiavrg/slider/defaultStep" };
|
||||
private readonly StyleMember _styleMinStep = new() { StylePath = "xuiavrg/slider/minStep" };
|
||||
private readonly StyleMember _styleMaxStep = new() { StylePath = "xuiavrg/slider/maxStep" };
|
||||
|
||||
// ============ 生命周期 ============
|
||||
private const float DefaultStep = 0.1f;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_slider == null) _slider = GetComponent<Slider>();
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
public override void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
base.OnEnable();
|
||||
_styleDefaultStep.Register();
|
||||
_styleMinStep.Register();
|
||||
_styleMaxStep.Register();
|
||||
base.OnPoolGet(metaData);
|
||||
|
||||
if (_slider == null) return;
|
||||
|
||||
// 从元数据初始化 Slider 参数
|
||||
if (metaData?.RangeMin.HasValue == true)
|
||||
_slider.minValue = metaData.RangeMin.Value;
|
||||
if (metaData?.RangeMax.HasValue == true)
|
||||
_slider.maxValue = metaData.RangeMax.Value;
|
||||
_slider.wholeNumbers = metaData?.ValueType == typeof(int);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
base.OnDisable();
|
||||
_styleDefaultStep.Unregister();
|
||||
_styleMinStep.Unregister();
|
||||
_styleMaxStep.Unregister();
|
||||
// 清除按钮监听
|
||||
if (_incrementButton != null)
|
||||
_incrementButton.onClick.RemoveAllListeners();
|
||||
if (_decrementButton != null)
|
||||
_decrementButton.onClick.RemoveAllListeners();
|
||||
if (_slider != null)
|
||||
_slider.onValueChanged.RemoveAllListeners();
|
||||
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
@@ -69,17 +74,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// 增减按钮
|
||||
if (_incrementButton != null)
|
||||
_incrementButton.onClick.AddListener(OnIncrementClicked);
|
||||
@@ -99,8 +93,8 @@ namespace XericUI.ReflectionUIGenerator
|
||||
#endif
|
||||
});
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
float sliderVal = Convert.ToSingle(value);
|
||||
_slider.SetValueWithoutNotify(sliderVal);
|
||||
@@ -109,8 +103,14 @@ namespace XericUI.ReflectionUIGenerator
|
||||
if (_valueLabel != null)
|
||||
_valueLabel.text = accessor.FormatDisplay(value);
|
||||
#endif
|
||||
|
||||
// 委托基类处理标签合成 + 值文本
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
if (initialValue != null)
|
||||
@@ -121,23 +121,14 @@ namespace XericUI.ReflectionUIGenerator
|
||||
_valueLabel.text = accessor.FormatDisplay(initialValue);
|
||||
#endif
|
||||
}
|
||||
|
||||
RefreshCommonNodes();
|
||||
UpdateValueDisplay(initialValue ?? 0f, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ 步进值 ============
|
||||
|
||||
private float GetStepValue()
|
||||
{
|
||||
var val = GetStyleValue(_styleDefaultStep);
|
||||
if (val != null)
|
||||
{
|
||||
float step = val.GetValueAsFloat();
|
||||
float min = GetStyleValue(_styleMinStep)?.GetValueAsFloat() ?? 0.001f;
|
||||
float max = GetStyleValue(_styleMaxStep)?.GetValueAsFloat() ?? 1000f;
|
||||
return Mathf.Clamp(step, min, max);
|
||||
}
|
||||
return _slider.wholeNumbers ? 1f : 0.1f;
|
||||
return _slider.wholeNumbers ? 1f : DefaultStep;
|
||||
}
|
||||
|
||||
private void OnIncrementClicked()
|
||||
@@ -158,6 +149,28 @@ namespace XericUI.ReflectionUIGenerator
|
||||
_slider.value = newVal;
|
||||
}
|
||||
|
||||
// ============ 验证状态回调 ============
|
||||
|
||||
protected override void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
base.OnValidationStateChanged(state);
|
||||
|
||||
// Slider 填充色随验证状态变化
|
||||
if (_slider != null)
|
||||
{
|
||||
var fillArea = _slider.fillRect;
|
||||
if (fillArea != null && fillArea.TryGetComponent<Image>(out var fill))
|
||||
{
|
||||
fill.color = state switch
|
||||
{
|
||||
ValidationState.Invalid => Color.red,
|
||||
ValidationState.Warning => new Color(1f, 0.92f, 0.016f),
|
||||
_ => Color.white
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static object ConvertValueForAccessor(float val, Type targetType)
|
||||
{
|
||||
if (targetType == typeof(int)) return (int)val;
|
||||
|
||||
@@ -4,25 +4,22 @@ using UnityEngine.UI;
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// 纹理值组件 — 处理 Texture 类型的预览显示
|
||||
/// 通过 RawImage.texture 展示当前纹理
|
||||
/// Texture 值组件 — 通过 RawImage 显示纹理
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDTextureValue")]
|
||||
public class XUIAvrgMDTextureValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("用于展示纹理的 RawImage 组件(未设置则自动查找)")]
|
||||
private RawImage _textureDisplay;
|
||||
[SerializeField, Tooltip("关联的 RawImage 组件(未设置则自动查找)")]
|
||||
private RawImage _rawImage;
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
[SerializeField]
|
||||
[Tooltip("纹理名称标签(可选,用于显示纹理资源名)")]
|
||||
[SerializeField, Tooltip("纹理名称标签(可选)")]
|
||||
private TMPro.TMP_Text _nameLabel;
|
||||
#endif
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_textureDisplay == null) _textureDisplay = GetComponent<RawImage>();
|
||||
if (_rawImage == null) _rawImage = GetComponent<RawImage>();
|
||||
}
|
||||
|
||||
public override void BindToAccessor(
|
||||
@@ -35,35 +32,29 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
if (value is Texture tex && _textureDisplay != null)
|
||||
{
|
||||
_textureDisplay.texture = tex;
|
||||
}
|
||||
UpdateValueDisplay(value, state);
|
||||
};
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
protected override void UpdateValueDisplay(object value, ValidationState state)
|
||||
{
|
||||
// 值显示:刷新 RawImage.texture
|
||||
if (_rawImage != null && value is Texture tex)
|
||||
{
|
||||
_rawImage.texture = tex;
|
||||
#if dUI_TextMeshPro
|
||||
if (_nameLabel != null)
|
||||
{
|
||||
_nameLabel.text = value is Texture t && t != null ? t.name : "None";
|
||||
}
|
||||
_nameLabel.text = tex?.name ?? "";
|
||||
#endif
|
||||
};
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
if (initialValue is Texture initTex && _textureDisplay != null)
|
||||
{
|
||||
_textureDisplay.texture = initTex;
|
||||
}
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
if (_nameLabel != null)
|
||||
{
|
||||
_nameLabel.text = initialValue is Texture t && t != null ? t.name : "None";
|
||||
}
|
||||
#endif
|
||||
base.UpdateValueDisplay(value, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ namespace XericUI.ReflectionUIGenerator
|
||||
});
|
||||
}
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
string valueStr = value?.ToString();
|
||||
for (int i = 0; i < _toggles.Count && i < _optionLabels.Count; i++)
|
||||
@@ -65,11 +65,16 @@ namespace XericUI.ReflectionUIGenerator
|
||||
if (_optionLabels[i] == valueStr)
|
||||
{
|
||||
_toggles[i].SetIsOnWithoutNotify(true);
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
string initStr = initialValue?.ToString();
|
||||
|
||||
@@ -1,43 +1,29 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Toggle 值组件 — 处理 bool 类型的开关交互
|
||||
/// 支持样式表控制开关颜色
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDToggleValue")]
|
||||
public class XUIAvrgMDToggleValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("关联的 Toggle 组件(未设置则自动查找)")]
|
||||
[SerializeField, Tooltip("关联的 Toggle 组件(未设置则自动查找)")]
|
||||
private Toggle _toggle;
|
||||
|
||||
// ============ 样式表参数 ============
|
||||
private readonly StyleMember _styleOnColor = new() { StylePath = "xuiavrg/toggle/onColor" };
|
||||
private readonly StyleMember _styleOffColor = new() { StylePath = "xuiavrg/toggle/offColor" };
|
||||
|
||||
// ============ 生命周期 ============
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_toggle == null) _toggle = GetComponent<Toggle>();
|
||||
}
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
_styleOnColor.Register();
|
||||
_styleOffColor.Register();
|
||||
}
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
protected override void OnDisable()
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
base.OnDisable();
|
||||
_styleOnColor.Unregister();
|
||||
_styleOffColor.Unregister();
|
||||
if (_toggle != null)
|
||||
_toggle.onValueChanged.RemoveAllListeners();
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
@@ -60,36 +46,53 @@ namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
binding.WriteFromUI(val);
|
||||
scheduler?.MarkUIDirty(binding);
|
||||
ApplyToggleColor(val);
|
||||
});
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
bool isOn = value is bool b && b;
|
||||
_toggle.SetIsOnWithoutNotify(isOn);
|
||||
ApplyToggleColor(isOn);
|
||||
ApplyToggleColor(isOn, state);
|
||||
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
bool initialOn = initialValue is bool bv && bv;
|
||||
_toggle.SetIsOnWithoutNotify(initialOn);
|
||||
ApplyToggleColor(initialOn);
|
||||
|
||||
RefreshCommonNodes();
|
||||
ApplyToggleColor(initialOn, ValidationState.Normal);
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ 样式颜色 ============
|
||||
// ============ 验证状态回调 ============
|
||||
|
||||
private void ApplyToggleColor(bool isOn)
|
||||
protected override void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
var target = _toggle.targetGraphic;
|
||||
base.OnValidationStateChanged(state);
|
||||
|
||||
// Switch 验证状态也影响 targetGraphic 颜色
|
||||
if (_toggle != null)
|
||||
{
|
||||
ApplyToggleColor(_toggle.isOn, state);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyToggleColor(bool isOn, ValidationState state)
|
||||
{
|
||||
var target = _toggle?.targetGraphic;
|
||||
if (target == null) return;
|
||||
|
||||
var colorVal = isOn ? GetStyleValue(_styleOnColor) : GetStyleValue(_styleOffColor);
|
||||
if (colorVal != null)
|
||||
target.color = colorVal;
|
||||
if (state == ValidationState.Invalid)
|
||||
target.color = Color.red;
|
||||
else if (state == ValidationState.Warning)
|
||||
target.color = new Color(1f, 0.92f, 0.016f);
|
||||
else
|
||||
target.color = isOn ? Color.green : new Color(0.5f, 0.5f, 0.5f, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
@@ -9,11 +9,11 @@ namespace XericUI.ReflectionUIGenerator
|
||||
///
|
||||
/// 职责:
|
||||
/// 1. 提供 EntryTag/TagIndex 供 XuiFieldTempEntry 匹配
|
||||
/// 2. 持有绑定的元数据引用
|
||||
/// 3. 通过 virtual BindToAccessor() 让子类自行管理 UI 交互事件和显示更新
|
||||
/// 4. 初始化时广播元数据到所有子节点上的 IXUIAvrgMDValueStyle
|
||||
/// 2. 持有绑定的元数据引用,管理验证状态
|
||||
/// 3. 通过 virtual BindToAccessor() 让子类自行管理 UI 交互事件
|
||||
/// 4. 对象池生命周期:OnPoolGet(meta) 初始化、OnPoolRelease() 重置
|
||||
/// 5. 定义绑定槽(BindingSlots),供拆分属性子绑定匹配
|
||||
/// 6. 管理通用 UI 插槽(标签、警告、只读、单位、值显示)+ SuperStyleSheet 样式读取
|
||||
/// 6. 通过 UpdateValueDisplay() 合成标签文本(名称 + 只读 + 警告标记)
|
||||
/// </summary>
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/XUIAvrgMDValue")]
|
||||
public class XUIAvrgMDValue : MonoBehaviour
|
||||
@@ -47,29 +47,29 @@ namespace XericUI.ReflectionUIGenerator
|
||||
[Tooltip("拆分属性子绑定槽列表 — 用于 XuiavrgSplit 的子属性匹配")]
|
||||
private List<AvrgBindingSlot> _bindingSlots = new List<AvrgBindingSlot>();
|
||||
|
||||
// ============ 通用 UI 插槽 ============
|
||||
// ============ UI 文本插槽 ============
|
||||
|
||||
[Header("通用 UI 插槽")]
|
||||
[SerializeField, Tooltip("字段标签节点 — 显示字段名称 + 只读前缀。为 null 则不显示标签行")]
|
||||
protected GameObject fieldLabelNode;
|
||||
[Header("UI 文本插槽")]
|
||||
[SerializeField, Tooltip("字段标签文本 — 显示 \"名称 (只读) ⚠警告\",为 null 则不显示标签")]
|
||||
#if dUI_TextMeshPro
|
||||
protected TMPro.TMP_Text _fieldLabelText;
|
||||
#else
|
||||
protected UnityEngine.UI.Text _fieldLabelText;
|
||||
#endif
|
||||
|
||||
[SerializeField, Tooltip("警告样式节点 — 验证失败时 SetActive(true),颜色由样式表控制")]
|
||||
protected GameObject warningNode;
|
||||
[SerializeField, Tooltip("值显示文本 — 可为 null(Slider/Color/Texture 子类不依赖文本)")]
|
||||
#if dUI_TextMeshPro
|
||||
protected TMPro.TMP_Text _valueDisplayText;
|
||||
#else
|
||||
protected UnityEngine.UI.Text _valueDisplayText;
|
||||
#endif
|
||||
|
||||
[SerializeField, Tooltip("只读标记节点 — IsReadOnly 时 SetActive(true)")]
|
||||
protected GameObject readOnlyNode;
|
||||
|
||||
[SerializeField, Tooltip("单位文本节点 — 显示元数据的 UnitLabelName")]
|
||||
protected GameObject unitTextNode;
|
||||
|
||||
[SerializeField, Tooltip("值显示文本节点 — 可为 null(子类如 Slider/Color 不依赖文本)")]
|
||||
protected GameObject valueTextNode;
|
||||
|
||||
// ============ 样式表参数(路径硬编码,命名空间从 BoundMetaData.StyleNamespace 获取)============
|
||||
|
||||
private readonly StyleMember _styleStepValue = new() { StylePath = "xuiavrg/base/stepValue" };
|
||||
private readonly StyleMember _styleReadOnlyPrefix = new() { StylePath = "xuiavrg/base/readOnlyPrefix" };
|
||||
private readonly StyleMember _styleWarningColor = new() { StylePath = "xuiavrg/base/warningColor" };
|
||||
[SerializeField, Tooltip("单位文本 — 可为 null")]
|
||||
#if dUI_TextMeshPro
|
||||
protected TMPro.TMP_Text _unitText;
|
||||
#else
|
||||
protected UnityEngine.UI.Text _unitText;
|
||||
#endif
|
||||
|
||||
// ============ 公共属性 ============
|
||||
|
||||
@@ -82,26 +82,44 @@ namespace XericUI.ReflectionUIGenerator
|
||||
/// <summary>当前关联的 MVVM 绑定</summary>
|
||||
public AvrgMvvmBinding CurrentBinding { get; protected set; }
|
||||
|
||||
/// <summary>当前验证状态</summary>
|
||||
public ValidationState CurrentValidationState { get; protected set; } = ValidationState.Normal;
|
||||
|
||||
public XuiavrgFieldTag EntryTag => _entryTag;
|
||||
public int TagIndex => _tagIndex;
|
||||
|
||||
/// <summary>只读访问绑定槽列表</summary>
|
||||
public IReadOnlyList<AvrgBindingSlot> AvailableBindingSlots => _bindingSlots;
|
||||
|
||||
// ============ 生命周期 ============
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
protected virtual void OnEnable()
|
||||
/// <summary>
|
||||
/// 从对象池取出时调用 — 注入元数据并初始化子属性
|
||||
/// 子类可覆写以初始化专属组件参数(Slider.minValue/maxValue 等)
|
||||
/// </summary>
|
||||
public virtual void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
_styleStepValue.Register();
|
||||
_styleReadOnlyPrefix.Register();
|
||||
_styleWarningColor.Register();
|
||||
BoundMetaData = metaData;
|
||||
CurrentValidationState = ValidationState.Normal;
|
||||
|
||||
// 子类覆写点
|
||||
}
|
||||
|
||||
protected virtual void OnDisable()
|
||||
/// <summary>
|
||||
/// 回收到对象池时调用 — 重置所有状态到初始值
|
||||
/// 子类可覆写以清理专属组件事件
|
||||
/// </summary>
|
||||
public virtual void OnPoolRelease()
|
||||
{
|
||||
_styleStepValue.Unregister();
|
||||
_styleReadOnlyPrefix.Unregister();
|
||||
_styleWarningColor.Unregister();
|
||||
CurrentAccessor = null;
|
||||
CurrentBinding = null;
|
||||
BoundMetaData = null;
|
||||
CurrentValidationState = ValidationState.Normal;
|
||||
|
||||
// 重置 UI 文本
|
||||
if (_fieldLabelText != null) _fieldLabelText.text = "";
|
||||
if (_valueDisplayText != null) _valueDisplayText.text = "";
|
||||
if (_unitText != null) _unitText.text = "";
|
||||
}
|
||||
|
||||
// ============ 核心绑定接口 ============
|
||||
@@ -119,44 +137,107 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (binding == null || accessor == null) return;
|
||||
|
||||
// 默认:数据 → UI 单向同步
|
||||
binding.OnUIDataChanged += value =>
|
||||
// 数据 → UI(带验证状态)
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
UpdateDisplayValue(value);
|
||||
UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
// 验证状态变更
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateDisplayValue(initialValue);
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// 刷新通用插槽
|
||||
RefreshCommonNodes();
|
||||
// ============ UI 更新 ============
|
||||
|
||||
/// <summary>
|
||||
/// 根据值和验证状态更新 UI 显示
|
||||
/// 子类可覆写以处理专属组件更新(Slider.value, Image.color 等)
|
||||
/// 基类负责合成标签文本、值文本颜色、单位文本
|
||||
/// </summary>
|
||||
protected virtual void UpdateValueDisplay(object value, ValidationState state)
|
||||
{
|
||||
// 1. 字段标签合成
|
||||
if (_fieldLabelText != null)
|
||||
{
|
||||
string label = BuildLabel(state);
|
||||
_fieldLabelText.text = label;
|
||||
}
|
||||
|
||||
// 2. 值显示
|
||||
if (_valueDisplayText != null)
|
||||
{
|
||||
string display = CurrentAccessor?.FormatDisplay(value) ?? value?.ToString() ?? "";
|
||||
_valueDisplayText.text = display;
|
||||
|
||||
_valueDisplayText.color = state == ValidationState.Invalid
|
||||
? Color.red
|
||||
: state == ValidationState.Warning
|
||||
? new Color(1f, 0.92f, 0.016f) // yellow
|
||||
: Color.white;
|
||||
}
|
||||
|
||||
// 3. 单位文本
|
||||
if (_unitText != null)
|
||||
_unitText.text = BoundMetaData?.UnitLabelName ?? "";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新 UI 显示值(基类默认仅查找 Text/TMP_Text 设置文本)
|
||||
/// 子类可覆写以实现专属更新逻辑(如 Slider.value, Image.color 等)
|
||||
/// 合成标签文本:名称 + (只读) + 警告标记
|
||||
/// </summary>
|
||||
protected virtual void UpdateDisplayValue(object value)
|
||||
protected string BuildLabel(ValidationState state)
|
||||
{
|
||||
if (value == null) return;
|
||||
var meta = BoundMetaData;
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(meta?.LabelName ?? meta?.MemberName ?? "");
|
||||
|
||||
#if dUI_TextMeshPro
|
||||
var tmpText = GetComponent<TMPro.TMP_Text>();
|
||||
if (tmpText != null)
|
||||
{
|
||||
tmpText.text = CurrentAccessor?.FormatDisplay(value) ?? value.ToString();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
// 必填标记
|
||||
if (meta != null && meta.Required)
|
||||
sb.Append(" *");
|
||||
|
||||
var uiText = GetComponent<UnityEngine.UI.Text>();
|
||||
if (uiText != null)
|
||||
// 只读标记
|
||||
if (CurrentAccessor?.IsReadOnly == true)
|
||||
sb.Append(" (只读)");
|
||||
|
||||
// 警告/无效标记(用富文本颜色)
|
||||
if (state == ValidationState.Warning)
|
||||
sb.Append(" <color=yellow>⚠警告</color>");
|
||||
else if (state == ValidationState.Invalid)
|
||||
sb.Append(" <color=red>⚠无效值</color>");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
// ============ 验证状态回调 ============
|
||||
|
||||
/// <summary>
|
||||
/// 验证状态变更回调 — 子类可覆写以实现专属样式切换
|
||||
/// 如 Slider 填充色变黄/红、InputField 文字变色等
|
||||
/// </summary>
|
||||
protected virtual void OnValidationStateChanged(ValidationState state)
|
||||
{
|
||||
CurrentValidationState = state;
|
||||
|
||||
// 刷新标签文本(警告/无效标记在标签上)
|
||||
if (_fieldLabelText != null)
|
||||
_fieldLabelText.text = BuildLabel(state);
|
||||
|
||||
// 值文本颜色
|
||||
if (_valueDisplayText != null)
|
||||
{
|
||||
uiText.text = CurrentAccessor?.FormatDisplay(value) ?? value.ToString();
|
||||
_valueDisplayText.color = state == ValidationState.Invalid
|
||||
? Color.red
|
||||
: state == ValidationState.Warning
|
||||
? new Color(1f, 0.92f, 0.016f)
|
||||
: Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 元数据广播 ============
|
||||
|
||||
/// <summary>
|
||||
/// 由绑定系统调用,传递元数据并广播到 IXUIAvrgMDValueStyle 子组件
|
||||
/// </summary>
|
||||
@@ -166,7 +247,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
if (metaData == null) return;
|
||||
|
||||
// 广播元数据到子节点样式组件
|
||||
var styles = GetComponentsInChildren<IXUIAvrgMDValueStyle>(true);
|
||||
foreach (var style in styles)
|
||||
{
|
||||
@@ -176,60 +256,5 @@ namespace XericUI.ReflectionUIGenerator
|
||||
style.ApplyMetaData(metaData);
|
||||
}
|
||||
}
|
||||
|
||||
// ============ 样式读取 ============
|
||||
|
||||
/// <summary>
|
||||
/// 从样式表获取值(自动使用 BoundMetaData.StyleNamespace)
|
||||
/// </summary>
|
||||
protected StyleValue GetStyleValue(StyleMember member)
|
||||
{
|
||||
if (member == null) return null;
|
||||
string ns = BoundMetaData?.StyleNamespace ?? "default";
|
||||
return StyleManager.GetValue(ns, member.StylePath);
|
||||
}
|
||||
|
||||
// ============ 通用 UI 插槽刷新 ============
|
||||
|
||||
/// <summary>刷新所有通用 UI 插槽(标签、单位、只读、警告标记)</summary>
|
||||
protected void RefreshCommonNodes()
|
||||
{
|
||||
var meta = BoundMetaData;
|
||||
if (meta == null) return;
|
||||
|
||||
// 1. 字段标签节点
|
||||
if (fieldLabelNode != null)
|
||||
{
|
||||
string prefix = "";
|
||||
if (CurrentAccessor?.IsReadOnly == true)
|
||||
{
|
||||
var pv = GetStyleValue(_styleReadOnlyPrefix);
|
||||
prefix = pv?.GetValueAsString() ?? "[只读] ";
|
||||
}
|
||||
SetNodeText(fieldLabelNode, prefix + (meta.LabelName ?? meta.MemberName));
|
||||
}
|
||||
|
||||
// 2. 单位文本节点
|
||||
if (unitTextNode != null)
|
||||
SetNodeText(unitTextNode, meta.UnitLabelName ?? "");
|
||||
|
||||
// 3. 只读标记节点
|
||||
if (readOnlyNode != null)
|
||||
readOnlyNode.SetActive(CurrentAccessor?.IsReadOnly == true);
|
||||
|
||||
// 4. warningNode 由外部验证系统触发,此方法不直接控制
|
||||
}
|
||||
|
||||
/// <summary>在节点及其子节点上查找 Text / TMP_Text 并设置文本</summary>
|
||||
protected static void SetNodeText(GameObject node, string text)
|
||||
{
|
||||
if (node == null) return;
|
||||
#if dUI_TextMeshPro
|
||||
var t = node.GetComponentInChildren<TMPro.TMP_Text>(true);
|
||||
if (t != null) { t.text = text; return; }
|
||||
#endif
|
||||
var ut = node.GetComponentInChildren<UnityEngine.UI.Text>(true);
|
||||
if (ut != null) ut.text = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
|
||||
namespace XericUI.ReflectionUIGenerator
|
||||
{
|
||||
@@ -11,37 +10,39 @@ namespace XericUI.ReflectionUIGenerator
|
||||
[AddComponentMenu("Xeric Library/UI Action Vessel/Values/XUIAvrgMDVectorValue")]
|
||||
public class XUIAvrgMDVectorValue : XUIAvrgMDValue
|
||||
{
|
||||
[SerializeField]
|
||||
[Tooltip("X 分量 InputField(可选,也可通过 BindingSlots 的子节点绑定)")]
|
||||
[SerializeField, Tooltip("X 分量 InputField(可选,也可通过 BindingSlots 的子节点绑定)")]
|
||||
private InputField _inputX;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Y 分量 InputField")]
|
||||
[SerializeField, Tooltip("Y 分量 InputField")]
|
||||
private InputField _inputY;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("Z 分量 InputField(Vector3/Vector4 时使用)")]
|
||||
[SerializeField, Tooltip("Z 分量 InputField(Vector3/Vector4 时使用)")]
|
||||
private InputField _inputZ;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("W 分量 InputField(Vector4 时使用)")]
|
||||
[SerializeField, Tooltip("W 分量 InputField(Vector4 时使用)")]
|
||||
private InputField _inputW;
|
||||
|
||||
// ============ 样式表参数 ============
|
||||
private readonly StyleMember _styleLayoutDirection = new() { StylePath = "xuiavrg/vector/layoutDirection" };
|
||||
// ============ 对象池生命周期 ============
|
||||
|
||||
// ============ 生命周期 ============
|
||||
|
||||
protected override void OnEnable()
|
||||
public override void OnPoolGet(AvrgMemberMetaData metaData)
|
||||
{
|
||||
base.OnEnable();
|
||||
_styleLayoutDirection.Register();
|
||||
base.OnPoolGet(metaData);
|
||||
|
||||
int componentCount = GetComponentCount(metaData?.ValueType);
|
||||
SetInputActive(_inputX, 0, componentCount);
|
||||
SetInputActive(_inputY, 1, componentCount);
|
||||
SetInputActive(_inputZ, 2, componentCount);
|
||||
SetInputActive(_inputW, 3, componentCount);
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
public override void OnPoolRelease()
|
||||
{
|
||||
base.OnDisable();
|
||||
_styleLayoutDirection.Unregister();
|
||||
if (_inputX != null) _inputX.onEndEdit.RemoveAllListeners();
|
||||
if (_inputY != null) _inputY.onEndEdit.RemoveAllListeners();
|
||||
if (_inputZ != null) _inputZ.onEndEdit.RemoveAllListeners();
|
||||
if (_inputW != null) _inputW.onEndEdit.RemoveAllListeners();
|
||||
|
||||
base.OnPoolRelease();
|
||||
}
|
||||
|
||||
// ============ 核心绑定 ============
|
||||
@@ -56,18 +57,6 @@ namespace XericUI.ReflectionUIGenerator
|
||||
|
||||
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);
|
||||
|
||||
// 样式表控制排列方向
|
||||
var dirVal = GetStyleValue(_styleLayoutDirection);
|
||||
string dir = dirVal?.GetValueAsString() ?? "Horizontal";
|
||||
ApplyLayoutDirection(dir);
|
||||
|
||||
// 为每个输入注册事件
|
||||
BindInputField(_inputX, 0, accessor, binding, scheduler);
|
||||
BindInputField(_inputY, 1, accessor, binding, scheduler);
|
||||
@@ -75,41 +64,18 @@ namespace XericUI.ReflectionUIGenerator
|
||||
BindInputField(_inputW, 3, accessor, binding, scheduler);
|
||||
|
||||
// 数据 → UI
|
||||
binding.OnUIDataChanged += value =>
|
||||
binding.OnUIDataChanged += (value, state) =>
|
||||
{
|
||||
UpdateAllInputs(value);
|
||||
base.UpdateValueDisplay(value, state);
|
||||
};
|
||||
|
||||
binding.OnValidationStateChanged += OnValidationStateChanged;
|
||||
|
||||
// 首次同步
|
||||
var initialValue = accessor.GetValue();
|
||||
UpdateAllInputs(initialValue);
|
||||
|
||||
RefreshCommonNodes();
|
||||
}
|
||||
|
||||
// ============ 布局 ============
|
||||
|
||||
private void ApplyLayoutDirection(string dir)
|
||||
{
|
||||
var layout = GetComponent<HorizontalOrVerticalLayoutGroup>();
|
||||
if (layout == null) return;
|
||||
|
||||
if (dir == "Vertical")
|
||||
{
|
||||
if (layout is HorizontalLayoutGroup)
|
||||
{
|
||||
Destroy(layout);
|
||||
gameObject.AddComponent<VerticalLayoutGroup>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (layout is VerticalLayoutGroup)
|
||||
{
|
||||
Destroy(layout);
|
||||
gameObject.AddComponent<HorizontalLayoutGroup>();
|
||||
}
|
||||
}
|
||||
UpdateValueDisplay(initialValue, ValidationState.Normal);
|
||||
}
|
||||
|
||||
// ============ 输入绑定 ============
|
||||
|
||||
Reference in New Issue
Block a user