diff --git a/Editor/InterfaceAttributeReflectionGenerator/XuiavrgPropertyGroupScriptableObjectEditor.cs b/Editor/InterfaceAttributeReflectionGenerator/XuiavrgPropertyGroupScriptableObjectEditor.cs deleted file mode 100644 index c0cd6dd..0000000 --- a/Editor/InterfaceAttributeReflectionGenerator/XuiavrgPropertyGroupScriptableObjectEditor.cs +++ /dev/null @@ -1,257 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using UnityEditor; -using UnityEngine; -using XericUI.ReflectionUIGenerator; - -namespace XericUIEditor.ReflectionUIGenerator -{ - [CustomEditor(typeof(XuiavrgPropertyGroupScriptableObject))] - internal class XuiavrgPropertyGroupScriptableObjectEditor : Editor - { - SerializedProperty _themeNameProp; - SerializedProperty _uiNameProp; - SerializedProperty _valueTypeNameProp; - SerializedProperty _prefabProp; - SerializedProperty _supportedTagsProp; - - private XuiavrgPropertyGroupScriptableObject _target; - private bool _showDetectedComponents = true; - - private void OnEnable() - { - _themeNameProp = serializedObject.FindProperty("themeName"); - _uiNameProp = serializedObject.FindProperty("uiName"); - _valueTypeNameProp = serializedObject.FindProperty("valueTypeName"); - _prefabProp = serializedObject.FindProperty("prefab"); - _supportedTagsProp = serializedObject.FindProperty("supportedTags"); - _target = target as XuiavrgPropertyGroupScriptableObject; - } - - public override void OnInspectorGUI() - { - serializedObject.Update(); - - // 标题 - EditorGUILayout.LabelField("UI Property Group", EditorStyles.boldLabel); - EditorGUILayout.HelpBox( - "将一个 C# 数据类型绑定到一个 UI 预制体,并归属到某个主题下。\n" + - "Manager 根据当前主题 + 字段 Tag 自动匹配。", - MessageType.Info); - EditorGUILayout.Space(); - - // 基本属性 - EditorGUILayout.PropertyField(_themeNameProp, new GUIContent("主题名称 (Theme)")); - EditorGUILayout.PropertyField(_uiNameProp, new GUIContent("UI 名称 (uiName)")); - - // 绑定类型 — 带快捷选择 + 自动纠错 - DrawValueTypeField(); - - EditorGUILayout.Space(); - - // 预制体 + 自动扫描 - EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(_prefabProp, new GUIContent("UI 预制体 (prefab)")); - if (EditorGUI.EndChangeCheck()) - { - serializedObject.ApplyModifiedProperties(); - if (_target.prefab != null) - { - _target.ScanPrefabComponents(); - EditorUtility.SetDirty(_target); - } - } - - EditorGUILayout.Space(); - - // 支持的功能标记 - EditorGUILayout.PropertyField(_supportedTagsProp, new GUIContent("支持的功能标记 (supportedTags)"), true); - - EditorGUILayout.Space(); - - // 组件审查区域 - _showDetectedComponents = EditorGUILayout.Foldout(_showDetectedComponents, "预制体组件审查", true); - if (_showDetectedComponents) - { - EditorGUI.indentLevel++; - - if (_target.prefab == null) - { - EditorGUILayout.HelpBox("请先拖入 UI 预制体,系统将自动扫描其中的 IXuiavrgFieldTagProvider 组件。", MessageType.Warning); - } - else if (_target.detectedComponents == null || _target.detectedComponents.Count == 0) - { - if (GUILayout.Button("扫描预制体组件")) - { - _target.ScanPrefabComponents(); - EditorUtility.SetDirty(_target); - } - if (_target.detectedComponents != null && _target.detectedComponents.Count == 0) - EditorGUILayout.HelpBox("未检测到 IXuiavrgFieldTagProvider 组件。请确保预制体上已挂载 XericUIrAttribute* 组件。", MessageType.Info); - } - else - { - EditorGUILayout.LabelField($"检测到 {_target.detectedComponents.Count} 个标记组件:", EditorStyles.boldLabel); - - // 按 Tag 分组显示 - var groups = _target.detectedComponents - .Where(c => c != null) - .GroupBy(c => - { - if (c is IXuiavrgFieldTagProvider tp) - return $"{tp.EntryTag}" + (tp.TagIndex > 0 ? $" [{tp.TagIndex}]" : ""); - return c.GetType().Name; - }); - - foreach (var group in groups) - { - EditorGUILayout.BeginVertical(EditorStyles.helpBox); - EditorGUILayout.LabelField($"Tag: {group.Key}", EditorStyles.miniBoldLabel); - foreach (var comp in group) - { - using (new EditorGUI.DisabledScope(true)) - { - EditorGUILayout.ObjectField(comp, typeof(Component), true); - } - } - EditorGUILayout.EndVertical(); - } - } - - EditorGUI.indentLevel--; - } - - EditorGUILayout.Space(); - - // 快捷操作按钮 - EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("重新扫描")) - { - _target.ScanPrefabComponents(); - EditorUtility.SetDirty(_target); - } - if (GUILayout.Button("同步 Tags")) - { - _target.ScanPrefabComponents(); - EditorUtility.SetDirty(_target); - } - EditorGUILayout.EndHorizontal(); - - serializedObject.ApplyModifiedProperties(); - } - - private static List _cachedTypeNames; - private static List _cachedFullNames; - - /// - /// 从 AppDomain 动态收集所有可用类型名(Editor 侧缓存) - /// - private static void EnsureEditorTypeCache() - { - if (_cachedTypeNames != null) return; - - _cachedTypeNames = new List(); - _cachedFullNames = new List(); - var seen = new HashSet(); - - foreach (var asm in System.AppDomain.CurrentDomain.GetAssemblies()) - { - try - { - foreach (var type in asm.GetTypes()) - { - var full = type.FullName; - if (string.IsNullOrEmpty(full) || full.Contains("`") || full.Contains("<")) continue; - if (!seen.Add(full)) continue; - - _cachedTypeNames.Add(type.Name); - _cachedFullNames.Add(full); - } - } - catch { } - } - } - - private void DrawValueTypeField() - { - EditorGUILayout.LabelField("绑定类型 (valueType)", EditorStyles.label); - - // 输入行 - EditorGUILayout.BeginHorizontal(); - var currentValue = _valueTypeNameProp.stringValue; - var newValue = EditorGUILayout.TextField(currentValue); - - if (newValue != currentValue) - { - var corrected = XuiavrgPropertyGroupScriptableObject.NormalizeTypeName(newValue); - _valueTypeNameProp.stringValue = corrected; - currentValue = corrected; - } - - // "搜索类型" 按钮 - if (GUILayout.Button("...", GUILayout.Width(30))) - { - var menu = new GenericMenu(); - EnsureEditorTypeCache(); - - // 如果有输入,用 LevenshteinDistance 排序,最近的排前面 - var ranked = new List<(int dist, string name, string full)>(); - for (int i = 0; i < _cachedTypeNames.Count; i++) - { - XericLibrary.Runtime.MacroLibrary.MacroMath.LevenshteinDistance( - new[] { _cachedTypeNames[i] }, currentValue, out int dist, out _); - ranked.Add((dist, _cachedTypeNames[i], _cachedFullNames[i])); - } - - ranked.Sort((a, b) => a.dist.CompareTo(b.dist)); - - int shown = 0; - // 完全匹配的排最前面 - foreach (var (dist, name, full) in ranked) - { - if (dist != 0 || shown >= 30) break; - menu.AddItem(new GUIContent($"{full} ({name})"), false, () => - { - _valueTypeNameProp.stringValue = full; - serializedObject.ApplyModifiedProperties(); - }); - shown++; - } - - // 用分隔线隔开,然后显示近匹配 - if (shown > 0 && ranked.Count > shown) - menu.AddSeparator(""); - - int nearTotal = 0; - foreach (var (dist, name, full) in ranked) - { - if (dist == 0) continue; - if (nearTotal >= 20) break; - menu.AddItem(new GUIContent($"{full} ({name})"), false, () => - { - _valueTypeNameProp.stringValue = full; - serializedObject.ApplyModifiedProperties(); - }); - nearTotal++; - } - - menu.ShowAsContext(); - } - - EditorGUILayout.EndHorizontal(); - - // 纠错提示 - var normalized = XuiavrgPropertyGroupScriptableObject.NormalizeTypeName(currentValue); - if (normalized != currentValue && !string.IsNullOrEmpty(currentValue)) - { - EditorGUILayout.BeginHorizontal(); - EditorGUILayout.HelpBox($"识别为: {normalized}", MessageType.Info); - if (GUILayout.Button("纠正", GUILayout.Width(60), GUILayout.Height(38))) - { - _valueTypeNameProp.stringValue = normalized; - } - EditorGUILayout.EndHorizontal(); - } - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeButtonEditor.cs b/Editor/OverrideUI/XericUIrAttributeButtonEditor.cs deleted file mode 100644 index 9a86472..0000000 --- a/Editor/OverrideUI/XericUIrAttributeButtonEditor.cs +++ /dev/null @@ -1,16 +0,0 @@ -using UnityEditor; -using XericUI.OverrideUI; - -namespace XericUIEditor.OverrideUI -{ - [CustomEditor(typeof(XericUIrAttributeButton))] - [CanEditMultipleObjects] - internal class XericUIrAttributeButtonEditor : XericButtonEditor - { - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - XericUIrAttributeEditorHelper.DrawAttributeTagFields(serializedObject); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeDropdownEditor.cs b/Editor/OverrideUI/XericUIrAttributeDropdownEditor.cs deleted file mode 100644 index 40a4376..0000000 --- a/Editor/OverrideUI/XericUIrAttributeDropdownEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -using UnityEditor; -using UnityEditor.UI; -using XericUI.OverrideUI; - -namespace XericUIEditor.OverrideUI -{ - [CustomEditor(typeof(XericUIrAttributeDropdown))] - [CanEditMultipleObjects] - internal class XericUIrAttributeDropdownEditor : DropdownEditor - { - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - XericUIrAttributeEditorHelper.DrawAttributeTagFields(serializedObject); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeEditorHelper.cs b/Editor/OverrideUI/XericUIrAttributeEditorHelper.cs deleted file mode 100644 index 10cacdd..0000000 --- a/Editor/OverrideUI/XericUIrAttributeEditorHelper.cs +++ /dev/null @@ -1,33 +0,0 @@ -using UnityEditor; - -namespace XericUIEditor.OverrideUI -{ - /// - /// XericUIrAttribute* 组件的 Editor 通用辅助方法 - /// - internal static class XericUIrAttributeEditorHelper - { - /// - /// 绘制 _entryTag 和 _tagIndex 字段 - /// 在 OnInspectorGUI 末尾调用 - /// - public static void DrawAttributeTagFields(SerializedObject serializedObject) - { - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Reflection UI Attribute Tag", EditorStyles.boldLabel); - - serializedObject.Update(); - - var entryTagProp = serializedObject.FindProperty("_entryTag"); - var tagIndexProp = serializedObject.FindProperty("_tagIndex"); - - if (entryTagProp != null) - EditorGUILayout.PropertyField(entryTagProp); - - if (tagIndexProp != null) - EditorGUILayout.PropertyField(tagIndexProp); - - serializedObject.ApplyModifiedProperties(); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeInputFieldEditor.cs b/Editor/OverrideUI/XericUIrAttributeInputFieldEditor.cs deleted file mode 100644 index a3a8e4e..0000000 --- a/Editor/OverrideUI/XericUIrAttributeInputFieldEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -using UnityEditor; -using UnityEditor.UI; -using XericUI.OverrideUI; - -namespace XericUIEditor.OverrideUI -{ - [CustomEditor(typeof(XericUIrAttributeInputField))] - [CanEditMultipleObjects] - internal class XericUIrAttributeInputFieldEditor : InputFieldEditor - { - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - XericUIrAttributeEditorHelper.DrawAttributeTagFields(serializedObject); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeInputFieldEditor.cs.meta b/Editor/OverrideUI/XericUIrAttributeInputFieldEditor.cs.meta deleted file mode 100644 index b3b86f2..0000000 --- a/Editor/OverrideUI/XericUIrAttributeInputFieldEditor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b1ce54b154544da4c8cca4b4b8063e88 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/OverrideUI/XericUIrAttributeSliderEditor.cs b/Editor/OverrideUI/XericUIrAttributeSliderEditor.cs deleted file mode 100644 index 1470f9e..0000000 --- a/Editor/OverrideUI/XericUIrAttributeSliderEditor.cs +++ /dev/null @@ -1,17 +0,0 @@ -using UnityEditor; -using UnityEditor.UI; -using XericUI.OverrideUI; - -namespace XericUIEditor.OverrideUI -{ - [CustomEditor(typeof(XericUIrAttributeSlider))] - [CanEditMultipleObjects] - internal class XericUIrAttributeSliderEditor : SliderEditor - { - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - XericUIrAttributeEditorHelper.DrawAttributeTagFields(serializedObject); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeSliderEditor.cs.meta b/Editor/OverrideUI/XericUIrAttributeSliderEditor.cs.meta deleted file mode 100644 index f0ffaa9..0000000 --- a/Editor/OverrideUI/XericUIrAttributeSliderEditor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 1d0a860b1f96faa44a2536f607030df9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/OverrideUI/XericUIrAttributeTextEditor.cs b/Editor/OverrideUI/XericUIrAttributeTextEditor.cs deleted file mode 100644 index 70e934a..0000000 --- a/Editor/OverrideUI/XericUIrAttributeTextEditor.cs +++ /dev/null @@ -1,30 +0,0 @@ -using UnityEditor; -using UnityEditor.UI; -using XericUI.OverrideUI; - -namespace XericUIEditor.OverrideUI -{ - [CustomEditor(typeof(XericUIrAttributeText))] - [CanEditMultipleObjects] - internal class XericUIrAttributeTextEditor : ImageEditor - { - SerializedProperty _targetTextProp; - - protected override void OnEnable() - { - base.OnEnable(); - _targetTextProp = serializedObject.FindProperty("_targetText"); - } - - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - - EditorGUILayout.Space(); - if (_targetTextProp != null) - EditorGUILayout.PropertyField(_targetTextProp); - - XericUIrAttributeEditorHelper.DrawAttributeTagFields(serializedObject); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeTextEditor.cs.meta b/Editor/OverrideUI/XericUIrAttributeTextEditor.cs.meta deleted file mode 100644 index 5583e95..0000000 --- a/Editor/OverrideUI/XericUIrAttributeTextEditor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 0a589d7a31bab924dadbb7b510489e86 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/OverrideUI/XericUIrAttributeTextMeshProInputFieldEditor.cs b/Editor/OverrideUI/XericUIrAttributeTextMeshProInputFieldEditor.cs deleted file mode 100644 index e4ae103..0000000 --- a/Editor/OverrideUI/XericUIrAttributeTextMeshProInputFieldEditor.cs +++ /dev/null @@ -1,54 +0,0 @@ -using UnityEditor; -using UnityEngine; -using XericUI.OverrideUI; - -namespace XericUIEditor.OverrideUI -{ - [CustomEditor(typeof(XericUIrAttributeTextMeshProInputField))] - [CanEditMultipleObjects] - internal class XericUIrAttributeTextMeshProInputFieldEditor : Editor - { -#if dUI_TextMeshPro - SerializedProperty _targetInputFieldProp; -#endif - SerializedProperty _entryTagProp; - SerializedProperty _tagIndexProp; - - private void OnEnable() - { -#if dUI_TextMeshPro - _targetInputFieldProp = serializedObject.FindProperty("_targetInputField"); -#endif - _entryTagProp = serializedObject.FindProperty("_entryTag"); - _tagIndexProp = serializedObject.FindProperty("_tagIndex"); - } - - public override void OnInspectorGUI() - { - serializedObject.Update(); - - EditorGUILayout.LabelField("XericUIrAttributeTextMeshProInputField", EditorStyles.boldLabel); - EditorGUILayout.Space(); - - EditorGUILayout.HelpBox( - "此组件为 TMP_InputField 的标记附加器,需挂在与 TMP_InputField 同一个 GameObject 上。\n" + - "_entryTag 和 _tagIndex 用于反射界面生成器自动绑定。", - MessageType.Info); - -#if dUI_TextMeshPro - EditorGUILayout.PropertyField(_targetInputFieldProp); -#endif - - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Reflection UI Attribute Tag", EditorStyles.boldLabel); - - if (_entryTagProp != null) - EditorGUILayout.PropertyField(_entryTagProp); - - if (_tagIndexProp != null) - EditorGUILayout.PropertyField(_tagIndexProp); - - serializedObject.ApplyModifiedProperties(); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeTextMeshProInputFieldEditor.cs.meta b/Editor/OverrideUI/XericUIrAttributeTextMeshProInputFieldEditor.cs.meta deleted file mode 100644 index 20bf62b..0000000 --- a/Editor/OverrideUI/XericUIrAttributeTextMeshProInputFieldEditor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ed1041e7533d7024a9e5e62b888082ba -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/OverrideUI/XericUIrAttributeTextMeshProTextEditor.cs b/Editor/OverrideUI/XericUIrAttributeTextMeshProTextEditor.cs deleted file mode 100644 index 78fa9d2..0000000 --- a/Editor/OverrideUI/XericUIrAttributeTextMeshProTextEditor.cs +++ /dev/null @@ -1,57 +0,0 @@ -using UnityEditor; -using UnityEngine; -using XericUI.OverrideUI; - -namespace XericUIEditor.OverrideUI -{ - [CustomEditor(typeof(XericUIrAttributeTextMeshProText))] - [CanEditMultipleObjects] - internal class XericUIrAttributeTextMeshProTextEditor : Editor - { -#if dUI_TextMeshPro - SerializedProperty _targetTextProp; -#endif - SerializedProperty _entryTagProp; - SerializedProperty _tagIndexProp; - - private void OnEnable() - { -#if dUI_TextMeshPro - _targetTextProp = serializedObject.FindProperty("_targetText"); -#endif - _entryTagProp = serializedObject.FindProperty("_entryTag"); - _tagIndexProp = serializedObject.FindProperty("_tagIndex"); - } - - public override void OnInspectorGUI() - { - serializedObject.Update(); - - var targetComp = target as XericUIrAttributeTextMeshProText; - - EditorGUILayout.LabelField("XericUIrAttributeTextMeshProText", EditorStyles.boldLabel); - EditorGUILayout.Space(); - - // 提示信息 - EditorGUILayout.HelpBox( - "此组件为 TMP_Text 的标记附加器,需挂在与 TMP_Text 同一个 GameObject 上。\n" + - "_entryTag 和 _tagIndex 用于反射界面生成器自动绑定。", - MessageType.Info); - -#if dUI_TextMeshPro - EditorGUILayout.PropertyField(_targetTextProp); -#endif - - EditorGUILayout.Space(); - EditorGUILayout.LabelField("Reflection UI Attribute Tag", EditorStyles.boldLabel); - - if (_entryTagProp != null) - EditorGUILayout.PropertyField(_entryTagProp); - - if (_tagIndexProp != null) - EditorGUILayout.PropertyField(_tagIndexProp); - - serializedObject.ApplyModifiedProperties(); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeTextMeshProTextEditor.cs.meta b/Editor/OverrideUI/XericUIrAttributeTextMeshProTextEditor.cs.meta deleted file mode 100644 index c22f257..0000000 --- a/Editor/OverrideUI/XericUIrAttributeTextMeshProTextEditor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8226bbbc53d035e43b6811dc1f346f60 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Editor/OverrideUI/XericUIrAttributeToggleEditor.cs b/Editor/OverrideUI/XericUIrAttributeToggleEditor.cs deleted file mode 100644 index 21907e5..0000000 --- a/Editor/OverrideUI/XericUIrAttributeToggleEditor.cs +++ /dev/null @@ -1,16 +0,0 @@ -using UnityEditor; -using XericUI; -using XericUI.OverrideUI; - -namespace XericUIEditor.OverrideUI -{ - [CustomEditor(typeof(XericUIrAttributeToggle))] - internal class XericUIrAttributeToggleEditor : XericToggleEditor - { - public override void OnInspectorGUI() - { - base.OnInspectorGUI(); - XericUIrAttributeEditorHelper.DrawAttributeTagFields(serializedObject); - } - } -} diff --git a/Editor/OverrideUI/XericUIrAttributeToggleEditor.cs.meta b/Editor/OverrideUI/XericUIrAttributeToggleEditor.cs.meta deleted file mode 100644 index ea01712..0000000 --- a/Editor/OverrideUI/XericUIrAttributeToggleEditor.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 8ae7bafc6acd1b345b62d3467fc23cbb -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/Core/Base/XericUIBehaviour.cs b/Runtime/Core/Base/XericUIBehaviour.cs index f352871..aff53d4 100644 --- a/Runtime/Core/Base/XericUIBehaviour.cs +++ b/Runtime/Core/Base/XericUIBehaviour.cs @@ -232,6 +232,33 @@ namespace XericUI.Core.Base rt.sizeDelta = sizeDelta; } + /// + /// 将窗口定位到目标 RectTransform 的指定锚点位置 + /// + /// 目标 RectTransform + /// 锚点偏移,(0,0)=左下, (1,1)=右上 + public void SetAnchorPosition(RectTransform target, Vector2 anchor) + { + var rt = rectTransform; + if (rt == null || target == null) return; + var parentRT = rt.parent as RectTransform; + if (parentRT == null) return; + + Vector3[] corners = new Vector3[4]; + target.GetWorldCorners(corners); + // corners[0]=左下, corners[1]=左上, corners[2]=右上, corners[3]=右下 + Vector3 worldPos = Vector3.Lerp( + Vector3.Lerp(corners[0], corners[1], anchor.y), + Vector3.Lerp(corners[3], corners[2], anchor.y), + anchor.x); + + if (RectTransformUtility.ScreenPointToLocalPointInRectangle( + parentRT, worldPos, null, out Vector2 localPoint)) + { + rt.anchoredPosition = localPoint; + } + } + #endregion #region 显影 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgButton-Default.asset b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgButton-Default.asset deleted file mode 100644 index 95e7afb..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgButton-Default.asset +++ /dev/null @@ -1,19 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3} - m_Name: XuiavrgButton-Default - m_EditorClassIdentifier: - themeName: Default - uiName: Default - valueTypeName: System.Single - prefab: {fileID: 6894309230704738580, guid: a58d335e7df1a994d9f1c0fd58aff68f, type: 3} - supportedTags: 010000001000000002000000 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgButton-Default.asset.meta b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgButton-Default.asset.meta deleted file mode 100644 index e3fc4dd..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgButton-Default.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 2f4eb311896b80d4b9ced716a740dfad -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgDropdown-Default.asset b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgDropdown-Default.asset deleted file mode 100644 index 46fd6cc..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgDropdown-Default.asset +++ /dev/null @@ -1,19 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3} - m_Name: XuiavrgDropdown-Default - m_EditorClassIdentifier: - themeName: Default - uiName: Default - valueTypeName: System.Enum - prefab: {fileID: 1100600009542095382, guid: 4f25b16125eac234e820506fff28a4b0, type: 3} - supportedTags: 01000000 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgDropdown-Default.asset.meta b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgDropdown-Default.asset.meta deleted file mode 100644 index 8af2386..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgDropdown-Default.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 8a13481739bcb4d468fced5fde896f0f -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFieldInput-Default.asset b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFieldInput-Default.asset deleted file mode 100644 index c5cf842..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFieldInput-Default.asset +++ /dev/null @@ -1,19 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3} - m_Name: XuiavrgFieldInput-Default - m_EditorClassIdentifier: - themeName: Default - uiName: Default - valueTypeName: System.String - prefab: {fileID: 8186464640817935025, guid: 1715f06fee075eb45b68d43575e9aa88, type: 3} - supportedTags: 0100000080000000 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFieldInput-Default.asset.meta b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFieldInput-Default.asset.meta deleted file mode 100644 index e672711..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFieldInput-Default.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 4aa3fe7aa30f1b240a4486c12152cd4d -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFloatSlider-Default.asset b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFloatSlider-Default.asset deleted file mode 100644 index 92f6c04..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFloatSlider-Default.asset +++ /dev/null @@ -1,19 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3} - m_Name: XuiavrgFloatSlider-Default - m_EditorClassIdentifier: - themeName: Default - uiName: Default - valueTypeName: System.Single - prefab: {fileID: 3950712135413418637, guid: f601d441cf135fe479d319c5eb01abd8, type: 3} - supportedTags: 010000002000000024000000 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFloatSlider-Default.asset.meta b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFloatSlider-Default.asset.meta deleted file mode 100644 index 6a471f6..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgFloatSlider-Default.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 496a772f348d94c419faf2f5c09b773f -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgToggle-Default.asset b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgToggle-Default.asset deleted file mode 100644 index 18908da..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgToggle-Default.asset +++ /dev/null @@ -1,19 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: bcd1c64483d2457ab9143f086dff511d, type: 3} - m_Name: XuiavrgToggle-Default - m_EditorClassIdentifier: - themeName: Default - uiName: Default - valueTypeName: System.Boolean - prefab: {fileID: 8328042880981269257, guid: 0dc894baa18f5cb4eb9c82fd3c40d2bd, type: 3} - supportedTags: 0100000008000000 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgToggle-Default.asset.meta b/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgToggle-Default.asset.meta deleted file mode 100644 index 094d306..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Resources/Config/XuiavrgToggle-Default.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 41a9df21e888bf3409ef31a6079aab61 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/IXuiavrgAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/IXuiavrgAttribute.cs index 44d9a08..6e428d9 100644 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/IXuiavrgAttribute.cs +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/IXuiavrgAttribute.cs @@ -1,7 +1,15 @@ -namespace XericUI.ReflectionUIGenerator +namespace XericUI.ReflectionUIGenerator { + /// + /// 反射 UI Attribute 标记接口 + /// 所有标记 Attribute 实现此接口,在元数据提取阶段按序执行 BindMetaData + /// public interface IXuiavrgAttribute { - public void BindProperty(XuiavrgInspectorProperty property); + /// + /// 将 Attribute 的配置绑定到元数据对象 + /// + /// 目标元数据 + public void BindMetaData(AvrgMemberMetaData metaData); } -} \ No newline at end of file +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgAttribute.cs deleted file mode 100644 index 146995b..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgAttribute.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 反射属性标记 - /// - [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] - public class XuiavrgAttribute : Attribute, IXuiavrgAttribute - { - #region 字段属性 - - public readonly string LabelName; - public readonly string UIPropertyName; - public readonly string UnitLabelName; - public readonly int InspectorOrder; - public readonly string GroupName; - public readonly string OnTriggerCall; - public readonly XuiavrgSize ArgumentSize; - - /// - /// 标记属性 - /// - /// 属性显示名称 - /// 属性标记名(对应界面元素名称) - /// 属性显示单位名称 - /// 是否标记为必填项目 - /// 属性显示顺序 - /// 属性组名称(可以是以/&\分割的路径) - /// 属性附加的动作,调用此内联反射调用的函数 - /// 属性组名称(可以是以/&\分割的路径) - public XuiavrgAttribute(string labelName, string uiPropertyName, string unitLabelName, - int inspectorOrder = 0, string groupName = null, string onTriggerCall = "", XuiavrgSize size = XuiavrgSize.Normal) - { - LabelName = labelName; - UIPropertyName = uiPropertyName; - UnitLabelName = unitLabelName; - InspectorOrder = inspectorOrder; - GroupName = groupName; - OnTriggerCall = onTriggerCall; - ArgumentSize = size; - } - - #endregion - - #region 生成器 - - public void BindProperty(XuiavrgInspectorProperty property) - { - property.labelName = LabelName; - property.uiPropertyName = UIPropertyName; - property.unitLabelName = UnitLabelName; - property.inspectorOrder = InspectorOrder; - property.groupName = GroupName; - property.onTriggerCall = OnTriggerCall; - property.argumentSize = ArgumentSize; - } - - #endregion - } - - /// - /// 属性尺寸 - /// - public enum XuiavrgSize - { - None = 0, - - Tiny = 1000 - 2, - Small = 1000 - 1, - Normal = 1000, - Big, - Large, - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgAttribute.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgAttribute.cs.meta deleted file mode 100644 index 7f9b43c..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgAttribute.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 10e270c86cab4817a0b8ce7877b2ab88 -timeCreated: 1776505683 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgCollectionCheckAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgCollectionCheckAttribute.cs deleted file mode 100644 index da28c4a..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgCollectionCheckAttribute.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 集合检查 - /// - public class XuiavrgCollectionCheckAttribute : Attribute, IXuiavrgAttribute - { - public readonly int minCount; - public readonly int maxCount; - public readonly bool notEmpty; - - public XuiavrgCollectionCheckAttribute(int minCount, int maxCount, bool notEmpty) - { - this.minCount = minCount; - this.maxCount = maxCount; - this.notEmpty = notEmpty; - } - - public void BindProperty(XuiavrgInspectorProperty property) - { - property.collectionMinCount = minCount; - property.collectionMaxCount = maxCount; - property.collectionNotEmpty = notEmpty; - } - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgCollectionCheckAttribute.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgCollectionCheckAttribute.cs.meta deleted file mode 100644 index e4211a9..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgCollectionCheckAttribute.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 4ec4224cb3844c439dd5fdb6b31c9730 -timeCreated: 1777363387 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldEntryGroupAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldEntryGroupAttribute.cs index 278602a..a2a9820 100644 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldEntryGroupAttribute.cs +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldEntryGroupAttribute.cs @@ -38,10 +38,11 @@ namespace XericUI.ReflectionUIGenerator TagIndex = tagIndex; } - public void BindProperty(XuiavrgInspectorProperty property) + public void BindMetaData(AvrgMemberMetaData metaData) { - // 新的 Entry 体系通过 Manager.BuildEntries 直接处理此 Attribute - // 保留 IXuiavrgAttribute 兼容旧的 SchemaHydration 流程 + metaData.GroupPath = GroupPath; + metaData.Tag = Tag; + metaData.TagIndex = TagIndex; } } } diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldOrderAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldOrderAttribute.cs index 82b1d47..fe77e5b 100644 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldOrderAttribute.cs +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldOrderAttribute.cs @@ -24,9 +24,9 @@ namespace XericUI.ReflectionUIGenerator Order = order; } - public void BindProperty(XuiavrgInspectorProperty property) + public void BindMetaData(AvrgMemberMetaData metaData) { - property.inspectorOrder = Order; + metaData.FieldOrder = Order; } } } diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldPrefabOverrideAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldPrefabOverrideAttribute.cs index 85e4736..c90bda0 100644 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldPrefabOverrideAttribute.cs +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgFieldPrefabOverrideAttribute.cs @@ -21,16 +21,16 @@ namespace XericUI.ReflectionUIGenerator /// 创建预制体覆盖标记 /// /// - /// 目标预制体组名称,与 XuiavrgPropertyGroupScriptableObject.groupName 匹配 + /// 目标预制体名称,在 AvrgTypePrefabMappingConfig 中查找匹配 /// public XuiavrgFieldPrefabOverrideAttribute(string prefabGroupName) { PrefabGroupName = prefabGroupName; } - public void BindProperty(XuiavrgInspectorProperty property) + public void BindMetaData(AvrgMemberMetaData metaData) { - // 新的 Entry 体系通过 Manager 直接处理此 Attribute + metaData.PrefabOverrideName = PrefabGroupName; } } } diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgHideAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgHideAttribute.cs index b74ca34..1ac2146 100644 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgHideAttribute.cs +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgHideAttribute.cs @@ -1,5 +1,4 @@ -using System; -using UnityEngine.Serialization; +using System; namespace XericUI.ReflectionUIGenerator { @@ -20,9 +19,9 @@ namespace XericUI.ReflectionUIGenerator Hide = hideInInspector; } - public void BindProperty(XuiavrgInspectorProperty property) + public void BindMetaData(AvrgMemberMetaData metaData) { - property.hideInInspector = Hide; + metaData.Hide = Hide; } } -} \ No newline at end of file +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNotEmptyAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNotEmptyAttribute.cs deleted file mode 100644 index 3e56035..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNotEmptyAttribute.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 不可为空 - /// - public class XuiavrgNotEmptyAttribute : Attribute, IXuiavrgAttribute - { - public void BindProperty(XuiavrgInspectorProperty property) - { - property.notEmpty = true; - } - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNotEmptyAttribute.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNotEmptyAttribute.cs.meta deleted file mode 100644 index 031e93f..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNotEmptyAttribute.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 89e35e544d66421c8cc524d3b4d7b943 -timeCreated: 1777360693 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNumberCheckAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNumberCheckAttribute.cs deleted file mode 100644 index db02e91..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNumberCheckAttribute.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 数值检查 - /// - public class XuiavrgNumberCheckAttribute : Attribute, IXuiavrgAttribute - { - public readonly float minValue; - public readonly float maxValue; - - public XuiavrgNumberCheckAttribute(float minValue, float maxValue) - { - this.minValue = minValue; - this.maxValue = maxValue; - } - - public void BindProperty(XuiavrgInspectorProperty property) - { - property.numberMinValue = minValue; - property.numberMaxValue = maxValue; - } - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNumberCheckAttribute.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNumberCheckAttribute.cs.meta deleted file mode 100644 index 86aa0ae..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgNumberCheckAttribute.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: d5b533c6d6ca4de8811adc3913d1fd9b -timeCreated: 1777362005 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgPropertyCheckAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgPropertyCheckAttribute.cs new file mode 100644 index 0000000..f680a83 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgPropertyCheckAttribute.cs @@ -0,0 +1,61 @@ +using System; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 属性检查标记 + /// 替代旧的 Required/NotEmpty 标记,扩展范围验证、警告、无效值检查 + /// + /// 使用示例: + /// [XuiavrgPropertyCheck(Required = true)] public string name; + /// [XuiavrgPropertyCheck(Required = true, ValidateReasonable = true)] public float speed; + /// [XuiavrgPropertyCheck(CheckKey = "MyCustomKey", ValidateWarning = true)] public int score; + /// + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)] + public class XuiavrgPropertyCheckAttribute : Attribute, IXuiavrgAttribute + { + /// + /// 判断依据标识 + /// 默认使用类型全称(如 "System.Single")与 AvrgValueRangeConfig 中的条目匹配 + /// 也可自定义其他名称 + /// + public string CheckKey { get; set; } + + /// 是否必填 + public bool Required { get; set; } + + /// 是否启用合理值范围检查 + public bool ValidateReasonable { get; set; } + + /// 是否启用警告状态检查 + public bool ValidateWarning { get; set; } + + /// 是否启用无效值状态检查 + public bool ValidateInvalid { get; set; } + + public XuiavrgPropertyCheckAttribute() + { + } + + /// + /// 创建必填检查标记 + /// + public XuiavrgPropertyCheckAttribute(bool required) + { + Required = required; + } + + public void BindMetaData(AvrgMemberMetaData metaData) + { + metaData.Required = Required; + + // 如果指定了 CheckKey,使用它;否则保持元数据默认(元数据默认用 Type.FullName) + if (!string.IsNullOrEmpty(CheckKey)) + metaData.PropertyCheckKey = CheckKey; + + metaData.ValidateReasonable = ValidateReasonable; + metaData.ValidateWarning = ValidateWarning; + metaData.ValidateInvalid = ValidateInvalid; + } + } +} diff --git a/Editor/OverrideUI/XericUIrAttributeDropdownEditor.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgPropertyCheckAttribute.cs.meta similarity index 83% rename from Editor/OverrideUI/XericUIrAttributeDropdownEditor.cs.meta rename to Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgPropertyCheckAttribute.cs.meta index ab7b4c1..5159ec7 100644 --- a/Editor/OverrideUI/XericUIrAttributeDropdownEditor.cs.meta +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgPropertyCheckAttribute.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8f8d011250fa17848a724fbb80e756fc +guid: 217e7cb9b7af5af4186e2d3f10a89c7c MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgRequiredAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgRequiredAttribute.cs deleted file mode 100644 index 03c16a7..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgRequiredAttribute.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 必填 - /// - public class XuiavrgRequiredAttribute : Attribute, IXuiavrgAttribute - { - public void BindProperty(XuiavrgInspectorProperty property) - { - property.required = true; - property.notEmpty = true; - } - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgRequiredAttribute.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgRequiredAttribute.cs.meta deleted file mode 100644 index 51f13ad..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgRequiredAttribute.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: bcc4b8b735e84251b11ef87e9fe5b2cb -timeCreated: 1777358718 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgStringCheckAttribute.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgStringCheckAttribute.cs deleted file mode 100644 index 0d53691..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgStringCheckAttribute.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using UnityEngine; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 字符串检查 - /// - public class XuiavrgStringCheckAttribute : Attribute, IXuiavrgAttribute - { - public readonly int minLength; - public readonly int maxLength; - public readonly string regexPattern; - public readonly bool onlyLetters; - public readonly bool onlyNumber; - public readonly bool onlyEmail; - public readonly bool onlyPhone; - public readonly bool onlyUrl; - - public XuiavrgStringCheckAttribute( - int minLength = 0, int maxLength = int.MaxValue, - string regexPattern = "", - bool onlyLetters = false, bool onlyNumber = false, bool onlyEmail = true, bool onlyPhone = true, - bool onlyUrl = true) - { - this.minLength = minLength; - this.maxLength = maxLength; - this.regexPattern = regexPattern; - this.onlyLetters = onlyLetters; - this.onlyNumber = onlyNumber; - this.onlyEmail = onlyEmail; - this.onlyPhone = onlyPhone; - this.onlyUrl = onlyUrl; - } - - public void BindProperty(XuiavrgInspectorProperty property) - { - property.stringMinLength = minLength; - property.stringMaxLength = maxLength; - property.stringRegexPattern = regexPattern; - property.stringOnlyLetters = onlyLetters; - property.stringOnlyNumber = onlyNumber; - property.stringOnlyEmail = onlyEmail; - property.stringOnlyPhone = onlyPhone; - property.stringOnlyUrl = onlyUrl; - } - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgStringCheckAttribute.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgStringCheckAttribute.cs.meta deleted file mode 100644 index 54e4288..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/Attribute/XuiavrgStringCheckAttribute.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 0a0e34665ed24a7c87e5eb84a45f6ef6 -timeCreated: 1777360997 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberCheckResult.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberCheckResult.cs new file mode 100644 index 0000000..af810da --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberCheckResult.cs @@ -0,0 +1,23 @@ +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 单个成员的验证结果枚举 + /// + public enum AvrgMemberCheckResult + { + /// 未标记检查或不适用(跳过) + Skipped = 0, + + /// 检查通过,值有效 + Valid = 1, + + /// 必填但为空 + Empty = 2, + + /// 值在警告范围内 + Warning = 3, + + /// 值在无效范围内 + Invalid = 4, + } +} diff --git a/Editor/InterfaceAttributeReflectionGenerator/XuiavrgPropertyGroupScriptableObjectEditor.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberCheckResult.cs.meta similarity index 83% rename from Editor/InterfaceAttributeReflectionGenerator/XuiavrgPropertyGroupScriptableObjectEditor.cs.meta rename to Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberCheckResult.cs.meta index 97605f2..740e5fe 100644 --- a/Editor/InterfaceAttributeReflectionGenerator/XuiavrgPropertyGroupScriptableObjectEditor.cs.meta +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberCheckResult.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c7acec0d907c14a4f9f8247b6ccd8261 +guid: 51cc10b88f508db4495e8998f44e8b01 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaData.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaData.cs new file mode 100644 index 0000000..ad67545 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaData.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 单个成员的元数据数据结构 + /// 记录从反射 + Attribute 标记中提取的所有信息 + /// + public class AvrgMemberMetaData + { + // ============ 反射信息 ============ + + /// 字段/属性名 + public string MemberName { get; set; } + + /// 反射信息 (FieldInfo 或 PropertyInfo) + public MemberInfo MemberInfo { get; set; } + + /// 值类型 + public Type ValueType { get; set; } + + /// 是否为属性 (false 为字段) + public bool IsProperty { get; set; } + + /// 绑定的目标实例 (可选,Step1 时可空) + public object TargetInstance { get; set; } + + // ============ 从 IXuiavrgAttribute 融合的元数据 ============ + + /// 显示标签 + public string LabelName { get; set; } + + /// 单位文本 + public string UnitLabelName { get; set; } + + /// 功能标记 + public XuiavrgFieldTag Tag { get; set; } = XuiavrgFieldTag.None; + + /// 标记索引 (同 Tag 多实例时区分) + public int TagIndex { get; set; } + + /// 排序值 (越小越靠前) + public int FieldOrder { get; set; } + + /// 编组路径 (以 "/" 分割层级) + public string GroupPath { get; set; } + + /// 预制体覆盖名称 + public string PrefabOverrideName { get; set; } + + /// 是否在 UI 中隐藏 + public bool Hide { get; set; } + + /// 值变更时触发的反射调用方法名 + public string OnTriggerCall { get; set; } + + /// + /// 类的初始默认值(由类字段初始化器决定) + /// 例如 public float speed = 10f; → DefaultValue = 10f + /// + public object DefaultValue { get; set; } + + // ============ 属性检查 (由 [PropertyCheck] 填充) ============ + + /// + /// 判断依据标识 + /// 默认使用 ValueType.FullName 与 AvrgValueRangeConfig 中的条目匹配 + /// + public string PropertyCheckKey { get; set; } + + /// 是否必填 + public bool Required { get; set; } + + /// 是否不可为空 + public bool NotEmpty { get; set; } + + /// 是否启用合理值范围检查 + public bool ValidateReasonable { get; set; } + + /// 是否启用警告状态检查 + public bool ValidateWarning { get; set; } + + /// 是否启用无效值状态检查 + public bool ValidateInvalid { get; set; } + + // ============ (已弃用字段保留) 旧的验证约束 ============ + + /// 字符串最小长度 + [Obsolete("将移至 ValueRangeConfig 管理")] + public int StringMinLength { get; set; } + + /// 字符串最大长度 + public int StringMaxLength { get; set; } = int.MaxValue; + + /// 数值最小值 + [Obsolete("将移至 ValueRangeConfig 管理")] + public float NumberMinValue { get; set; } + + /// 数值最大值 + [Obsolete("将移至 ValueRangeConfig 管理")] + public float NumberMaxValue { get; set; } + + // ============ 原始 Attribute 引用 ============ + + /// + /// 收集到的所有 IXuiavrgAttribute 列表 + /// 保留原始引用,供扩展和自定义处理使用 + /// + public List Attributes { get; set; } = new List(); + + /// + /// 从绑定目标获取值 + /// + 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; + } + } + + /// + /// 设置值到绑定目标 + /// + 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; + } + } + } +} diff --git a/Editor/OverrideUI/XericUIrAttributeEditorHelper.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaData.cs.meta similarity index 83% rename from Editor/OverrideUI/XericUIrAttributeEditorHelper.cs.meta rename to Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaData.cs.meta index 0a0b630..e53a3cd 100644 --- a/Editor/OverrideUI/XericUIrAttributeEditorHelper.cs.meta +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaData.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 9a59db39a3dc2734bb5f3adaafbed06c +guid: f152de979aea6de48a4979fa509ac23f MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaDataSet.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaDataSet.cs new file mode 100644 index 0000000..fe7ccd1 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaDataSet.cs @@ -0,0 +1,247 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 元数据集 + /// 包含从对象反射提取的完整元数据,以及用于生成 MVVM 绑定的 BuildMDVM() 方法 + /// + public class AvrgMemberMetaDataSet + { + private List _metaDataList = new List(); + + /// + /// 只读访问元数据列表 + /// + public IReadOnlyList MetaDataList => _metaDataList; + + /// + /// 添加元数据 + /// + public void Add(AvrgMemberMetaData metaData) + { + _metaDataList.Add(metaData); + } + + /// + /// 处理编组合并 — 将 GroupPath 相同的元数据合并 + /// 非编组元数据保持为独立 Entry + /// + internal void MergeGroupEntries() + { + // 提取编组和非编组 + var grouped = _metaDataList + .Where(m => !string.IsNullOrEmpty(m.GroupPath)) + .GroupBy(m => m.GroupPath) + .ToList(); + + var nonGrouped = _metaDataList + .Where(m => string.IsNullOrEmpty(m.GroupPath)) + .ToList(); + + var merged = new List(); + + foreach (var gEntry in grouped) + { + // 编组内第一个成员作为主元数据 + var first = gEntry.First(); + // 合成的 metaData 保持 GroupPath 和主 Tag + merged.Add(first); + } + + // 非编组直接追加 + merged.AddRange(nonGrouped); + + _metaDataList = merged; + } + + /// + /// 按 FieldOrder 排序 + /// + internal void SortByFieldOrder() + { + _metaDataList.Sort((a, b) => + { + int orderCompare = a.FieldOrder.CompareTo(b.FieldOrder); + if (orderCompare != 0) return orderCompare; + return string.Compare(a.MemberName, b.MemberName, StringComparison.Ordinal); + }); + } + + /// + /// 为元数据生成对应的 MVVM 值绑定模板 + /// + /// 可选的业务目标对象(如果元数据中没有绑定目标,则在此传入) + /// 可选的冲突解决配置 + /// 绑定集合 + public AvrgMvvmBindingCollection BuildMDVM( + object businessTarget = null, + AvrgMvvmResolverConfig resolverConfig = null) + { + var bindingCollection = new AvrgMvvmBindingCollection(); + var scheduler = new AvrgMvvmScheduler(resolverConfig); + + foreach (var metaData in _metaDataList) + { + // 确定目标实例 + var target = metaData.TargetInstance ?? businessTarget; + if (target == null) + continue; + + // 创建绑定 + var binding = new AvrgMvvmBinding + { + MetaData = metaData, + ReadFunc = () => metaData.GetValue(), + }; + + var capturedBinding = binding; + binding.WriteAction = (val) => + { + metaData.SetValue(val); + scheduler.MarkDataDirty(capturedBinding); + }; + + // 注册到调度器 + scheduler.Register(binding); + bindingCollection.Add(binding); + } + + bindingCollection.AttachScheduler(scheduler); + return bindingCollection; + } + + // ============ 验证方法 ============ + + /// + /// 对当前元数据集执行属性检查验证 + /// + /// 值范围配置(可选,不传入则不执行范围检查) + /// 验证结果集合 + public AvrgValidationResultSet Validate(AvrgValueRangeConfig valueRangeConfig = null) + { + var resultSet = new AvrgValidationResultSet(); + + foreach (var metaData in _metaDataList) + { + if (metaData.Hide) + continue; + + // 只有标记了 PropertyCheck 的才验证 + if (!metaData.Required && !metaData.ValidateReasonable + && !metaData.ValidateWarning && !metaData.ValidateInvalid) + { + resultSet.Results.Add(new AvrgMemberValidationResult + { + MetaData = metaData, + Result = AvrgMemberCheckResult.Skipped, + CurrentValueString = metaData.GetValue()?.ToString(), + Message = "未标记属性检查", + }); + continue; + } + + var value = metaData.GetValue(); + var checkResult = CheckSingleMember(metaData, value, valueRangeConfig); + resultSet.Results.Add(checkResult); + } + + return resultSet; + } + + /// + /// 验证单个成员 + /// + private AvrgMemberValidationResult CheckSingleMember( + AvrgMemberMetaData metaData, + object value, + AvrgValueRangeConfig rangeConfig) + { + var result = new AvrgMemberValidationResult + { + MetaData = metaData, + CurrentValueString = value?.ToString() ?? "(null)", + }; + + // 根据 CheckKey 查找值范围规则 + AvrgValueRangeEntry rangeEntry = null; + string checkKey = !string.IsNullOrEmpty(metaData.PropertyCheckKey) + ? metaData.PropertyCheckKey + : metaData.ValueType?.FullName; + + if (rangeConfig != null && !string.IsNullOrEmpty(checkKey)) + { + rangeEntry = rangeConfig.FindEntry(checkKey); + } + + // ——— 优先级 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); + } + + if (isEmpty) + { + result.Result = AvrgMemberCheckResult.Empty; + result.Message = $"必填字段 '{metaData.MemberName}' 为空"; + return result; + } + } + + // 如果没有范围配置,无法继续检查 + if (rangeEntry == null) + { + result.Result = AvrgMemberCheckResult.Valid; + result.Message = "通过(无范围配置)"; + return result; + } + + // ——— 优先级 2: 合理值检查(最高优先级,命中即通过) ——— + if (metaData.ValidateReasonable && rangeEntry.IsReasonable(value)) + { + result.Result = AvrgMemberCheckResult.Valid; + result.Message = "值在合理范围内"; + return result; + } + + // ——— 优先级 3: 无效值检查 ——— + if (metaData.ValidateInvalid && rangeEntry.IsInvalid(value)) + { + result.Result = AvrgMemberCheckResult.Invalid; + result.Message = $"值 {value} 在无效范围内"; + return result; + } + + // ——— 优先级 4: 警告值检查 ——— + if (metaData.ValidateWarning && rangeEntry.IsWarning(value)) + { + result.Result = AvrgMemberCheckResult.Warning; + result.Message = $"值 {value} 在警告范围内"; + return result; + } + + // ——— 最终: 通过 ——— + result.Result = AvrgMemberCheckResult.Valid; + result.Message = "通过"; + return result; + } + } +} diff --git a/Editor/OverrideUI/XericUIrAttributeButtonEditor.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaDataSet.cs.meta similarity index 83% rename from Editor/OverrideUI/XericUIrAttributeButtonEditor.cs.meta rename to Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaDataSet.cs.meta index b839bac..49972a9 100644 --- a/Editor/OverrideUI/XericUIrAttributeButtonEditor.cs.meta +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberMetaDataSet.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 326a7ebf62af0d5419630e20cf2861e7 +guid: 9382162bffd9c7c43ac24fca5ca83cb0 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberValidationResult.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberValidationResult.cs new file mode 100644 index 0000000..0140bfa --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberValidationResult.cs @@ -0,0 +1,69 @@ +using System.Collections.Generic; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 单个成员的详细验证结果 + /// + public struct AvrgMemberValidationResult + { + /// 验证的元数据 + public AvrgMemberMetaData MetaData { get; set; } + + /// 验证结果 + public AvrgMemberCheckResult Result { get; set; } + + /// 当前值(字符串形式,便于显示) + public string CurrentValueString { get; set; } + + /// 验证消息(说明原因) + public string Message { get; set; } + + /// 是否通过验证 + public bool IsPassed => Result == AvrgMemberCheckResult.Valid + || Result == AvrgMemberCheckResult.Skipped + || Result == AvrgMemberCheckResult.Warning; // Warning 视为通过但需注意 + } + + /// + /// 完整验证结果集合 + /// + public class AvrgValidationResultSet + { + /// 所有成员的验证结果 + public List Results { get; set; } = new List(); + + /// 未验证通过的条目数(Empty + Invalid) + public int FailedCount + { + get + { + int count = 0; + foreach (var r in Results) + { + if (r.Result == AvrgMemberCheckResult.Empty || r.Result == AvrgMemberCheckResult.Invalid) + count++; + } + return count; + } + } + + /// 警告条目数 + public int WarningCount + { + get + { + int count = 0; + foreach (var r in Results) + { + if (r.Result == AvrgMemberCheckResult.Warning) + count++; + } + return count; + } + } + + /// 是否全部通过(无失败项) + public bool IsAllPassed => FailedCount == 0; + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberValidationResult.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberValidationResult.cs.meta new file mode 100644 index 0000000..c74509c --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMemberValidationResult.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb4d50aa593365b4abae59c7ac2ae557 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBinding.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBinding.cs new file mode 100644 index 0000000..c9acc12 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBinding.cs @@ -0,0 +1,98 @@ +using System; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 值绑定模板 + /// 为单个元数据成员创建的 MVVM 双向绑定,包含读/写委托和脏标记 + /// + public class AvrgMvvmBinding + { + /// 关联的元数据 + public AvrgMemberMetaData MetaData { get; set; } + + /// 从数据源读取值的委托 + public Func ReadFunc { get; set; } + + /// 写入值到数据源的委托 + public Action WriteAction { get; set; } + + // ============ 脏标记 ============ + + /// UI 侧是否标记为脏 + public bool IsUIDirty { get; set; } + + /// 数据侧是否标记为脏 + public bool IsDataDirty { get; set; } + + /// UI 侧写入的缓存值 + public object UICachedValue { get; set; } + + /// 数据侧写入的缓存值 + public object DataCachedValue { get; set; } + + // ============ UI 绑定 ============ + + /// UI 值变更回调委托 — UI 组件订阅此回调以更新显示 + public Action OnUIDataChanged; + + /// 通知 UI 更新值 + public void NotifyUIUpdate(object value) + { + OnUIDataChanged?.Invoke(value); + } + + /// 清除所有 UI 变更订阅 + public void ClearUIEvent() + { + OnUIDataChanged = null; + } + + // ============ 便捷方法 ============ + + /// + /// UI 侧写入值(标记脏,不立即传播) + /// + public void WriteFromUI(object value) + { + UICachedValue = value; + IsUIDirty = true; + } + + /// + /// 数据侧写入值(标记脏,不立即传播) + /// + public void WriteFromData(object value) + { + DataCachedValue = value; + IsDataDirty = true; + } + + /// + /// 从数据源读取当前值 + /// + public object ReadFromDataSource() + { + return ReadFunc?.Invoke(); + } + + /// + /// 将值实际写入数据源 + /// + public void CommitToDataSource(object value) + { + WriteAction?.Invoke(value); + } + + /// + /// 重置脏标记 + /// + public void ClearDirty() + { + IsUIDirty = false; + IsDataDirty = false; + UICachedValue = null; + DataCachedValue = null; + } + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBinding.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBinding.cs.meta new file mode 100644 index 0000000..7d5795d --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBinding.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0d011f5c07bb55648b27e40166a1ca58 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBindingCollection.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBindingCollection.cs new file mode 100644 index 0000000..39a5a0d --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBindingCollection.cs @@ -0,0 +1,82 @@ +using System.Collections; +using System.Collections.Generic; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 绑定集合 — 统一管理所有 AvrgMvvmBinding + /// + public class AvrgMvvmBindingCollection : IEnumerable + { + private List _bindings = new List(); + private AvrgMvvmScheduler _scheduler; + + /// + /// 绑定的元数据集引用(可选) + /// + public AvrgMemberMetaDataSet SourceMetaDataSet { get; set; } + + /// + /// 绑定数量 + /// + public int Count => _bindings.Count; + + /// + /// 添加绑定 + /// + public void Add(AvrgMvvmBinding binding) + { + _bindings.Add(binding); + } + + /// + /// 获取指定索引的绑定 + /// + public AvrgMvvmBinding this[int index] => _bindings[index]; + + /// + /// 获取指定成员名称的绑定 + /// + public AvrgMvvmBinding FindByMemberName(string memberName) + { + return _bindings.Find(b => b.MetaData?.MemberName == memberName); + } + + /// + /// 获取指定 Tag 类型的绑定列表 + /// + public List FindByTag(XuiavrgFieldTag tag) + { + return _bindings.FindAll(b => (b.MetaData?.Tag & tag) == tag); + } + + /// + /// 附加调度器 + /// + internal void AttachScheduler(AvrgMvvmScheduler scheduler) + { + _scheduler = scheduler; + } + + /// + /// 触发调度器 Flush,处理所有脏标记 + /// + public void Flush() + { + _scheduler?.Flush(); + } + + /// + /// 清空所有绑定 + /// + public void Clear() + { + _bindings.Clear(); + _scheduler?.Clear(); + } + + public IEnumerator GetEnumerator() => _bindings.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBindingCollection.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBindingCollection.cs.meta new file mode 100644 index 0000000..f73ce58 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmBindingCollection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2cd947ac831d21b4288a0b3268d79ffd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmResolverConfig.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmResolverConfig.cs new file mode 100644 index 0000000..cf6dca5 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmResolverConfig.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 可配置的冲突解决器 ScriptableObject + /// 预配置各种冲突场景下的决策方法,以及可注册的自定义解析器列表 + /// + [CreateAssetMenu( + fileName = "Xeric UI avr Resolver Config", + menuName = "Xeric Library/UI Action Vessel/Reflection UI Gen/UI avr Resolver Config", + order = 20)] + public class AvrgMvvmResolverConfig : ScriptableObject, IAvrgMvvmConflictResolver + { + /// 全局默认冲突解决策略 + [Tooltip("全局默认冲突解决策略")] + public ConflictResolutionStrategy defaultStrategy = ConflictResolutionStrategy.PreferUI; + + /// 自定义解析器列表(按序尝试) + [Tooltip("自定义解析器类型全名列表(按序尝试,需实现 IAvrgMvvmConflictResolver)")] + public List customResolverTypeNames = new List(); + + // 运行时已实例化的解析器缓存 + [NonSerialized] + private List _resolverInstances; + + /// + /// 获取或实例化自定义解析器 + /// + private List GetResolverInstances() + { + if (_resolverInstances != null) + return _resolverInstances; + + _resolverInstances = new List(); + + foreach (var typeName in customResolverTypeNames) + { + try + { + var type = Type.GetType(typeName); + if (type != null && typeof(IAvrgMvvmConflictResolver).IsAssignableFrom(type)) + { + var instance = Activator.CreateInstance(type) as IAvrgMvvmConflictResolver; + if (instance != null) + _resolverInstances.Add(instance); + } + } + catch (Exception e) + { + Debug.LogWarning($"[AvrgMvvmResolverConfig] 无法实例化解析器 {typeName}: {e.Message}"); + } + } + + return _resolverInstances; + } + + /// + /// 尝试按配置解决冲突 + /// + public bool TryResolve(AvrgMvvmBinding binding, out object resolvedValue) + { + // 按序尝试自定义解析器 + var resolvers = GetResolverInstances(); + foreach (var resolver in resolvers) + { + if (resolver.TryResolve(binding, defaultStrategy, out resolvedValue)) + return true; + } + + resolvedValue = null; + return false; + } + + /// + /// IAvrgMvvmConflictResolver 接口实现 + /// + bool IAvrgMvvmConflictResolver.TryResolve( + AvrgMvvmBinding binding, + ConflictResolutionStrategy defaultStrategy, + out object resolvedValue) + { + return TryResolve(binding, out resolvedValue); + } + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmResolverConfig.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmResolverConfig.cs.meta new file mode 100644 index 0000000..39871bf --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmResolverConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e2b7e3ef1b0932a4a9f2cbbb1ae11546 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmScheduler.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmScheduler.cs new file mode 100644 index 0000000..5f1486d --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmScheduler.cs @@ -0,0 +1,209 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// MVVM 调度器 — 管理脏标记、触发 Flush 更新、检测并解决冲突 + /// + /// 冲突解决策略: + /// - 仅数据脏 → 通知 UI 更新 + /// - 仅 UI 脏 → 写入数据源 + /// - 双方都脏 → 走配置的冲突解决策略 + /// + public class AvrgMvvmScheduler + { + /// 用户可注册的外部冲突解决事件 + public event Func OnConflictDetected; + + /// 当前配置的冲突解决策略 + public ConflictResolutionStrategy DefaultStrategy { get; set; } = ConflictResolutionStrategy.PreferUI; + + private List _bindings = new List(); + private HashSet _dirtySet = new HashSet(); + private AvrgMvvmResolverConfig _resolverConfig; + + public AvrgMvvmScheduler(AvrgMvvmResolverConfig resolverConfig = null) + { + _resolverConfig = resolverConfig; + } + + /// + /// 注册绑定到调度器 + /// + public void Register(AvrgMvvmBinding binding) + { + if (!_bindings.Contains(binding)) + _bindings.Add(binding); + } + + /// + /// UI 侧写入时调用 — 标记脏 + /// + public void MarkUIDirty(AvrgMvvmBinding binding) + { + binding.IsUIDirty = true; + _dirtySet.Add(binding); + } + + /// + /// 数据侧写入时调用 — 标记脏 + /// + public void MarkDataDirty(AvrgMvvmBinding binding) + { + binding.IsDataDirty = true; + _dirtySet.Add(binding); + } + + /// + /// 处理所有脏绑定 + /// + public void Flush() + { + if (_dirtySet.Count == 0) return; + + foreach (var binding in _dirtySet) + { + ResolveBinding(binding); + binding.ClearDirty(); + } + + _dirtySet.Clear(); + } + + private void ResolveBinding(AvrgMvvmBinding binding) + { + bool uiDirty = binding.IsUIDirty; + bool dataDirty = binding.IsDataDirty; + + if (uiDirty && dataDirty) + { + // 双方都脏 — 走冲突解决 + ResolveConflict(binding); + } + else if (dataDirty) + { + // 仅数据侧更新 → 通知 UI + var value = binding.DataCachedValue ?? binding.ReadFromDataSource(); + binding.CommitToDataSource(value); + binding.NotifyUIUpdate(value); + } + else if (uiDirty) + { + // 仅 UI 侧更新 → 写入数据源 + binding.CommitToDataSource(binding.UICachedValue); + } + } + + private void ResolveConflict(AvrgMvvmBinding binding) + { + // 1. 检查外部事件处理 + if (OnConflictDetected != null) + { + try + { + var resolvedValue = OnConflictDetected.Invoke(binding, DefaultStrategy); + if (resolvedValue != null) + { + binding.CommitToDataSource(resolvedValue); + binding.NotifyUIUpdate(resolvedValue); + return; + } + } + catch (Exception e) + { + Debug.LogError($"[AvrgMvvmScheduler] 外部冲突解决事件异常: {e.Message}"); + } + } + + // 2. 检查 ResolverConfig 中的自定义策略 + if (_resolverConfig != null && _resolverConfig.TryResolve(binding, out object configResolvedValue)) + { + binding.CommitToDataSource(configResolvedValue); + binding.NotifyUIUpdate(configResolvedValue); + return; + } + + // 3. 按默认策略解决 + 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); + break; + } + + // 值不同,回退到 PreferUI + binding.CommitToDataSource(binding.UICachedValue); + binding.NotifyUIUpdate(binding.UICachedValue); + Debug.LogWarning($"[AvrgMvvmScheduler] 值冲突(CompareValues回退PreferUI): {binding.MetaData?.MemberName}"); + break; + + default: + binding.CommitToDataSource(binding.UICachedValue); + binding.NotifyUIUpdate(binding.UICachedValue); + break; + } + } + + /// + /// 清空所有注册 + /// + public void Clear() + { + _bindings.Clear(); + _dirtySet.Clear(); + } + + /// + /// 手动将绑定的 UI 值标记为脏 + /// + public void MarkUI(AvrgMvvmBinding binding, object value) + { + binding.WriteFromUI(value); + MarkUIDirty(binding); + } + + /// + /// 手动将绑定的数据值写入并标记脏 + /// + public void MarkData(AvrgMvvmBinding binding, object value) + { + binding.WriteFromData(value); + MarkDataDirty(binding); + } + } + + /// + /// 冲突解决策略枚举 + /// + public enum ConflictResolutionStrategy + { + /// 优先使用 UI 侧的值 + PreferUI, + + /// 优先使用数据源侧的值 + PreferData, + + /// 比较两者,相同则忽略,不同则优先 UI + CompareValues, + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmScheduler.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmScheduler.cs.meta new file mode 100644 index 0000000..441018f --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgMvvmScheduler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c0f505fa03fa4e74399ff1e62540dfd8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgTypePrefabMappingConfig.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgTypePrefabMappingConfig.cs new file mode 100644 index 0000000..2a0f1bd --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgTypePrefabMappingConfig.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 类型-预制体映射配置 ScriptableObject + /// 用于在 GenerateUI 阶段根据数据类型查找匹配的预制体 + /// + [CreateAssetMenu( + fileName = "Xeric UI avr Type-Prefab Mapping", + menuName = "Xeric Library/UI Action Vessel/Reflection UI Gen/UI avr Type-Prefab Mapping", + order = 15)] + public class AvrgTypePrefabMappingConfig : ScriptableObject + { + /// + /// 单个类型映射条目 + /// + [Serializable] + public class TypePrefabEntry + { + [Tooltip("C# 数据类型全名 (如 System.Single, System.String, System.Boolean)")] + public string valueTypeName; + + [Tooltip("对应的 UI 预制体")] + public GameObject prefab; + + /// 缓存的类型 + [NonSerialized] public Type cachedType; + } + + /// 类型映射列表 + [Tooltip("类型映射列表:数据类型 → UI 预制体")] + public List typePrefabEntries = new List(); + + /// 默认预制体(无精确匹配时的回退) + [Tooltip("默认预制体(没有类型匹配时的回退方案)")] + public GameObject defaultPrefab; + + [NonSerialized] + private Dictionary _typeCache; + + private void BuildCache() + { + if (_typeCache != null) return; + _typeCache = new Dictionary(); + + foreach (var entry in typePrefabEntries) + { + if (entry.prefab == null) continue; + + if (entry.cachedType == null && !string.IsNullOrEmpty(entry.valueTypeName)) + entry.cachedType = Type.GetType(entry.valueTypeName); + + if (entry.cachedType != null && !_typeCache.ContainsKey(entry.cachedType)) + _typeCache[entry.cachedType] = entry.prefab; + } + } + + /// + /// 根据数据类型查找匹配的预制体 + /// + public GameObject FindPrefabByType(Type valueType) + { + if (valueType == null) return defaultPrefab; + + BuildCache(); + + if (_typeCache.TryGetValue(valueType, out var prefab)) + return prefab; + + // 精确匹配失败,尝试基类/接口匹配 + foreach (var kvp in _typeCache) + { + if (kvp.Key.IsAssignableFrom(valueType)) + return kvp.Value; + } + + return defaultPrefab; + } + + /// + /// 根据绑定的 Entry Bindings 查找匹配的预制体 + /// + public GameObject FindPrefabByBindings(IReadOnlyList bindings) + { + if (bindings == null || bindings.Count == 0) return defaultPrefab; + + var metaData = bindings[0].MetaData; + if (metaData?.ValueType == null) return defaultPrefab; + + // 优先按 PrefabOverrideName 查找预制体 + if (!string.IsNullOrEmpty(metaData.PrefabOverrideName)) + { + foreach (var entry in typePrefabEntries) + { + if (entry.prefab != null && entry.prefab.name == metaData.PrefabOverrideName) + return entry.prefab; + } + } + + // 按类型匹配 + var typeMatch = FindPrefabByType(metaData.ValueType); + if (typeMatch != null) return typeMatch; + + return defaultPrefab; + } + + /// + /// 按预制体名称查找 + /// + public GameObject FindPrefabByName(string prefabName) + { + foreach (var entry in typePrefabEntries) + { + if (entry.prefab != null && entry.prefab.name == prefabName) + return entry.prefab; + } + return null; + } + + /// + /// 清空缓存 + /// + public void ClearCache() + { + _typeCache = null; + } + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgTypePrefabMappingConfig.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgTypePrefabMappingConfig.cs.meta new file mode 100644 index 0000000..2bcb20d --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgTypePrefabMappingConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 31a97ff1112e7a84e9ade9ca81ea19a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeConfig.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeConfig.cs new file mode 100644 index 0000000..04083c6 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeConfig.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 值范围配置 ScriptableObject + /// 定义不同类型的空/无效/警告/合理值范围判断依据 + /// + [CreateAssetMenu( + fileName = "Xeric UI avr Value Range Config", + menuName = "Xeric Library/UI Action Vessel/Reflection UI Gen/UI avr Value Range Config", + order = 25)] + public class AvrgValueRangeConfig : ScriptableObject + { + /// 值范围规则列表 + [Tooltip("按类型定义的取值范围规则")] + public List entries = new List(); + + [NonSerialized] + private Dictionary _entryCache; + + private void BuildCache() + { + if (_entryCache != null) return; + _entryCache = new Dictionary(); + foreach (var entry in entries) + { + if (!string.IsNullOrEmpty(entry.key) && !_entryCache.ContainsKey(entry.key)) + _entryCache[entry.key] = entry; + } + } + + /// + /// 根据键查找值范围规则 + /// + /// 类型标识(通常为 Type.FullName) + /// 匹配的规则,未找到则返回 null + public AvrgValueRangeEntry FindEntry(string key) + { + if (string.IsNullOrEmpty(key)) return null; + BuildCache(); + _entryCache.TryGetValue(key, out var entry); + return entry; + } + + /// + /// 根据 Type 查找值范围规则 + /// + public AvrgValueRangeEntry FindEntryByType(Type type) + { + if (type == null) return null; + return FindEntry(type.FullName); + } + + /// + /// 清空缓存(用于编辑器热重载后重新构建) + /// + public void ClearCache() + { + _entryCache = null; + } + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeConfig.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeConfig.cs.meta new file mode 100644 index 0000000..e6c7e31 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a432178c6615374f89344e6e53c75dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeEntry.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeEntry.cs new file mode 100644 index 0000000..755cb1c --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeEntry.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 值范围规则条目 + /// 定义某个类型(由 CheckKey 标识)的空/无效/警告/合理值范围 + /// + [Serializable] + public class AvrgValueRangeEntry + { + /// + /// 类型标识符(默认使用 Type.FullName,如 "System.Single", "System.Int32") + /// 也支持自定义字符串以匹配特定语义 + /// + public string key = ""; + + // ===== 空值范围 ===== + + /// + /// 是否跳过空值检查(如 bool 类型无法判断空) + /// + public bool skipEmptyCheck = false; + + /// + /// 视为空的最小值(>= 此值视为空) + /// + public float emptyRangeMin = float.MinValue; + + /// + /// 视为空的最大值(<= 此值视为空) + /// + public float emptyRangeMax = float.MaxValue; + + // ===== 合理值范围(最高优先级) ===== + + /// + /// 合理值最小值 + /// + public float reasonableRangeMin = float.MinValue; + + /// + /// 合理值最大值 + /// + public float reasonableRangeMax = float.MaxValue; + + // ===== 无效值范围 ===== + + /// + /// 无效值最小值 + /// + public float invalidRangeMin = float.MinValue; + + /// + /// 无效值最大值 + /// + public float invalidRangeMax = float.MaxValue; + + // ===== 警告值范围 ===== + + /// + /// 警告值最小值 + /// + public float warningRangeMin = float.MinValue; + + /// + /// 警告值最大值 + /// + public float warningRangeMax = float.MaxValue; + + // ===== 字符串相关 ===== + + /// + /// 警告关键词列表(仅字符串类型使用) + /// + public List warningKeywords = new List(); + + // ===== 便捷判断方法 ===== + + /// + /// 将值转为 float 进行比较(bool→0/1, 数值→直接转, 字符串→长度) + /// + public static float ToComparableFloat(object value) + { + if (value == null) return float.NaN; + + switch (value) + { + case float f: return f; + case double d: return (float)d; + case int i: return i; + case long l: return l; + case short s: return s; + case byte b: return b; + case uint ui: return ui; + case bool bVal: return bVal ? 1f : 0f; + case string str: return str.Length; + default: return float.NaN; + } + } + + /// + /// 判断值是否为空 + /// + public bool IsEmpty(object value) + { + if (skipEmptyCheck) return false; + + // 引用类型 null + if (value == null) return true; + + // 字符串 null/空 + if (value is string str) return string.IsNullOrEmpty(str); + + // 数值范围判断 + float f = ToComparableFloat(value); + if (float.IsNaN(f)) return false; // 无法判断的类型跳过 + + return f >= emptyRangeMin && f <= emptyRangeMax; + } + + /// + /// 判断值是否在合理范围内 + /// + public bool IsReasonable(object value) + { + float f = ToComparableFloat(value); + if (float.IsNaN(f)) return false; + return f >= reasonableRangeMin && f <= reasonableRangeMax; + } + + /// + /// 判断值是否在无效范围内 + /// + public bool IsInvalid(object value) + { + float f = ToComparableFloat(value); + if (float.IsNaN(f)) return false; + return f >= invalidRangeMin && f <= invalidRangeMax; + } + + /// + /// 判断值是否在警告范围内 + /// + public bool IsWarning(object value) + { + // 先检查关键词(字符串) + if (value is string str && warningKeywords.Count > 0) + { + foreach (var kw in warningKeywords) + { + if (str.Contains(kw, StringComparison.OrdinalIgnoreCase)) + return true; + } + } + + // 数值范围 + float f = ToComparableFloat(value); + if (float.IsNaN(f)) return false; + return f >= warningRangeMin && f <= warningRangeMax; + } + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeEntry.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeEntry.cs.meta new file mode 100644 index 0000000..c0a56c7 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/AvrgValueRangeEntry.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3e5066f03b4a03a4f8b2993f027f713b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/IAvrgMvvmConflictResolver.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/IAvrgMvvmConflictResolver.cs new file mode 100644 index 0000000..751fa61 --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/IAvrgMvvmConflictResolver.cs @@ -0,0 +1,18 @@ +namespace XericUI.ReflectionUIGenerator +{ + /// + /// 冲突解决器接口 + /// 用户可实现此接口注册自定义冲突解决逻辑 + /// + public interface IAvrgMvvmConflictResolver + { + /// + /// 尝试解决冲突 + /// + /// 发生冲突的绑定 + /// 当前配置的默认策略 + /// 返回解决后的值 + /// true 表示已解决,false 表示未处理(回退到默认策略) + bool TryResolve(AvrgMvvmBinding binding, ConflictResolutionStrategy defaultStrategy, out object resolvedValue); + } +} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/IAvrgMvvmConflictResolver.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/IAvrgMvvmConflictResolver.cs.meta new file mode 100644 index 0000000..eb5e3ec --- /dev/null +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/IAvrgMvvmConflictResolver.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c4e7c4a0ce16c64082ee641e03ce5d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiFieldTempEntry.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiFieldTempEntry.cs index 66f9b3e..7799eb1 100644 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiFieldTempEntry.cs +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiFieldTempEntry.cs @@ -1,21 +1,10 @@ using System; using System.Collections.Generic; -using System.Reflection; using UnityEngine; using UnityEngine.Pool; namespace XericUI.ReflectionUIGenerator { - /// - /// UI 字段标记提供者接口 - /// 所有 Tag 增强的 UI 组件需实现此接口,便于 Entry 在绑定阶段自动匹配 - /// - public interface IXuiavrgFieldTagProvider - { - XuiavrgFieldTag EntryTag { get; } - int TagIndex { get; } - } - /// /// 字段 Entry 接口 /// 数据对象字段与 UI 组件之间的中间缓存层 @@ -49,12 +38,15 @@ namespace XericUI.ReflectionUIGenerator /// 生成的 UI 实例引用 GameObject UIInstance { get; } - /// 绑定的预制体组 - XuiavrgPropertyGroupScriptableObject BoundPrefabGroup { get; set; } + /// 绑定的预制体引用 + GameObject BoundPrefab { get; set; } /// 覆盖的预制体组名称 string PrefabOverrideName { get; set; } + /// 关联的 MVVM 绑定列表 + IReadOnlyList Bindings { get; } + // === 生命周期 === /// Entry 创建时调用 @@ -69,39 +61,22 @@ namespace XericUI.ReflectionUIGenerator /// 销毁时调用 void OnEntryDestroy(); - // === 数据访问 === + // === 绑定管理 === - /// 获取指定成员的数据值 - object GetDataValue(string memberName); - - /// 设置指定成员的数据值 - void SetDataValue(string memberName, object value); - - /// 获取 Entry 下所有绑定成员的名称 - IEnumerable GetMemberNames(); + /// 添加 MVVM 绑定到此 Entry + void AddBinding(AvrgMvvmBinding binding, AvrgMvvmScheduler scheduler); // === UI 绑定 === /// /// 绑定 UI 组件 /// - /// UI 预制体实例 - /// - /// 组件缓存列表。传入 null 时会自动收集 uiInstance 上所有 Component。 - /// 父 Entry 可将已收集的缓存传递给子 Entry 以提升性能 - /// void BindUIComponent(GameObject uiInstance, List componentCache); } /// /// 字段临时 Entry 实现类 - /// 作为数据对象字段和 UI 组件之间的中间缓存层 - /// - /// 支持: - /// - 多字段合并为一个 Entry(通过 XuiavrgFieldEntryGroupAttribute) - /// - 嵌套 Entry(父 Entry 包含子 Entry) - /// - 对象池复用(通过静态 ObjectPool) - /// - MVVM 双向绑定的生命周期 + /// 作为数据对象和 UI 组件之间的中间缓存层 /// public class XuiFieldTempEntry : IFieldEntry { @@ -130,73 +105,19 @@ namespace XericUI.ReflectionUIGenerator #endregion - #region 内部绑定数据结构 - - /// - /// 成员反射绑定信息 - /// - internal class MemberBinding - { - /// 绑定的数据目标对象 - public object valueTarget; - - /// 是否为属性(false 为字段) - public bool isProperty; - - /// 属性信息 - public PropertyInfo propertyInfo; - - /// 字段信息 - public FieldInfo fieldInfo; - - /// 功能标记 - public XuiavrgFieldTag tag; - - /// 标记索引 - public int tagIndex; - - /// - /// 从绑定目标获取值 - /// - public object GetValue() - { - if (valueTarget == null) return null; - return isProperty - ? propertyInfo.GetValue(valueTarget) - : fieldInfo.GetValue(valueTarget); - } - - /// - /// 设置值到绑定目标 - /// - public void SetValue(object value) - { - if (valueTarget == null) return; - if (isProperty) - { - if (propertyInfo.CanWrite) - propertyInfo.SetValue(valueTarget, value); - } - else - { - fieldInfo.SetValue(valueTarget, value); - } - } - } - - #endregion - #region 字段 private string _entryName; private string _entryPath; private IFieldEntry _parent; private List _children = new List(); - private Dictionary _bindings = new Dictionary(); + private List _bindings = new List(); + private AvrgMvvmScheduler _scheduler; private GameObject _uiInstance; - private XuiavrgPropertyGroupScriptableObject _boundPrefabGroup; + private GameObject _boundPrefab; private string _prefabOverrideName; private int _fieldOrder; + private bool _uiBound; // 防止重复绑定 #endregion @@ -237,10 +158,10 @@ namespace XericUI.ReflectionUIGenerator public GameObject UIInstance => _uiInstance; - public XuiavrgPropertyGroupScriptableObject BoundPrefabGroup + public GameObject BoundPrefab { - get => _boundPrefabGroup; - set => _boundPrefabGroup = value; + get => _boundPrefab; + set => _boundPrefab = value; } public string PrefabOverrideName @@ -249,6 +170,8 @@ namespace XericUI.ReflectionUIGenerator set => _prefabOverrideName = value; } + public IReadOnlyList Bindings => _bindings; + #endregion #region 初始化方法 @@ -263,6 +186,7 @@ namespace XericUI.ReflectionUIGenerator Tag = tag; TagIndex = tagIndex; _fieldOrder = 0; + _uiBound = false; } /// @@ -271,7 +195,7 @@ namespace XericUI.ReflectionUIGenerator public void SetParent(IFieldEntry parent) { _parent = parent; - _entryPath = null; // 重置路径,下次访问重新计算 + _entryPath = null; } /// @@ -285,98 +209,19 @@ namespace XericUI.ReflectionUIGenerator } /// - /// 注册成员绑定 + /// 添加 MVVM 绑定到此 Entry /// - /// 成员名称 - /// 反射信息 - /// 数据目标对象 - /// 功能标记 - /// 标记索引 - public void RegisterBinding(string memberName, MemberInfo memberInfo, object target, - XuiavrgFieldTag tag = XuiavrgFieldTag.None, int tagIndex = 0) + public void AddBinding(AvrgMvvmBinding binding, AvrgMvvmScheduler scheduler) { - MemberBinding binding = new MemberBinding - { - valueTarget = target, - tag = tag, - tagIndex = tagIndex - }; + if (binding == null) return; + _bindings.Add(binding); + _scheduler = scheduler; - switch (memberInfo) + if (Tag == XuiavrgFieldTag.None && binding.MetaData?.Tag != XuiavrgFieldTag.None) { - case PropertyInfo propInfo: - binding.isProperty = true; - binding.propertyInfo = propInfo; - break; - case FieldInfo fieldInfo: - binding.isProperty = false; - binding.fieldInfo = fieldInfo; - break; + Tag = binding.MetaData.Tag; + TagIndex = binding.MetaData.TagIndex; } - - _bindings[memberName] = binding; - - // 如果是第一个注册的绑定,设置主 Tag - if (Tag == XuiavrgFieldTag.None && tag != XuiavrgFieldTag.None) - { - Tag = tag; - TagIndex = tagIndex; - } - } - - #endregion - - #region 数据访问 - - public object GetDataValue(string memberName) - { - if (_bindings.TryGetValue(memberName, out var binding)) - return binding.GetValue(); - - // 查找子 Entry - foreach (var child in _children) - { - var value = child.GetDataValue(memberName); - if (value != null) return value; - } - - return null; - } - - public void SetDataValue(string memberName, object value) - { - if (_bindings.TryGetValue(memberName, out var binding)) - { - binding.SetValue(value); - return; - } - - // 查找子 Entry - foreach (var child in _children) - { - if (child.HasBinding(memberName)) - { - child.SetDataValue(memberName, value); - return; - } - } - } - - public IEnumerable GetMemberNames() => _bindings.Keys; - - /// - /// 检查是否包含指定成员绑定 - /// - public bool HasBinding(string memberName) => _bindings.ContainsKey(memberName); - - /// - /// 获取指定成员的标记 - /// - public XuiavrgFieldTag GetMemberTag(string memberName) - { - if (_bindings.TryGetValue(memberName, out var binding)) - return binding.tag; - return XuiavrgFieldTag.None; } #endregion @@ -386,7 +231,7 @@ namespace XericUI.ReflectionUIGenerator public void BindUIComponent(GameObject uiInstance, List componentCache) { _uiInstance = uiInstance; - if (_uiInstance == null) return; + if (_uiInstance == null || _uiBound) return; if (componentCache == null) { @@ -394,183 +239,131 @@ namespace XericUI.ReflectionUIGenerator _uiInstance.GetComponentsInChildren(true, componentCache); } - BindUIComponentWithCache(_uiInstance, componentCache); + // 遍历组件缓存,寻找 XUIAvrgMDValue 按 Tag 匹配 + foreach (var comp in componentCache) + { + if (comp is XUIAvrgMDValue mdValue) + { + if (mdValue.EntryTag == this.Tag && + (this.TagIndex == 0 || mdValue.TagIndex == this.TagIndex)) + { + BindComponentWithBinding(mdValue); + } + } + } // 子 Entry 复用组件缓存 foreach (var child in _children) { child.BindUIComponent(_uiInstance, componentCache); } - } - private void BindUIComponentWithCache(GameObject uiInstance, List componentCache) - { - // 遍历组件缓存,匹配 tag - var locators = new List(); - foreach (var comp in componentCache) - { - if (comp is XuiavrgLocator locator) - { - locators.Add(locator); - } - - if (comp is IXuiavrgFieldTagProvider tagProvider) - { - // 匹配 Tag 和 TagIndex - if (tagProvider.EntryTag == this.Tag && - (this.TagIndex == 0 || tagProvider.TagIndex == this.TagIndex)) - { - // 找到匹配的 UI 组件,建立绑定 - BuildComponentBinding(comp); - } - } - } - - // 如果没有 IXuiavrgFieldTagProvider,尝试通过 Locator 匹配 - if (locators.Count > 0) - { - BuildLocatorBindings(locators); - } + _uiBound = true; } /// - /// 通过标签提供者建立组件绑定 + /// 将 XUIAvrgMDValue 与 MVVM 绑定连接 /// - private void BuildComponentBinding(Component comp) + private void BindComponentWithBinding(XUIAvrgMDValue mdValue) { - // 根据组件类型和标记类型进行数据同步 - switch (comp) - { - case UnityEngine.UI.Text uiText when Tag == XuiavrgFieldTag.Label || Tag == XuiavrgFieldTag.Output: - uiText.text = GetFirstBindingValue()?.ToString() ?? ""; - break; - case UnityEngine.UI.Button _ when Tag == XuiavrgFieldTag.Button: - // 按钮绑定在 BindUIEvents 中处理 - break; - case UnityEngine.UI.Toggle uiToggle when Tag == XuiavrgFieldTag.Toggle: - if (GetFirstBindingValue() is bool boolVal) - uiToggle.isOn = boolVal; - break; - case UnityEngine.UI.Slider uiSlider when Tag == XuiavrgFieldTag.Slider: - if (GetFirstBindingValue() is float floatVal) - uiSlider.value = floatVal; - break; - case UnityEngine.UI.InputField uiInput when Tag == XuiavrgFieldTag.Input: - uiInput.text = GetFirstBindingValue()?.ToString() ?? ""; - break; - case UnityEngine.UI.Dropdown uiDropdown when Tag == XuiavrgFieldTag.Dropdown: - if (GetFirstBindingValue() is int intVal) - uiDropdown.value = intVal; - break; - } - } + if (_bindings.Count == 0) return; - /// - /// 通过 Locator 建立绑定 - /// - private void BuildLocatorBindings(List locators) - { - foreach (var locator in locators) + var primaryBinding = _bindings[0]; + + // 数据 → UI:订阅数据变更 + primaryBinding.OnUIDataChanged += value => UpdateUIComponent(mdValue, value); + + // UI → 数据:订阅 UI 交互事件 + // 根据组件类型决定 UI 事件 + if (mdValue.TryGetComponent(out UnityEngine.UI.Button uiButton)) { - foreach (var locProp in locator.Properties) + uiButton.onClick.AddListener(() => { - // 按 propertyName 匹配成员名 - if (_bindings.TryGetValue(locProp.propertyName, out var binding)) - { - // 从数据端同步到 UI - var dataValue = binding.GetValue(); - locProp.SetValue(dataValue); - } - } + primaryBinding.WriteFromUI(true); + _scheduler?.MarkUIDirty(primaryBinding); + }); } - } - /// - /// 获取第一个绑定的值 - /// - private object GetFirstBindingValue() - { - foreach (var kvp in _bindings) + if (mdValue.TryGetComponent(out UnityEngine.UI.Toggle uiToggle)) { - var val = kvp.Value.GetValue(); - if (val != null) return val; - } - return null; - } - - #endregion - - #region 事件绑定 - - /// - /// 绑定 UI 交互事件(双向绑定) - /// - public void BindUIEvents() - { - if (_uiInstance == null) return; - - var allComponents = new List(); - _uiInstance.GetComponentsInChildren(true, allComponents); - - foreach (var comp in allComponents) - { - if (comp is IXuiavrgFieldTagProvider tagProvider) + uiToggle.onValueChanged.AddListener(val => { - if (tagProvider.EntryTag != this.Tag) continue; - BindComponentEvents(comp); - } + primaryBinding.WriteFromUI(val); + _scheduler?.MarkUIDirty(primaryBinding); + }); } - // 子 Entry 事件绑定 - foreach (var child in _children) + if (mdValue.TryGetComponent(out UnityEngine.UI.Slider uiSlider)) { - child.BindUIEvents(); + uiSlider.onValueChanged.AddListener(val => + { + primaryBinding.WriteFromUI(val); + _scheduler?.MarkUIDirty(primaryBinding); + }); } + + if (mdValue.TryGetComponent(out UnityEngine.UI.InputField uiInput)) + { + uiInput.onEndEdit.AddListener(val => + { + primaryBinding.WriteFromUI(val); + _scheduler?.MarkUIDirty(primaryBinding); + }); + } + + if (mdValue.TryGetComponent(out UnityEngine.UI.Dropdown uiDropdown)) + { + uiDropdown.onValueChanged.AddListener(val => + { + primaryBinding.WriteFromUI(val); + _scheduler?.MarkUIDirty(primaryBinding); + }); + } + + // 广播元数据到 XUIAvrgMDValue(触发 IXUIAvrgMDValueStyle 子组件) + mdValue.InitializeWithMetaData(primaryBinding.MetaData); } - private void BindComponentEvents(Component comp) + /// + /// 更新 UI 组件的显示值(通过 XUIAvrgMDValue 所在 GameObject 上的具体组件) + /// + private static void UpdateUIComponent(XUIAvrgMDValue mdValue, object value) { - switch (comp) - { - case UnityEngine.UI.Button uiButton: - uiButton.onClick.AddListener(() => - { - SetFirstBindingValue(true); - }); - break; - case UnityEngine.UI.Toggle uiToggle: - uiToggle.onValueChanged.AddListener(val => - { - SetFirstBindingValue(val); - }); - break; - case UnityEngine.UI.Slider uiSlider: - uiSlider.onValueChanged.AddListener(val => - { - SetFirstBindingValue(val); - }); - break; - case UnityEngine.UI.InputField uiInput: - uiInput.onEndEdit.AddListener(val => - { - SetFirstBindingValue(val); - }); - break; - case UnityEngine.UI.Dropdown uiDropdown: - uiDropdown.onValueChanged.AddListener(val => - { - SetFirstBindingValue(val); - }); - break; - } - } + if (mdValue == null || value == null) return; - private void SetFirstBindingValue(object value) - { - foreach (var kvp in _bindings) + // Text + if (mdValue.TryGetComponent(out UnityEngine.UI.Text uiText)) { - kvp.Value.SetValue(value); - break; + 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; } } @@ -585,11 +378,16 @@ namespace XericUI.ReflectionUIGenerator _parent = null; _fieldOrder = 0; _prefabOverrideName = null; + _uiBound = false; } public void OnEntryBind() { - BindUIEvents(); + foreach (var binding in _bindings) + { + var value = binding.ReadFromDataSource(); + binding.NotifyUIUpdate(value); + } } public void OnEntryRelease() @@ -598,16 +396,20 @@ namespace XericUI.ReflectionUIGenerator _entryPath = null; _parent = null; _uiInstance = null; - _boundPrefabGroup = null; + _boundPrefab = null; _prefabOverrideName = null; Tag = XuiavrgFieldTag.None; TagIndex = 0; _fieldOrder = 0; + _uiBound = false; - // 清理绑定引用 + foreach (var binding in _bindings) + { + binding.ClearUIEvent(); + } _bindings.Clear(); + _scheduler = null; - // 递归释放子 Entry foreach (var child in _children) { Release(child); @@ -617,9 +419,14 @@ namespace XericUI.ReflectionUIGenerator public void OnEntryDestroy() { + foreach (var binding in _bindings) + { + binding.ClearUIEvent(); + } _bindings.Clear(); _children.Clear(); _uiInstance = null; + _scheduler = null; } #endregion diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrPropertyScriptableObject.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrPropertyScriptableObject.cs deleted file mode 100644 index 4aae244..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrPropertyScriptableObject.cs +++ /dev/null @@ -1,107 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -#if ODIN_INSPECTOR -using Sirenix.OdinInspector; -#endif -using UnityEngine; -using XericLibrary.Runtime.MacroLibrary; -using Object = UnityEngine.Object; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// [已弃用] 属性映射器。请使用 XuiavrgPropertyGroupScriptableObject 替代。 - /// 功能已整合到 PropertyGroup 中,此类保留仅用于向后兼容。 - /// - [Serializable] - [Obsolete("Use XuiavrgPropertyGroupScriptableObject instead. Property mapping is now integrated directly into the group.")] - public class XuiavrPropertyScriptableObject : ScriptableObject - { -#if ODIN_INSPECTOR - [LabelText("属性类型")] - [ValueDropdown("AssemblyValueTypeNames")] -#endif - public string valueType; -#if ODIN_INSPECTOR - [LabelText("属性名称")] -#endif - public string propertyName; -#if ODIN_INSPECTOR - [LabelText("界面预制体")] -#endif - public GameObject prefab; - - /// - /// 属性类型 - /// - public System.Type ValueType - { - get - { - if (_valueType != null) - return _valueType; - return _valueType = Type.GetType(valueType); - } - } - private System.Type _valueType; - -#if ODIN_INSPECTOR // 属性类型名称枚举 - private IEnumerable AssemblyValueTypeNames - { - get - { - if (_assemblyValueTypeName != null) - return _assemblyValueTypeName; - _assemblyValueTypeName = new ValueDropdownList(); - - // foreach (var typeName in MacroReflection.GetAllAssignableTypeInAppDomainAssembly( - // typeof(float), - // typeof(string), - // typeof(bool), - // typeof(Enum), - // typeof(int) - // ).Select(a => a.FullName)) - // { - // _assemblyValueTypeName.Add(typeName); - // } - - _assemblyValueTypeName.AddRange(AllValueType.Select(a => new ValueDropdownItem(a.Name, a.FullName))); - return _assemblyValueTypeName; - } - } - private static ValueDropdownList _assemblyValueTypeName = null; -#endif - - /// - /// 直接定义所有需要检查的类型 - /// - public static IEnumerable AllValueType - { - get - { - yield return typeof(Enum); - yield return typeof(decimal); - yield return typeof(double); - yield return typeof(float); - yield return typeof(ulong); - yield return typeof(long); - yield return typeof(int); - yield return typeof(uint); - yield return typeof(short); - yield return typeof(ushort); - yield return typeof(byte); - yield return typeof(sbyte); - yield return typeof(string); - yield return typeof(char); - yield return typeof(bool); - } - } - - - - - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrPropertyScriptableObject.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrPropertyScriptableObject.cs.meta deleted file mode 100644 index fbe1215..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrPropertyScriptableObject.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: be0acbdac8e549578dcccf8c076a5433 -timeCreated: 1776998687 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgExampleTestData.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgExampleTestData.cs deleted file mode 100644 index ca1a82b..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgExampleTestData.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 测试用枚举 - 用于下拉选择测试 - /// - public enum XuiavrgTestQuality - { - Low, - Medium, - High, - Epic, - Legendary - } - - /// - /// 反射界面生成器测试数据类 - /// 涵盖新版 Entry 体系的所有特性:编组、排序、预制体覆盖、多种字段类型 - /// - /// 使用方式: - /// 1. 在 Hierarchy 右键 -> Xeric UI -> Create Reflection UI Example 创建示例节点 - /// 2. 创建 XuiavrgPropertyGroupScriptableObject 并设置 groupName 和 supportedTags - /// 3. 将编组对应的预制体拖入 - /// 4. 调用 manager.BuildEntries(new XuiavrgExampleTestData()) 构建 Entry - /// 5. 调用 manager.GenerateUI() 生成界面 - /// 或直接使用兼容方法: manager.SchemaHydration(new XuiavrgExampleTestData()) - /// - [Serializable] - public class XuiavrgExampleTestData - { - // ==================== 基础数值类型 ==================== - - [XuiavrgFieldOrder(1)] - [XuiavrgNumberCheck(0, 100)] - public int intValue = 42; - - [XuiavrgFieldOrder(2)] - [XuiavrgNumberCheck(0f, 999f)] - public float floatValue = 3.14f; - - [XuiavrgFieldOrder(3)] - public double doubleValue = 123.456; - - [XuiavrgFieldOrder(4)] - [XuiavrgNumberCheck(0, 999999)] - public long longValue = 100000; - - // ==================== 多字段编组示例 ==================== - // 以下三个字段合并为一个 Entry,共享一个 UI 组件实例 - // UI 组件上需要有 Label、Context、Unit 对应的 IXuiavrgFieldTagProvider - - [XuiavrgFieldEntryGroup("speed", XuiavrgFieldTag.Label)] - [XuiavrgFieldOrder(5)] - public string speedLabel = "速度"; - - [XuiavrgFieldEntryGroup("speed", XuiavrgFieldTag.Context)] - [XuiavrgNumberCheck(-999f, 999f)] - public float speedValue = 0.1f; - - [XuiavrgFieldEntryGroup("speed", XuiavrgFieldTag.Unit)] - public string speedUnit = "m/s"; - - // 第二个编组示例 - 健康值 - [XuiavrgFieldEntryGroup("health", XuiavrgFieldTag.Label)] - [XuiavrgFieldOrder(6)] - public string healthLabel = "生命值"; - - [XuiavrgFieldEntryGroup("health", XuiavrgFieldTag.Context)] - [XuiavrgNumberCheck(0, 9999)] - public float healthValue = 100f; - - [XuiavrgFieldEntryGroup("health", XuiavrgFieldTag.Unit)] - public string healthUnit = "HP"; - - // ==================== 字符串类型 ==================== - - [XuiavrgFieldOrder(7)] - [XuiavrgRequired] - [XuiavrgStringCheck(minLength: 2, maxLength: 16)] - public string playerName = "DefaultName"; - - [XuiavrgFieldOrder(8)] - [XuiavrgNotEmpty] - [XuiavrgStringCheck(onlyEmail: false)] - public string emailAddress = "example@mail.com"; - - [XuiavrgFieldOrder(9)] - [XuiavrgFieldPrefabOverride("MultilineTextArea")] - [XuiavrgStringCheck(maxLength: 500)] - public string biography = "这是一段个人简介..."; - - // ==================== 布尔类型 ==================== - - [XuiavrgFieldOrder(10)] - public bool enableFeature = true; - - [XuiavrgFieldOrder(11)] - public bool advancedMode = false; - - // ==================== 枚举类型 ==================== - - [XuiavrgFieldOrder(12)] - public XuiavrgTestQuality quality = XuiavrgTestQuality.High; - - // ==================== 隐藏属性 ==================== - - [XuiavrgHide(true)] - public string hiddenField = "此字段被隐藏,不会生成 Entry"; - - // ==================== 不同尺寸 ==================== - - [XuiavrgFieldOrder(13)] - [XuiavrgAttribute("小号", "size_small", null, size: XuiavrgSize.Small)] - public string smallSizeField = "小尺寸"; - - [XuiavrgFieldOrder(14)] - [XuiavrgAttribute("大号", "size_large", null, size: XuiavrgSize.Large)] - public string largeSizeField = "大尺寸"; - - // ==================== 集合类型(兼容旧标记) ==================== - - [XuiavrgFieldOrder(15)] - [XuiavrgCollectionCheck(1, 10, true)] - public List tags = new List { "Unity", "C#", "UI" }; - - // ==================== 按钮触发器 ==================== - - [XuiavrgFieldOrder(16)] - public bool submitTrigger = false; - } -} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgExampleTestData.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgExampleTestData.cs.meta deleted file mode 100644 index b815d6e..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgExampleTestData.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 211cf8030617ad143ba1f4f68f544658 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorManager.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorManager.cs index 67a31d3..a922d06 100644 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorManager.cs +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorManager.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Reflection; using UnityEngine; using UnityEngine.Pool; using Object = UnityEngine.Object; @@ -9,41 +7,22 @@ using Object = UnityEngine.Object; namespace XericUI.ReflectionUIGenerator { /// - /// 节点下生成反射属性管理器 + /// 节点下生成反射属性管理器 — Metadata-Driven View Model (MDVM 元数据驱动的视图数据模型) + /// 负责:构建 Entry → 创建组容器 → 生成 UI → 绑定数据 /// - /// - /// 管理器需要挂载在 Unity 节点中,管理器中指定的属性承载节点将用于生成属性内容。 - /// - /// 新的生命周期流程: - /// 1. BuildEntries(object payload) — 传入数据对象,构建 Entry 列表 - /// 2. OnPostBuildEntries(entries) — 虚方法,可重写添加额外 Entry - /// 3. 自动按 FieldOrder 排序 - /// 4. GenerateUI() — 根据 Entry 列表生成 UI 实例并绑定 - /// - /// 对象池说明: - /// - Entry 通过 XuiFieldTempEntry.Pool 静态池管理 - /// - UI 预制体通过内部 PropertyGroupPools 字典管理,以 PropertyGroupScriptableObject 为键 - /// public class XuiavrgInspectorManager : MonoBehaviour { #region 对象池 - /// - /// UI 预制体对象池,以 PropertyGroupScriptableObject 为键 - /// - private Dictionary> _propertyGroupPools - = new Dictionary>(); + private Dictionary> _prefabPools + = new Dictionary>(); - /// - /// 从指定 PropertyGroup 的预制体池获取 UI 实例 - /// - public GameObject PropertyGroupPoolGet(XuiavrgPropertyGroupScriptableObject group) + public GameObject PrefabPoolGet(GameObject prefab) { - if (group == null) return null; + if (prefab == null) return null; - if (!_propertyGroupPools.TryGetValue(group, out var pool)) + if (!_prefabPools.TryGetValue(prefab, out var pool)) { - var prefab = group.prefab; pool = new ObjectPool( createFunc: () => prefab != null @@ -59,19 +38,16 @@ namespace XericUI.ReflectionUIGenerator defaultCapacity: 4, maxSize: 64 ); - _propertyGroupPools[group] = pool; + _prefabPools[prefab] = pool; } return pool.Get(); } - /// - /// 将 UI 实例归还到指定 PropertyGroup 的预制体池 - /// - public void PropertyGroupPoolRelease(XuiavrgPropertyGroupScriptableObject group, GameObject instance) + public void PrefabPoolRelease(GameObject prefab, GameObject instance) { - if (group == null || instance == null) return; - if (_propertyGroupPools.TryGetValue(group, out var pool)) + if (prefab == null || instance == null) return; + if (_prefabPools.TryGetValue(prefab, out var pool)) { pool.Release(instance); } @@ -81,219 +57,109 @@ namespace XericUI.ReflectionUIGenerator } } - /// - /// 清空所有预制体对象池 - /// - public void ClearAllPropertyGroupPools() + public void ClearAllPrefabPools() { - foreach (var pool in _propertyGroupPools.Values) + foreach (var pool in _prefabPools.Values) { pool.Clear(); } - _propertyGroupPools.Clear(); + _prefabPools.Clear(); } #endregion #region 字段 - [SerializeField] - [Tooltip("当前使用的 UI 主题名称。切换主题会立即销毁所有已生成 UI 和对象池,需重新 BuildEntries + GenerateUI")] - private string _currentTheme = XuiavrgPropertyGroupScriptableObject.DefaultThemeName; - - /// - /// 当前 UI 主题名称 - /// - public string CurrentTheme => _currentTheme; - - /// - /// 当前活跃的 Entry 列表 - /// public List CurrentEntries { get; private set; } = new List(); + public AvrgMvvmBindingCollection CurrentBindingCollection { get; private set; } + public AvrgMemberMetaDataSet CurrentMetaDataSet { get; private set; } /// - /// 当前传入的数据负载 + /// 当前活跃的组容器字典 /// - public object CurrentPayload { get; private set; } + private Dictionary _activeGroups = new Dictionary(); - /// - /// 切换 UI 主题。会立即销毁所有已生成 UI 和对象池。 - /// 调用后需重新 BuildEntries + GenerateUI。 - /// - /// 新的主题名称 - public void SetTheme(string themeName) - { - if (_currentTheme == themeName) return; - - _currentTheme = string.IsNullOrEmpty(themeName) - ? XuiavrgPropertyGroupScriptableObject.DefaultThemeName - : themeName; - - // 销毁所有已生成 UI 和对象池 - ClearAll(); - } + [Tooltip("类型-预制体映射配置 ScriptableObject")] + public AvrgTypePrefabMappingConfig typePrefabMappingConfig; #endregion #region 核心生命周期 - /// - /// 传入对象实例,构建 Entry 列表 - /// - /// 数据端对象 - public void BuildEntries(object payload) + public void BuildFromMetaDataSet(AvrgMemberMetaDataSet metaDataSet, + AvrgMvvmBindingCollection bindingCollection = null) { - // 清空之前的 Entry ClearCurrentEntries(); - CurrentPayload = payload; + CurrentMetaDataSet = metaDataSet; + CurrentBindingCollection = bindingCollection; - if (payload == null) + if (metaDataSet == null) { - Debug.LogError("[Xuiavrg] 未指定有效负载,无法构建 Entry"); + Debug.LogError("[Xuiavrg] 未指定元数据集,无法构建 Entry"); return; } - var type = payload.GetType(); - var memberInfos = type.GetRuntimeMemberInfo().ToList(); - - // 用于合并编组 Entry 的临时字典 - var groupEntries = new Dictionary(); - - foreach (var memberInfo in memberInfos) + foreach (var metaData in metaDataSet.MetaDataList) { - // 检查隐藏标记 - var hideAttr = memberInfo.GetCustomAttribute(); - if (hideAttr != null && hideAttr.Hide) + if (metaData.Hide) continue; - // 检查编组标记 - var groupAttr = memberInfo.GetCustomAttribute(); + var entry = XuiFieldTempEntry.Get(); + entry.Initialize( + string.IsNullOrEmpty(metaData.GroupPath) ? metaData.MemberName : metaData.GroupPath, + metaData.Tag, + metaData.TagIndex); - if (groupAttr != null) + if (!string.IsNullOrEmpty(metaData.PrefabOverrideName)) + entry.PrefabOverrideName = metaData.PrefabOverrideName; + + if (bindingCollection != null) { - // 编组字段:合并到同名 Entry - if (!groupEntries.TryGetValue(groupAttr.GroupPath, out var groupEntry)) + var matchedBindings = bindingCollection.FindByMemberName(metaData.MemberName); + if (matchedBindings != null) { - groupEntry = XuiFieldTempEntry.Get(); - groupEntry.Initialize(groupAttr.GroupPath); - groupEntries[groupAttr.GroupPath] = groupEntry; - CurrentEntries.Add(groupEntry); - } - - groupEntry.RegisterBinding( - memberInfo.Name, memberInfo, payload, - groupAttr.Tag, groupAttr.TagIndex); - } - else - { - // 普通字段:创建独立 Entry - var entry = XuiFieldTempEntry.Get(); - entry.Initialize(memberInfo.Name); - entry.RegisterBinding(memberInfo.Name, memberInfo, payload); - - // 检查预制体覆盖 - var overrideAttr = memberInfo.GetCustomAttribute(); - if (overrideAttr != null) - { - entry.PrefabOverrideName = overrideAttr.PrefabGroupName; - } - - CurrentEntries.Add(entry); - } - - // 应用排序标记 - var orderAttr = memberInfo.GetCustomAttribute(); - if (orderAttr != null) - { - // 找到该成员对应的 Entry(可能是编组 Entry) - if (groupAttr != null && groupEntries.TryGetValue(groupAttr.GroupPath, out var gEntry)) - { - if (gEntry.FieldOrder == 0 || orderAttr.Order < gEntry.FieldOrder) - gEntry.FieldOrder = orderAttr.Order; + entry.AddBinding(matchedBindings, null); } else { - // 普通字段的排序应用到其 Entry - var normalEntry = CurrentEntries.LastOrDefault(); - if (normalEntry != null) - normalEntry.FieldOrder = orderAttr.Order; + foreach (var binding in bindingCollection) + { + entry.AddBinding(binding, null); + break; + } } } - // 应用旧的 IXuiavrgAttribute(兼容) - ApplyLegacyAttributes(memberInfo, payload, groupAttr, groupEntries); + entry.FieldOrder = metaData.FieldOrder; + CurrentEntries.Add(entry); } - // 调用后期回调 OnPostBuildEntries(CurrentEntries); - - // 排序 SortEntries(); } - /// - /// 应用旧的 IXuiavrgAttribute 体系中的属性(兼容保留) - /// - private void ApplyLegacyAttributes( - MemberInfo memberInfo, object payload, - XuiavrgFieldEntryGroupAttribute groupAttr, - Dictionary groupEntries) + public void SetBindingCollection(AvrgMvvmBindingCollection bindingCollection) { - // 处理旧的 XuiavrgAttribute(设置 labelName, inspectorOrder 等) - var oldAttr = memberInfo.GetCustomAttribute(); - if (oldAttr != null) + CurrentBindingCollection = bindingCollection; + + if (CurrentEntries != null && bindingCollection != null) { - // 创建临时的 InspectorProperty 来接收绑定 - var tempProp = new XuiavrgInspectorProperty(); - tempProp.RegsiterDataValue(memberInfo, payload); - oldAttr.BindProperty(tempProp); - - // 将相关属性同步到 Entry - IFieldEntry targetEntry; - if (groupAttr != null && groupEntries.TryGetValue(groupAttr.GroupPath, out var gEntry)) - targetEntry = gEntry; - else - targetEntry = CurrentEntries.LastOrDefault(e => !e.IsGroupEntry || e == null) ?? CurrentEntries.LastOrDefault(); - - if (targetEntry != null) + foreach (var entry in CurrentEntries) { - if (tempProp.inspectorOrder != 0 && targetEntry.FieldOrder == 0) - targetEntry.FieldOrder = tempProp.inspectorOrder; - - targetEntry.PrefabOverrideName ??= tempProp.uiPropertyName; + if (entry is XuiFieldTempEntry tempEntry) + { + var matched = bindingCollection.FindByMemberName(entry.EntryName); + if (matched != null) + tempEntry.AddBinding(matched, null); + } } } - - // 处理其他旧的验证 Attribute - foreach (var attr in memberInfo.GetCustomAttributes().OfType()) - { - if (attr is XuiavrgAttribute) continue; // 已处理 - if (attr is XuiavrgHideAttribute) continue; // 已处理 - if (attr is XuiavrgFieldEntryGroupAttribute) continue; // 已处理 - if (attr is XuiavrgFieldOrderAttribute) continue; // 已处理 - if (attr is XuiavrgFieldPrefabOverrideAttribute) continue; // 已处理 - - // 其他旧 Attribute(Required, StringCheck, NumberCheck 等) - var tempProp = new XuiavrgInspectorProperty(); - tempProp.RegsiterDataValue(memberInfo, payload); - attr.BindProperty(tempProp); - // 这些验证信息目前由 InspectorProperty 保留,后续可迁移到 Entry - } } - /// - /// 生成后期回调(可重写) - /// 在此方法中可以手动添加额外的 Entry(如 Preview 结构、不在数据对象中的内容) - /// - /// 当前 Entry 列表 protected virtual void OnPostBuildEntries(List entries) { - // 默认无操作,子类可重写 } - /// - /// 对 Entry 列表按 FieldOrder 排序 - /// private void SortEntries() { CurrentEntries.Sort((a, b) => @@ -304,106 +170,181 @@ namespace XericUI.ReflectionUIGenerator }); } + // ============ 三阶段 GenerateUI ============ + /// - /// 根据 Entry 列表生成 UI + /// 生成 UI — 三阶段流程 + /// Phase A: 按 GroupPath 分组 + /// Phase B: 创建 XuiAvrgMDGroup 组容器 + /// Phase C: 在组容器内生成 UI 组件并绑定 /// public void GenerateUI() { if (CurrentEntries == null || CurrentEntries.Count == 0) { - Debug.LogWarning("[Xuiavrg] 没有可生成的 Entry,请先调用 BuildEntries"); + Debug.LogWarning("[Xuiavrg] 没有可生成的 Entry,请先调用 BuildFromMetaDataSet"); return; } + // Phase A: 收集并分组 + var groupedEntries = new Dictionary>(); + var ungroupedEntries = new List(); + foreach (var entry in CurrentEntries) { - if (entry is not XuiFieldTempEntry tempEntry) continue; + string groupKey = !string.IsNullOrEmpty(entry.EntryName) + ? GetGroupPathFromEntry(entry) + : null; - // 查找匹配的 PropertyGroup - var group = ResolvePropertyGroup(entry); - if (group == null) + if (!string.IsNullOrEmpty(groupKey)) { - Debug.LogWarning($"[Xuiavrg] 未找到 Entry '{entry.EntryName}' 的匹配预制体组,跳过"); - continue; + if (!groupedEntries.ContainsKey(groupKey)) + groupedEntries[groupKey] = new List(); + groupedEntries[groupKey].Add(entry); } + else + { + ungroupedEntries.Add(entry); + } + } - entry.BoundPrefabGroup = group; + // Phase B: 创建组容器 + _activeGroups.Clear(); + foreach (var kvp in groupedEntries) + { + string groupPath = kvp.Key; + var entries = kvp.Value; - // 从对象池获取 UI 实例 - var uiInstance = PropertyGroupPoolGet(group); - if (uiInstance == null) continue; + // 检查是否已创建(相同 GroupPath 只创建一个容器) + if (_activeGroups.ContainsKey(groupPath)) continue; - // 设置父节点和命名 - uiInstance.name = entry.EntryName; - uiInstance.transform.SetParent(transform, false); + var groupGo = new GameObject($"Group [{groupPath}]", typeof(RectTransform)); + groupGo.transform.SetParent(transform, false); - // 绑定 UI 组件(首次绑定不传缓存,由 Entry 内部收集) - entry.BindUIComponent(uiInstance, null); + var rect = groupGo.GetComponent(); + rect.anchorMin = new Vector2(0, 1); + rect.anchorMax = new Vector2(1, 1); + rect.pivot = new Vector2(0.5f, 1); + rect.sizeDelta = new Vector2(0, 0); - // 绑定事件 - entry.OnEntryBind(); + var group = groupGo.AddComponent(); + int firstOrder = entries.Count > 0 ? entries[0].FieldOrder : 0; + group.Initialize(groupPath, firstOrder); + + _activeGroups[groupPath] = group; + } + + // Phase C: 生成 UI 组件 + foreach (var kvp in groupedEntries) + { + string groupPath = kvp.Key; + var entries = kvp.Value; + + if (!_activeGroups.TryGetValue(groupPath, out var group)) continue; + + foreach (var entry in entries) + { + GenerateSingleUI(entry, group.contentRoot); + } + } + + foreach (var entry in ungroupedEntries) + { + GenerateSingleUI(entry, transform); } } /// - /// 解析 Entry 对应的 PropertyGroup - /// 优先使用 PrefabOverrideName,否则按 Tag 匹配,所有查找在 currentTheme 下进行 + /// 生成单个 Entry 的 UI /// - private XuiavrgPropertyGroupScriptableObject ResolvePropertyGroup(IFieldEntry entry) + private void GenerateSingleUI(IFieldEntry entry, Transform parent) + { + if (entry is not XuiFieldTempEntry tempEntry) return; + + var prefab = ResolvePrefab(entry); + if (prefab == null) + { + Debug.LogWarning($"[Xuiavrg] 未找到 Entry '{entry.EntryName}' 的匹配预制体,跳过"); + return; + } + + entry.BoundPrefab = prefab; + var uiInstance = PrefabPoolGet(prefab); + + if (uiInstance == null) return; + + uiInstance.name = entry.EntryName; + uiInstance.transform.SetParent(parent, false); + + // 检查 XUIAvrgMDValue 是否存在 + if (uiInstance.GetComponentInChildren(true) == null) + { + Debug.LogError($"[Xuiavrg] 预制体 '{prefab.name}' 不符合生成要求:缺少 XUIAvrgMDValue 组件。请确保预制体根节点或其子节点包含 XUIAvrgMDValue 脚本。"); + PrefabPoolRelease(prefab, uiInstance); + return; + } + + entry.BindUIComponent(uiInstance, null); + entry.OnEntryBind(); + } + + /// + /// 从 Entry 提取 GroupPath(通过 EntryName 判断) + /// + private string GetGroupPathFromEntry(IFieldEntry entry) + { + // 通过 Bindings 中的元数据找到 GroupPath + if (entry.Bindings != null && entry.Bindings.Count > 0) + { + var metaData = entry.Bindings[0].MetaData; + if (metaData != null && !string.IsNullOrEmpty(metaData.GroupPath)) + return metaData.GroupPath; + } + return null; + } + + // ============ 预制体解析 ============ + + private GameObject ResolvePrefab(IFieldEntry entry) { - // 1. 优先检查 PrefabOverrideName(按主题 + UI 名称查找) if (!string.IsNullOrEmpty(entry.PrefabOverrideName)) { - var overrideGroup = XuiavrgPropertyGroupScriptableObject.FindGroupByUiName(_currentTheme, entry.PrefabOverrideName); - if (overrideGroup != null) return overrideGroup; - } - - // 2. 按 Tag 查找匹配的组(在当前主题下) - if (entry.Tag != XuiavrgFieldTag.None) - { - var tags = new List { entry.Tag }; - // 收集子 Entry 的 Tag(如果是编组 Entry) - if (entry.IsGroupEntry) + if (typePrefabMappingConfig != null) { - foreach (var child in entry.Children) - { - if (child.Tag != XuiavrgFieldTag.None && !tags.Contains(child.Tag)) - tags.Add(child.Tag); - } + var namedPrefab = typePrefabMappingConfig.FindPrefabByName(entry.PrefabOverrideName); + if (namedPrefab != null) return namedPrefab; } - - var matchedGroup = XuiavrgPropertyGroupScriptableObject.FindGroup(_currentTheme, tags.ToArray()); - if (matchedGroup != null) return matchedGroup; } - // 3. 回退到当前主题下的 "Default" UI 名称 - return XuiavrgPropertyGroupScriptableObject.FindGroupByUiName(_currentTheme, "Default"); + if (typePrefabMappingConfig != null) + { + var typeMatch = typePrefabMappingConfig.FindPrefabByBindings(entry.Bindings); + if (typeMatch != null) return typeMatch; + } + + return typePrefabMappingConfig != null ? typePrefabMappingConfig.defaultPrefab : null; } - /// - /// 清空当前界面和 Entry 列表 - /// + // ============ 清理 ============ + public void ClearAll() { ClearCurrentEntries(); - ClearAllPropertyGroupPools(); - CurrentPayload = null; + ClearAllPrefabPools(); + ClearGroups(); + CurrentMetaDataSet = null; + CurrentBindingCollection = null; } - /// - /// 清空当前 Entry 列表(归还到池中) - /// private void ClearCurrentEntries() { foreach (var entry in CurrentEntries) { - // 释放 UI 实例 - if (entry.UIInstance != null && entry.BoundPrefabGroup != null) + if (entry.UIInstance != null && entry.BoundPrefab != null) { - PropertyGroupPoolRelease(entry.BoundPrefabGroup, entry.UIInstance); + PrefabPoolRelease(entry.BoundPrefab, entry.UIInstance); } - // 归还 Entry 到池 if (entry is XuiFieldTempEntry tempEntry) { XuiFieldTempEntry.Release(tempEntry); @@ -412,29 +353,35 @@ namespace XericUI.ReflectionUIGenerator CurrentEntries.Clear(); } - #endregion - - #region 生命周期 + private void ClearGroups() + { + foreach (var kvp in _activeGroups) + { + if (kvp.Value != null && kvp.Value.gameObject != null) + { + Destroy(kvp.Value.gameObject); + } + } + _activeGroups.Clear(); + } private void OnDestroy() { ClearAll(); } - #endregion + // ============ 兼容旧 API ============ - #region 兼容旧 API - - /// - /// [已弃用] 请使用 BuildEntries + GenerateUI 替代 - /// - [Obsolete("Use BuildEntries + GenerateUI instead", false)] + [Obsolete("Use BuildFromMetaDataSet + GenerateUI instead", false)] public void SchemaHydration(object payload) { - BuildEntries(payload); + var metaDataSet = XuiavrgUtilities.ExtractAvrgMemberMetaData(payload); + var bindingCollection = metaDataSet.BuildMDVM(payload); + BuildFromMetaDataSet(metaDataSet, bindingCollection); GenerateUI(); } - + #endregion + } } diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorProperty.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorProperty.cs deleted file mode 100644 index 8623c00..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorProperty.cs +++ /dev/null @@ -1,171 +0,0 @@ -using System; -using System.Reflection; -using UnityEngine; -using UnityEngine.Serialization; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 反射属性中间层 - /// - [Serializable] - public class XuiavrgInspectorProperty - { - #region 字段属性 - - public object valueTarget; - public bool isProperty; - public PropertyInfo propertyInfo; - public FieldInfo fieldInfo; - - // 基本信息 - public string labelName; // 字段名称 - public string uiPropertyName; // ui属性名称 - public string unitLabelName; // 单位名称 - public bool required; // 项目必填 - public bool notEmpty; // 不可为空 - public int inspectorOrder; // 显示在检查器上的顺序 - public string groupName; // 编组名称 - public string onTriggerCall; // 值变更时触发的反射 - public XuiavrgSize argumentSize; // 属性尺寸 - public bool hideInInspector; // 在检查器上隐藏显示 - - // 字符串检查 - public int stringMinLength; // 最小长度 - public int stringMaxLength = int.MaxValue; // 最大长度 - public string stringRegexPattern = ""; // 正则检查 - public bool stringOnlyLetters; // 允许字符 - public bool stringOnlyNumber; // 允许数字 - public bool stringOnlyEmail = true; // 允许邮箱 - public bool stringOnlyPhone = true; // 允许电话号码 - public bool stringOnlyUrl = true; // 允许网址 - - // 数值检查 - public float numberMinValue; // 最小值 - public float numberMaxValue; // 最大值 - - // 集合检查 - public int collectionMinCount; // 集合的最小尺寸 - public int collectionMaxCount = int.MaxValue; // 集合的最大尺寸 - public bool collectionNotEmpty; // 集合不可为空 - - #endregion - - #region 检查方法 - - /// - /// 绑定到数据端 - /// - /// - /// - public void RegsiterDataValue(MemberInfo memberInfo, object target) - { - switch (memberInfo) - { - case PropertyInfo propertyInfo: - RegsiterDataValue(propertyInfo, target); - break; - case FieldInfo fieldInfo: - RegsiterDataValue(fieldInfo, target); - break; - } - } - - /// - /// 绑定到数据端 - /// - /// - /// - public virtual void RegsiterDataValue(PropertyInfo memberInfo, object target) - { - valueTarget = target; - isProperty = true; - propertyInfo = memberInfo; - } - - /// - /// 绑定到数据端 - /// - /// - /// - public virtual void RegsiterDataValue(FieldInfo memberInfo, object target) - { - valueTarget = target; - isProperty = false; - fieldInfo = memberInfo; - } - - /// - /// 绑定到界面端 - /// - public virtual void RegsiterUIElement() - { - - } - - - /// - /// 数据端值发生变更 - /// - public void CallDataValueChange() - { - - } - - /// - /// 界面端数据发生变更 - /// - public void CallUIValueChange() - { - - } - - /// - /// 从数据端获取值 - /// - /// - public object GetValueFromTargetMember() - { - if (isProperty) - return propertyInfo.GetValue(valueTarget); - else - return fieldInfo.GetValue(valueTarget); - } - - /// - /// 设置值到数据端 - /// - /// - public void SetValueToTargetMember(object value) - { - if (isProperty) - propertyInfo.SetValue(valueTarget, value); - else - fieldInfo.SetValue(valueTarget, value); - } - - /// - /// 从界面端获取值 - /// - /// - public object GetValueFormUIElement() - { - return null; - } - - /// - /// 设置值到界面端 - /// - /// - public void SetValueToUIElement(object value) - { - - } - - public void Clear() - { - } - - #endregion - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorProperty.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorProperty.cs.meta deleted file mode 100644 index a6c88e1..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgInspectorProperty.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 5d82ae110adb48b397c09939694cc3b2 -timeCreated: 1777011022 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgLocator.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgLocator.cs deleted file mode 100644 index 4cd62d9..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgLocator.cs +++ /dev/null @@ -1,309 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -#if ODIN_INSPECTOR -using Sirenix.OdinInspector; -#endif -using UnityEngine; -using UnityEngine.Serialization; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// uiԶλUIĸڵϣ趨ýĿ꼴 - /// - public class XuiavrgLocator : MonoBehaviour - { - public delegate void AnyTrigger(LocatorProperty locator, object value, int selectStart, int selectEnd); - -#if ODIN_INSPECTOR - [LabelText("ӳ")] -#endif - public List Properties; - - [Serializable] - public class LocatorProperty - { -#if ODIN_INSPECTOR - [LabelText("ʶ")] -#endif - [Tooltip("ʹ name, label, unit, sign, input, output, button, slider, edit, select ȼ׶ƽб")] - public string propertyName; -#if ODIN_INSPECTOR - [LabelText("")] -#endif - public UIElementClassify classify; -#if dUI_TextMeshPro -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.TMPText)] -#endif - public TMPro.TMP_Text TMPText; -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.TMPInputField)] -#endif - public TMPro.TMP_InputField TMPInputField; -#endif -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.UIText)] -#endif - public UnityEngine.UI.Text UIText; -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.UIInputField)] -#endif - public UnityEngine.UI.InputField UIInputField; -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.UIToggle)] -#endif - public UnityEngine.UI.Toggle UIToggle; -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.UIButton)] -#endif - public UnityEngine.UI.Button UIButton; -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.UISlider)] -#endif - public UnityEngine.UI.Slider UISlider; -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.UIDropdown)] -#endif - public UnityEngine.UI.Dropdown UIDropdown; -#if dUI_TextMeshPro -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.TMPInputField)] -#endif - public TMPInputTriggerEvent TMPInputTriggerOnEventType; -#endif -#if ODIN_INSPECTOR - [ShowIf("classify", UIElementClassify.UIInputField)] -#endif - public UIInputTriggerEvent UIInputTriggerOnEventType; - - /// - /// ֵ֧ - /// - public enum UIElementClassify - { - None, -#if dUI_TextMeshPro - TMPText, - TMPInputField, -#endif - UIText, - UIInputField, - UIToggle, - UIButton, - UISlider, - UIDropdown, - } - - [Flags] - public enum TMPInputTriggerEvent - { - none, - onEndEdit = 1 << 1, - onSubmit = 1 << 2, - onSelect = 1 << 3, - onDeselect = 1 << 4, - onTextSelection = 1 << 5, - onEndTextSelection = 1 << 6, - onValueChanged = 1 << 7, - onTouchScreenKeyboardStatusChanged = 1 << 8, - } - - public enum UIInputTriggerEvent - { - none, - onEndEdit, - onSubmit, - onValueChanged, - } - - /// - /// ȡֵ - /// - /// - /// - public object GetValue() - { - switch (classify) - { -#if dUI_TextMeshPro - case UIElementClassify.TMPText: - return TMPText.text; - case UIElementClassify.TMPInputField: - return TMPInputField.text; -#endif - case UIElementClassify.UIText: - return UIText.text; - case UIElementClassify.UIInputField: - return UIInputField.text; - case UIElementClassify.UIToggle: - return UIToggle.isOn; - case UIElementClassify.UIButton: - return false; - case UIElementClassify.UISlider: - return UISlider.value; - case UIElementClassify.UIDropdown: - return UIDropdown.value; - default: - return null; - } - } - - /// - /// ֵ - /// - /// - /// - public void SetValue(object value) - { - switch (classify) - { -#if dUI_TextMeshPro - case UIElementClassify.TMPText: - TMPText.text = value.ToString(); - break; - case UIElementClassify.TMPInputField: - TMPInputField.text = value.ToString(); - break; -#endif - case UIElementClassify.UIText: - UIText.text = value.ToString(); - break; - case UIElementClassify.UIInputField: - UIInputField.text = value.ToString(); - break; - case UIElementClassify.UIToggle: - UIToggle.isOn = Convert.ToBoolean(value); - break; - case UIElementClassify.UIButton: - // UIButton do nothing - break; - case UIElementClassify.UISlider: - UISlider.value = Convert.ToSingle(value); - break; - case UIElementClassify.UIDropdown: - UIDropdown.value = Convert.ToInt32(value); - break; - default: - break; - } - } - - /// - /// ui¼ - /// - public event AnyTrigger OnUITrigger - { - add - { - switch (classify) - { -#if dUI_TextMeshPro - case UIElementClassify.TMPInputField: - switch (TMPInputTriggerOnEventType) - { - case TMPInputTriggerEvent.onEndEdit: - TMPInputField.onEndEdit.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - case TMPInputTriggerEvent.onSubmit: - TMPInputField.onSubmit.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - case TMPInputTriggerEvent.onSelect: - TMPInputField.onSelect.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - case TMPInputTriggerEvent.onDeselect: - TMPInputField.onDeselect.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - case TMPInputTriggerEvent.onTextSelection: - TMPInputField.onTextSelection.AddListener((content, start, end) => - value?.Invoke(this, content, start, end)); - break; - case TMPInputTriggerEvent.onEndTextSelection: - TMPInputField.onEndTextSelection.AddListener((content, start, end) => - value?.Invoke(this, content, start, end)); - break; - case TMPInputTriggerEvent.onValueChanged: - TMPInputField.onValueChanged.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - case TMPInputTriggerEvent.onTouchScreenKeyboardStatusChanged: - TMPInputField.onTouchScreenKeyboardStatusChanged.AddListener(a => - value?.Invoke(this, a, 0, 0)); - break; - default: - break; - } - - break; -#endif - case UIElementClassify.UIInputField: - switch (UIInputTriggerOnEventType) - { - case UIInputTriggerEvent.onEndEdit: - UIInputField.onEndEdit.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - case UIInputTriggerEvent.onSubmit: - UIInputField.onSubmit.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - case UIInputTriggerEvent.onValueChanged: - UIInputField.onValueChanged.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - default: break; - } - - break; - case UIElementClassify.UIToggle: - UIToggle.onValueChanged.AddListener(a => value?.Invoke(this, value, 0, 0)); - break; - case UIElementClassify.UIButton: - UIButton.onClick.AddListener(() => value?.Invoke(this, true, 0, 0)); - break; - case UIElementClassify.UISlider: - UISlider.onValueChanged.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - case UIElementClassify.UIDropdown: - UIDropdown.onValueChanged.AddListener(a => value?.Invoke(this, a, 0, 0)); - break; - default: - break; - } - } - remove { Debug.Log("ⴥ޷жض¼"); } - } - - /// - /// ui¼ - /// - public void ClearTriggerEvent() - { -#if dUI_TextMeshPro - if (TMPInputField) - { - TMPInputField.onEndEdit.RemoveAllListeners(); - TMPInputField.onSubmit.RemoveAllListeners(); - TMPInputField.onSelect.RemoveAllListeners(); - TMPInputField.onDeselect.RemoveAllListeners(); - TMPInputField.onTextSelection.RemoveAllListeners(); - TMPInputField.onEndTextSelection.RemoveAllListeners(); - TMPInputField.onValueChanged.RemoveAllListeners(); - TMPInputField.onTouchScreenKeyboardStatusChanged.RemoveAllListeners(); - } -#endif - if (UIInputField) - { - UIInputField.onEndEdit.RemoveAllListeners(); - UIInputField.onSubmit.RemoveAllListeners(); - UIInputField.onValueChanged.RemoveAllListeners(); - } - - UIToggle?.onValueChanged.RemoveAllListeners(); - UIButton?.onClick.RemoveAllListeners(); - UISlider?.onValueChanged.RemoveAllListeners(); - UIDropdown?.onValueChanged.RemoveAllListeners(); - } - } - - // todo ʹƣɸѡõ - // todo 齨ֵٲңã - } -} \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgLocator.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgLocator.cs.meta deleted file mode 100644 index 02245ad..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgLocator.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ba7420972b684874dbab8713369d6f1b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgPropertyGroupScriptableObject.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgPropertyGroupScriptableObject.cs deleted file mode 100644 index 15bddc7..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgPropertyGroupScriptableObject.cs +++ /dev/null @@ -1,299 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace XericUI.ReflectionUIGenerator -{ - /// - /// 界面属性编组 - /// - /// 将一种 C# 数据类型与一个 UI 预制体绑定,并归属于一个主题。 - /// 类似 Newtonsoft.Json 的 Converter 概念 — 针对特定 Type,指定特定的 UI 模板。 - /// - /// 同一类型可以有多个不同 uiName/主题的重载(如 "简单输入框" vs "带标签的输入框")。 - /// Manager 根据当前主题名 + 字段 Tag 自动匹配对应的编组。 - /// - /// 全局注册表以 themeName 为键分组管理,OnEnable/OnDisable 自动注册/注销。 - /// - [Serializable] - [CreateAssetMenu(fileName = "Xeric UI avr Property group", menuName = "Xeric Library/UI Action Vessel/Reflection UI Gen/UI avr Property Group", order = 10)] - public class XuiavrgPropertyGroupScriptableObject : ScriptableObject - { - #region 全局注册表 - - /// - /// 全局预制体组字典,以主题名称为键分组 - /// - internal static Dictionary> GlobalPrefabDict - = new Dictionary>(); - - /// - /// 根据主题名获取该主题下所有编组 - /// - public static List GetGroupsByTheme(string themeName) - { - if (string.IsNullOrEmpty(themeName)) - themeName = DefaultThemeName; - - GlobalPrefabDict.TryGetValue(themeName, out var list); - return list ?? new List(); - } - - /// - /// 根据主题名 + 标记查找编组 - /// - public static XuiavrgPropertyGroupScriptableObject FindGroup(string themeName, params XuiavrgFieldTag[] tags) - { - var groups = GetGroupsByTheme(themeName); - return groups.FirstOrDefault(g => tags.All(t => g.supportedTags.Contains(t))); - } - - /// - /// 根据主题名 + UI 名称查找编组 - /// - public static XuiavrgPropertyGroupScriptableObject FindGroupByUiName(string themeName, string uiName) - { - var groups = GetGroupsByTheme(themeName); - return groups.FirstOrDefault(g => g.uiName == uiName); - } - - /// - /// 根据主题名 + 绑定类型查找所有匹配的编组 - /// - public static List FindGroupsByType(string themeName, string valueTypeName) - { - var groups = GetGroupsByTheme(themeName); - return groups.Where(g => g.valueTypeName == valueTypeName).ToList(); - } - - /// - /// 默认主题名称 - /// - public const string DefaultThemeName = "Default"; - - #endregion - - #region 属性字段 - - /// - /// 主题名称。同一主题下的 UI 组件共享统一风格。 - /// - [Tooltip("主题名称。同一主题下的 UI 组件共享统一风格。Manager 根据其 currentTheme 查找对应编组")] - public string themeName = DefaultThemeName; - - /// - /// UI 个体名称。同一类型可有多个不同名称的重载。 - /// 在数据字段上通过 XuiavrgFieldPrefabOverride 按此名称指定。 - /// - [Tooltip("UI 个体名称。用于 XuiavrgFieldPrefabOverride 按名指定,同一类型可有多个重载")] - public string uiName = "Default"; - - /// - /// 此编组绑定的 C# 数据类型全名(如 System.Single、System.String) - /// - [Tooltip("绑定的 C# 数据类型全名,如 System.Single、System.Boolean")] - public string valueTypeName = "System.Single"; - - /// - /// 解析后的类型缓存 - /// - public Type ValueType - { - get - { - if (_valueType != null) return _valueType; - if (!string.IsNullOrEmpty(valueTypeName)) - _valueType = Type.GetType(valueTypeName); - return _valueType; - } - } - private Type _valueType; - - /// - /// 所有程序集中类型名的缓存 — 简单名列表(如 "Boolean"、"Int32"、"String") - /// 与 _allTypeFullNames 下标一一对应 - /// - private static List _allTypeSimpleNames; - - /// - /// 所有程序集中类型名的缓存 — 全名列表(如 "System.Boolean"、"System.Int32") - /// 与 _allTypeSimpleNames 下标一一对应 - /// - private static List _allTypeFullNames; - - /// - /// 初始化类型名缓存 — 反射扫描所有已加载程序集中的类型 - /// - private static void EnsureTypeCacheBuilt() - { - if (_allTypeFullNames != null) return; - - _allTypeSimpleNames = new List(); - _allTypeFullNames = new List(); - - var seen = new HashSet(); - - foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) - { - try - { - foreach (var type in asm.GetTypes()) - { - var fullName = type.FullName; - if (string.IsNullOrEmpty(fullName)) continue; - // 跳过泛型参数位置和编译器生成的类型 - if (fullName.Contains("`") || fullName.Contains("<")) continue; - - if (!seen.Add(fullName)) continue; - - _allTypeSimpleNames.Add(type.Name); - _allTypeFullNames.Add(fullName); - } - } - catch - { - // 某些程序集可能无法反射(如动态程序集),忽略 - } - } - } - - /// - /// 尝试将用户输入的类型名纠正为标准全名。 - /// 通过 LevenshteinDistance 在所有已知类型中找到最匹配的名称。 - /// 如 "boolean" → "System.Boolean", "single" → "System.Single"。 - /// 如果已经是标准全名或无法匹配则返回原值(不做修改)。 - /// - /// 用户输入的类型名 - /// 纠正后的标准类型全名,无法识别时返回原值 - public static string NormalizeTypeName(string userInput) - { - if (string.IsNullOrWhiteSpace(userInput)) - return userInput; - - userInput = userInput.Trim(); - - // 已经是标准全名(包含 "."),直接视为 OK,不做修改 - if (userInput.Contains('.')) - return userInput; - - // 初始化类型缓存 - EnsureTypeCacheBuilt(); - - // 通过 Levenshtein 距离在所有类型简单名中找到最匹配的 - var bestMatch = XericLibrary.Runtime.MacroLibrary.MacroMath.LevenshteinDistance( - _allTypeSimpleNames, userInput, - out int dis, out int index); - - // 阈值:距离超过输入长度 2 倍 + 2,认为不匹配,返回原值 - if (bestMatch == null || dis > userInput.Length * 2 + 2) - return userInput; - - // 返回对应的全名 - return _allTypeFullNames[index]; - } - - /// - /// 尝试将此实例的 valueTypeName 纠正为标准全名 - /// - public void NormalizeValueTypeName() - { - valueTypeName = NormalizeTypeName(valueTypeName); - _valueType = null; // 清空缓存,下次访问时重新解析 - } - - /// - /// 对应的 UI 预制体 - /// - [Tooltip("拖入 UI 预制体,系统会自动扫描其中的 IXuiavrgFieldTagProvider 组件")] - public GameObject prefab; - - /// - /// 此编组支持的 UI 功能标记集合 - /// - [Tooltip("此编组包含的 UI 功能标记。Manager 根据字段 Tag 匹配对应编组")] - public List supportedTags = new List(); - - #endregion - - #region 组件自动识别(运行时 + Editor 共用) - - /// - /// 从预制体中自动扫描到的 IXuiavrgFieldTagProvider 组件列表(非序列化,仅供 Editor 展示) - /// - [NonSerialized] - public List detectedComponents = new List(); - - /// - /// 重新扫描预制体中的标记组件 - /// - public void ScanPrefabComponents() - { - detectedComponents.Clear(); - if (prefab == null) return; - - var components = prefab.GetComponentsInChildren(true); - foreach (var comp in components) - { - if (comp is IXuiavrgFieldTagProvider tagProvider && tagProvider.EntryTag != XuiavrgFieldTag.None) - { - detectedComponents.Add(comp); - } - } - - // 自动同步 supportedTags - HashSet autoTags = new HashSet(); - foreach (var comp in detectedComponents) - { - if (comp is IXuiavrgFieldTagProvider tp && tp.EntryTag != XuiavrgFieldTag.None) - autoTags.Add(tp.EntryTag); - } - - foreach (var tag in autoTags) - { - if (!supportedTags.Contains(tag)) - supportedTags.Add(tag); - } - } - - #endregion - - #region 生命周期 - - void OnEnable() - { - RegisterToGlobalDict(); - } - - void OnDisable() - { - UnregisterFromGlobalDict(); - } - - private void RegisterToGlobalDict() - { - string key = string.IsNullOrEmpty(themeName) ? DefaultThemeName : themeName; - if (!GlobalPrefabDict.TryGetValue(key, out var list)) - { - list = new List(); - GlobalPrefabDict[key] = list; - } - if (!list.Contains(this)) - list.Add(this); - } - - private void UnregisterFromGlobalDict() - { - string key = string.IsNullOrEmpty(themeName) ? DefaultThemeName : themeName; - if (GlobalPrefabDict.TryGetValue(key, out var list)) - { - list.Remove(this); - if (list.Count == 0) - GlobalPrefabDict.Remove(key); - } - } - - #endregion - } -} diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgPropertyGroupScriptableObject.cs.meta b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgPropertyGroupScriptableObject.cs.meta deleted file mode 100644 index 5f0eb3a..0000000 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgPropertyGroupScriptableObject.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: bcd1c64483d2457ab9143f086dff511d -timeCreated: 1777024131 \ No newline at end of file diff --git a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgUtilities.cs b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgUtilities.cs index 64376f4..79bd6aa 100644 --- a/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgUtilities.cs +++ b/Runtime/InterfaceAttributeReflectionGenerator/Scripts/XuiavrgUtilities.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; namespace XericUI.ReflectionUIGenerator @@ -9,8 +10,6 @@ namespace XericUI.ReflectionUIGenerator /// /// 获取所有运行时成员(字段和属性) /// - /// - /// public static IEnumerable GetRuntimeMemberInfo(this Type type) { foreach (var field in type.GetRuntimeFields()) @@ -18,5 +17,97 @@ namespace XericUI.ReflectionUIGenerator foreach (var property in type.GetRuntimeProperties()) yield return property; } + + /// + /// 从任意对象提取 AvrgMemberMetaData 元数据集 + /// 反射识别成员、识别 IXuiavrgAttribute 标记、执行标记绑定的完整流程 + /// + /// 待提取的数据对象 + /// 提取完成的元数据集 + public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData(this object source) + { + if (source == null) + throw new ArgumentNullException(nameof(source)); + + var metaDataSet = new AvrgMemberMetaDataSet(); + var type = source.GetType(); + + foreach (var memberInfo in type.GetRuntimeMemberInfo()) + { + // 检查隐藏标记 + var hideAttr = memberInfo.GetCustomAttribute(); + if (hideAttr != null && hideAttr.Hide) + continue; + + // 创建元数据 + var metaData = new AvrgMemberMetaData + { + MemberName = memberInfo.Name, + MemberInfo = memberInfo, + TargetInstance = source, + }; + + // 识别值类型和 IsProperty + switch (memberInfo) + { + case PropertyInfo pi: + metaData.IsProperty = true; + metaData.ValueType = pi.PropertyType; + break; + case FieldInfo fi: + metaData.IsProperty = false; + metaData.ValueType = fi.FieldType; + break; + } + + // 提取字段默认值(类初始值) + // Unity 序列化字段可以通过反射读取实例上的当前值作为"默认"参考 + // 但更准确的在类声明阶段的初始值需要通过运行时观察或 Fallback 判断 + // 此处策略:如果 TargetInstance 非 null,直接 ReadValue 作为 DefaultValue + if (source != null && memberInfo is FieldInfo fi2) + { + metaData.DefaultValue = fi2.GetValue(source); + } + else if (source != null && memberInfo is PropertyInfo pi2 && pi2.CanRead) + { + metaData.DefaultValue = pi2.GetValue(source); + } + + // 设置 PropertyCheckKey 默认值(用类型全称) + if (metaData.ValueType != null) + metaData.PropertyCheckKey = metaData.ValueType.FullName; + + // 收集所有 IXuiavrgAttribute + var attributes = memberInfo.GetCustomAttributes() + .OfType() + .ToList(); + + metaData.Attributes = attributes; + + // 按序执行 BindMetaData + foreach (var attr in attributes) + { + attr.BindMetaData(metaData); + } + + metaDataSet.Add(metaData); + } + + // 处理编组合并 + metaDataSet.MergeGroupEntries(); + + // 排序 + metaDataSet.SortByFieldOrder(); + + return metaDataSet; + } + + /// + /// 从任意对象提取 AvrgMemberMetaData 元数据集的泛型版本 + /// + public static AvrgMemberMetaDataSet ExtractAvrgMemberMetaData(this T source) + { + return ExtractAvrgMemberMetaData((object)source); + } } -} \ No newline at end of file +} diff --git a/Runtime/OverrideUI/IXUIAvrgMDValueStyle.cs b/Runtime/OverrideUI/IXUIAvrgMDValueStyle.cs new file mode 100644 index 0000000..06a03f1 --- /dev/null +++ b/Runtime/OverrideUI/IXUIAvrgMDValueStyle.cs @@ -0,0 +1,16 @@ +namespace XericUI.ReflectionUIGenerator +{ + /// + /// MDVM 值组件样式扩展接口 + /// 实现此接口的脚本可挂载到 XUIAvrgMDValue 预制体的子节点上, + /// 在元数据绑定阶段由 XUIAvrgMDValue 自动调用 ApplyMetaData + /// + public interface IXUIAvrgMDValueStyle + { + /// + /// 应用元数据以控制组件样式 + /// + /// 当前绑定的元数据 + void ApplyMetaData(AvrgMemberMetaData metaData); + } +} diff --git a/Runtime/OverrideUI/IXUIAvrgMDValueStyle.cs.meta b/Runtime/OverrideUI/IXUIAvrgMDValueStyle.cs.meta new file mode 100644 index 0000000..e2ad33b --- /dev/null +++ b/Runtime/OverrideUI/IXUIAvrgMDValueStyle.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dfd29eb0237f4e644a57bfe7046e8d7a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/OverrideUI/XUIAvrgMDValue.cs b/Runtime/OverrideUI/XUIAvrgMDValue.cs new file mode 100644 index 0000000..a28a2a2 --- /dev/null +++ b/Runtime/OverrideUI/XUIAvrgMDValue.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// MDVM 值组件 — 统一 UI 脚本组件 + /// 替换所有 XericUIrAttributeXXX 作为预制体上的标记组件 + /// + /// 职责: + /// 1. 提供 EntryTag/TagIndex 供 XuiFieldTempEntry 匹配 + /// 2. 持有绑定的 AvrgMemberMetaData 引用 + /// 3. 初始化时广播元数据到所有子节点上的 IXUIAvrgMDValueStyle + /// + [AddComponentMenu("Xeric Library/UI Action Vessel/XUIAvrgMDValue")] + public class XUIAvrgMDValue : MonoBehaviour + { + [SerializeField] + [Tooltip("功能标记,用于与 Entry 的 Tag 匹配")] + private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.None; + + [SerializeField] + [Tooltip("同 Tag 多实例时区分(0 表示不限制)")] + private int _tagIndex; + + /// 绑定的元数据引用 + public AvrgMemberMetaData BoundMetaData { get; private set; } + + public XuiavrgFieldTag EntryTag => _entryTag; + public int TagIndex => _tagIndex; + + /// + /// 由绑定系统调用,传递元数据并广播到 IXUIAvrgMDValueStyle 子组件 + /// + public void InitializeWithMetaData(AvrgMemberMetaData metaData) + { + BoundMetaData = metaData; + + if (metaData == null) return; + + // 查找子节点上的所有样式扩展组件并广播元数据 + var styles = GetComponentsInChildren(true); + foreach (var style in styles) + { + // 跳过自身(XUIAvrgMDValue 不是 IXUIAvrgMDValueStyle) + if (style is MonoBehaviour mb && mb.gameObject == gameObject) + continue; + + style.ApplyMetaData(metaData); + } + } + } +} diff --git a/Runtime/OverrideUI/XUIAvrgMDValue.cs.meta b/Runtime/OverrideUI/XUIAvrgMDValue.cs.meta new file mode 100644 index 0000000..8ecde3f --- /dev/null +++ b/Runtime/OverrideUI/XUIAvrgMDValue.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 94518c2eae79c634393df9ebf950bef6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/OverrideUI/XericUIrAttributeButton.cs b/Runtime/OverrideUI/XericUIrAttributeButton.cs deleted file mode 100644 index e396582..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeButton.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using UnityEngine; -using UnityEngine.UI; -using XericUI.Helper; -using XericUI.ReflectionUIGenerator; - -namespace XericUI.OverrideUI -{ - /// - /// 带字段标记的增强按钮组件 - /// 继承 XericButton,额外支持 XuiavrgFieldTag 标记,便于反射界面生成器自动绑定 - /// - [AddComponentMenu("Xeric UI Vessel/Attribute/Button", 51)] - public class XericUIrAttributeButton : XericButton, IXuiavrgFieldTagProvider - { - [SerializeField] - [Tooltip("Entry 功能标记,用于自动绑定匹配")] - public XuiavrgFieldTag _entryTag = XuiavrgFieldTag.Button; - - [SerializeField] - [Tooltip("同一 Tag 存在多个时的索引区分")] - public int _tagIndex = 0; - - public XuiavrgFieldTag EntryTag => _entryTag; - public int TagIndex => _tagIndex; - - #region 组件替换 - -#if UNITY_EDITOR - [UnityEditor.MenuItem("CONTEXT/Button/Replace To/Xeric (Attribute)", true)] - private static bool ValidateReplaceToXeric(UnityEditor.MenuCommand command) - => command.context is Button and not XericUIrAttributeButton; - - [UnityEditor.MenuItem("CONTEXT/Button/Replace To/Xeric (Attribute)", false, 12)] - private static void ReplaceToXeric(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => (o.targetGraphic, o.onClick), - (t, d) => { t.targetGraphic = d.targetGraphic; t.onClick = d.onClick; }); - - [UnityEditor.MenuItem("CONTEXT/Button/Replace To/Unity Button (Attribute)", true)] - private static bool ValidateReplaceToUnity(UnityEditor.MenuCommand command) - => command.context is XericUIrAttributeButton; - - [UnityEditor.MenuItem("CONTEXT/Button/Replace To/Unity Button (Attribute)", false, 100)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => (o.targetGraphic, o.onClick), - (t, d) => { t.targetGraphic = d.targetGraphic; t.onClick = d.onClick; }); -#endif - - #endregion - } -} diff --git a/Runtime/OverrideUI/XericUIrAttributeButton.cs.meta b/Runtime/OverrideUI/XericUIrAttributeButton.cs.meta deleted file mode 100644 index a046e86..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeButton.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f39882be385237b4791af43ba89fec27 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/OverrideUI/XericUIrAttributeDropdown.cs b/Runtime/OverrideUI/XericUIrAttributeDropdown.cs deleted file mode 100644 index 07bea43..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeDropdown.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using UnityEngine; -using UnityEngine.UI; -using XericUI.Helper; -using XericUI.ReflectionUIGenerator; - -namespace XericUI.OverrideUI -{ - /// - /// 带字段标记的增强下拉框组件 - /// 继承 Unity Dropdown,额外支持 XuiavrgFieldTag 标记,便于反射界面生成器自动绑定 - /// - [AddComponentMenu("Xeric UI Vessel/Attribute/Dropdown", 51)] - public class XericUIrAttributeDropdown : Dropdown, IXuiavrgFieldTagProvider - { - [SerializeField] - [Tooltip("Entry 功能标记,用于自动绑定匹配")] - private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.Dropdown; - - [SerializeField] - [Tooltip("同一 Tag 存在多个时的索引区分")] - private int _tagIndex = 0; - - public XuiavrgFieldTag EntryTag => _entryTag; - public int TagIndex => _tagIndex; - - #region 组件替换 - -#if UNITY_EDITOR - [UnityEditor.MenuItem("CONTEXT/Dropdown/Replace To/Xeric (Attribute)", true)] - private static bool ValidateReplaceToAttribute(UnityEditor.MenuCommand command) - => command.context is Dropdown and not XericUIrAttributeDropdown; - - [UnityEditor.MenuItem("CONTEXT/Dropdown/Replace To/Xeric (Attribute)", false, 10)] - private static void ReplaceToAttribute(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext options, DropdownEvent onValueChanged)>( - o => - (o.targetGraphic, o.value, o.options, o.onValueChanged), - (t, d) => - { - t.targetGraphic = d.targetGraphic; - t.value = d.value; - t.options = d.options; - t.onValueChanged = d.onValueChanged; - }); - - [UnityEditor.MenuItem("CONTEXT/Dropdown/Replace To/Unity Dropdown", true)] - private static bool ValidateReplaceToUnity(UnityEditor.MenuCommand command) - => command.context is XericUIrAttributeDropdown; - - [UnityEditor.MenuItem("CONTEXT/Dropdown/Replace To/Unity Dropdown", false, 100)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext options, DropdownEvent onValueChanged)>( - o => - (o.targetGraphic, o.value, o.options, o.onValueChanged), - (t, d) => - { - t.targetGraphic = d.targetGraphic; - t.value = d.value; - t.options = d.options; - t.onValueChanged = d.onValueChanged; - }); -#endif - - #endregion - } -} diff --git a/Runtime/OverrideUI/XericUIrAttributeDropdown.cs.meta b/Runtime/OverrideUI/XericUIrAttributeDropdown.cs.meta deleted file mode 100644 index 0c1fcac..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeDropdown.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4f84f2204fb53b04fbf647213e92b0f9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/OverrideUI/XericUIrAttributeInputField.cs b/Runtime/OverrideUI/XericUIrAttributeInputField.cs deleted file mode 100644 index 96a6e0d..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeInputField.cs +++ /dev/null @@ -1,66 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using XericUI.Helper; -using XericUI.ReflectionUIGenerator; - -namespace XericUI.OverrideUI -{ - /// - /// 带字段标记的增强输入框组件 - /// 继承 Unity InputField,额外支持 XuiavrgFieldTag 标记,便于反射界面生成器自动绑定 - /// - [AddComponentMenu("Xeric UI Vessel/Attribute/InputField", 51)] - public class XericUIrAttributeInputField : InputField, IXuiavrgFieldTagProvider - { - [SerializeField] - [Tooltip("Entry 功能标记,用于自动绑定匹配")] - private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.Input; - - [SerializeField] - [Tooltip("同一 Tag 存在多个时的索引区分")] - private int _tagIndex = 0; - - public XuiavrgFieldTag EntryTag => _entryTag; - public int TagIndex => _tagIndex; - - #region 组件替换 - -#if UNITY_EDITOR - [UnityEditor.MenuItem("CONTEXT/InputField/Replace To/Xeric (Attribute)", true)] - private static bool ValidateReplaceToAttribute(UnityEditor.MenuCommand command) - => command.context is InputField and not XericUIrAttributeInputField; - - [UnityEditor.MenuItem("CONTEXT/InputField/Replace To/Xeric (Attribute)", false, 10)] - private static void ReplaceToAttribute(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => (o.targetGraphic, o.text, o.onValueChanged, o.onEndEdit), - (t, d) => - { - t.targetGraphic = d.targetGraphic; - t.text = d.text; - t.onValueChanged = d.onValueChanged; - t.onEndEdit = d.onEndEdit; - }); - - [UnityEditor.MenuItem("CONTEXT/InputField/Replace To/Unity InputField", true)] - private static bool ValidateReplaceToUnity(UnityEditor.MenuCommand command) - => command.context is XericUIrAttributeInputField; - - [UnityEditor.MenuItem("CONTEXT/InputField/Replace To/Unity InputField", false, 100)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => (o.targetGraphic, o.text, o.onValueChanged, o.onEndEdit), - (t, d) => - { - t.targetGraphic = d.targetGraphic; - t.text = d.text; - t.onValueChanged = d.onValueChanged; - t.onEndEdit = d.onEndEdit; - }); -#endif - - #endregion - } -} diff --git a/Runtime/OverrideUI/XericUIrAttributeInputField.cs.meta b/Runtime/OverrideUI/XericUIrAttributeInputField.cs.meta deleted file mode 100644 index 94345e4..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeInputField.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: cd5f00c3fa1461a4e8b222779a796cc9 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/OverrideUI/XericUIrAttributeSlider.cs b/Runtime/OverrideUI/XericUIrAttributeSlider.cs deleted file mode 100644 index 47d4d83..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeSlider.cs +++ /dev/null @@ -1,66 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using XericUI.Helper; -using XericUI.ReflectionUIGenerator; - -namespace XericUI.OverrideUI -{ - /// - /// 带字段标记的增强滑块组件 - /// 继承 Unity Slider,额外支持 XuiavrgFieldTag 标记,便于反射界面生成器自动绑定 - /// - [AddComponentMenu("Xeric UI Vessel/Attribute/Slider", 51)] - public class XericUIrAttributeSlider : Slider, IXuiavrgFieldTagProvider - { - [SerializeField] - [Tooltip("Entry 功能标记,用于自动绑定匹配")] - private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.Slider; - - [SerializeField] - [Tooltip("同一 Tag 存在多个时的索引区分")] - private int _tagIndex = 0; - - public XuiavrgFieldTag EntryTag => _entryTag; - public int TagIndex => _tagIndex; - - #region 组件替换 - -#if UNITY_EDITOR - [UnityEditor.MenuItem("CONTEXT/Slider/Replace To/Xeric (Attribute)", true)] - private static bool ValidateReplaceToAttribute(UnityEditor.MenuCommand command) - => command.context is Slider and not XericUIrAttributeSlider; - - [UnityEditor.MenuItem("CONTEXT/Slider/Replace To/Xeric (Attribute)", false, 10)] - private static void ReplaceToAttribute(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => - (o.targetGraphic, o.value, o.onValueChanged), - (t, d) => - { - t.targetGraphic = d.targetGraphic; - t.value = d.value; - t.onValueChanged = d.onValueChanged; - }); - - [UnityEditor.MenuItem("CONTEXT/Slider/Replace To/Unity Slider", true)] - private static bool ValidateReplaceToUnity(UnityEditor.MenuCommand command) - => command.context is XericUIrAttributeSlider; - - [UnityEditor.MenuItem("CONTEXT/Slider/Replace To/Unity Slider", false, 100)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => - (o.targetGraphic, o.value, o.onValueChanged), - (t, d) => - { - t.targetGraphic = d.targetGraphic; - t.value = d.value; - t.onValueChanged = d.onValueChanged; - }); -#endif - - #endregion - } -} diff --git a/Runtime/OverrideUI/XericUIrAttributeSlider.cs.meta b/Runtime/OverrideUI/XericUIrAttributeSlider.cs.meta deleted file mode 100644 index c7d41b9..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeSlider.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 263e829bad1c50d469036934e671aaa8 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/OverrideUI/XericUIrAttributeText.cs b/Runtime/OverrideUI/XericUIrAttributeText.cs deleted file mode 100644 index cf0c739..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeText.cs +++ /dev/null @@ -1,71 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using XericUI.Helper; -using XericUI.ReflectionUIGenerator; - -namespace XericUI.OverrideUI -{ - /// - /// 带字段标记的增强文本组件 - /// 继承 XericImage,额外支持 XuiavrgFieldTag 标记。 - /// 主要用于预制体中标记 Label/Output/Sign 等文本功能元素 - /// - [AddComponentMenu("Xeric UI Vessel/Attribute/Text", 51)] - public class XericUIrAttributeText : XericImage, IXuiavrgFieldTagProvider - { - [SerializeField] - [Tooltip("关联的 Text 组件引用(可选,用于读取/写入文本内容)")] - private Text _targetText; - - [SerializeField] - [Tooltip("Entry 功能标记,用于自动绑定匹配")] - private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.Label; - - [SerializeField] - [Tooltip("同一 Tag 存在多个时的索引区分")] - private int _tagIndex = 0; - - public XuiavrgFieldTag EntryTag => _entryTag; - public int TagIndex => _tagIndex; - - /// 文本内容(通过 _targetText 读写) - public string TextContent - { - get => _targetText != null ? _targetText.text : string.Empty; - set { if (_targetText) _targetText.text = value; } - } - - /// 文本颜色 - public Color TextColor - { - get => _targetText != null ? _targetText.color : Color.white; - set { if (_targetText) _targetText.color = value; } - } - - #region 组件替换 - -#if UNITY_EDITOR - [UnityEditor.MenuItem("CONTEXT/Image/Replace To/Xeric (AttributeText)", true)] - private static bool ValidateReplaceToAttribute(UnityEditor.MenuCommand command) - => command.context is Image and not XericUIrAttributeText; - - [UnityEditor.MenuItem("CONTEXT/Image/Replace To/Xeric (AttributeText)", false, 13)] - private static void ReplaceToAttribute(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => o.sprite, - (t, d) => { t.sprite = d; }); - - [UnityEditor.MenuItem("CONTEXT/Image/Replace To/Unity Image (Attribute)", true)] - private static bool ValidateReplaceToUnity(UnityEditor.MenuCommand command) - => command.context is XericUIrAttributeText; - - [UnityEditor.MenuItem("CONTEXT/Image/Replace To/Unity Image (Attribute)", false, 100)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => o.sprite, - (t, d) => { t.sprite = d; }); -#endif - - #endregion - } -} diff --git a/Runtime/OverrideUI/XericUIrAttributeText.cs.meta b/Runtime/OverrideUI/XericUIrAttributeText.cs.meta deleted file mode 100644 index 90603a2..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeText.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5189154ac8f148f449041fd8890c9730 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/OverrideUI/XericUIrAttributeTextMeshProInputField.cs b/Runtime/OverrideUI/XericUIrAttributeTextMeshProInputField.cs deleted file mode 100644 index f125b5f..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeTextMeshProInputField.cs +++ /dev/null @@ -1,97 +0,0 @@ -using XericUI.ReflectionUIGenerator; - -#if dUI_TextMeshPro -using TMPro; -#endif - -namespace XericUI.OverrideUI -{ - /// - /// 带字段标记的增强 TextMeshPro 输入框组件 - /// 附加在 TMP_InputField 所在的 GameObject 上,提供 XuiavrgFieldTag 标记, - /// 便于反射界面生成器自动绑定。支持从 TMP_InputField 一键替换。 - /// - [UnityEngine.AddComponentMenu("Xeric UI Vessel/Attribute/TextMeshPro InputField", 51)] - public class XericUIrAttributeTextMeshProInputField : UnityEngine.MonoBehaviour, IXuiavrgFieldTagProvider - { -#if dUI_TextMeshPro - [UnityEngine.SerializeField] - [UnityEngine.Tooltip("关联的 TMP_InputField 组件引用(自动获取或手动指定)")] - private TMP_InputField _targetInputField; - - /// 输入文本内容 - public string TextContent - { - get => _targetInputField != null ? _targetInputField.text : string.Empty; - set { if (_targetInputField) _targetInputField.text = value; } - } - - /// 占位符文本 - public string Placeholder - { - get => _targetInputField != null && _targetInputField.placeholder is TMPro.TMP_Text ph - ? ph.text : string.Empty; - set - { - if (_targetInputField && _targetInputField.placeholder is TMPro.TMP_Text ph) - ph.text = value; - } - } - - private void Awake() - { - if (_targetInputField == null) - _targetInputField = GetComponent(); - } -#endif - - [UnityEngine.SerializeField] - [UnityEngine.Tooltip("Entry 功能标记,用于自动绑定匹配")] - private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.Input; - - [UnityEngine.SerializeField] - [UnityEngine.Tooltip("同一 Tag 存在多个时的索引区分")] - private int _tagIndex = 0; - - public XuiavrgFieldTag EntryTag => _entryTag; - public int TagIndex => _tagIndex; - - #region 组件替换 - -#if UNITY_EDITOR - [UnityEditor.MenuItem("CONTEXT/TMP_InputField/Replace To/Xeric (Attribute)", true)] - private static bool ValidateReplaceToAttribute(UnityEditor.MenuCommand command) - { -#if dUI_TextMeshPro - return command.context is TMP_InputField && !(command.context is XericUIrAttributeTextMeshProInputField); -#else - return false; -#endif - } - - [UnityEditor.MenuItem("CONTEXT/TMP_InputField/Replace To/Xeric (Attribute)", false, 10)] - private static void ReplaceToAttribute(UnityEditor.MenuCommand command) - { -#if dUI_TextMeshPro - var tmpComp = command.context as UnityEngine.Component; - if (tmpComp != null) - tmpComp.gameObject.AddComponent(); -#endif - } - - [UnityEditor.MenuItem("CONTEXT/TMP_InputField/Replace To/Unity TMP_InputField", true)] - private static bool ValidateReplaceToUnity(UnityEditor.MenuCommand command) - => command.context is XericUIrAttributeTextMeshProInputField; - - [UnityEditor.MenuItem("CONTEXT/TMP_InputField/Replace To/Unity TMP_InputField", false, 100)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) - { - var comp = command.context as XericUIrAttributeTextMeshProInputField; - if (comp != null) - UnityEngine.Object.DestroyImmediate(comp); - } -#endif - - #endregion - } -} diff --git a/Runtime/OverrideUI/XericUIrAttributeTextMeshProInputField.cs.meta b/Runtime/OverrideUI/XericUIrAttributeTextMeshProInputField.cs.meta deleted file mode 100644 index 4b0f5bc..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeTextMeshProInputField.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: eb8f3a8825c4b06458021d0414b254cf -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/OverrideUI/XericUIrAttributeTextMeshProText.cs b/Runtime/OverrideUI/XericUIrAttributeTextMeshProText.cs deleted file mode 100644 index f4fd0aa..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeTextMeshProText.cs +++ /dev/null @@ -1,94 +0,0 @@ -using XericUI.Helper; -using XericUI.ReflectionUIGenerator; - -#if dUI_TextMeshPro -using TMPro; -using UnityEngine; -#endif - -namespace XericUI.OverrideUI -{ - /// - /// 带字段标记的增强 TextMeshPro 文本组件 - /// 附加在 TMP_Text 所在的 GameObject 上,提供 XuiavrgFieldTag 标记, - /// 便于反射界面生成器自动绑定。同时支持从 TMP_Text 一键替换。 - /// - [UnityEngine.AddComponentMenu("Xeric UI Vessel/Attribute/TextMeshPro Text", 51)] - public class XericUIrAttributeTextMeshProText : UnityEngine.MonoBehaviour, IXuiavrgFieldTagProvider - { -#if dUI_TextMeshPro - [UnityEngine.SerializeField] - [UnityEngine.Tooltip("关联的 TMP_Text 组件引用(自动获取或手动指定)")] - private TMP_Text _targetText; - - /// 文本内容(通过 _targetText 读写) - public string TextContent - { - get => _targetText != null ? _targetText.text : string.Empty; - set { if (_targetText) _targetText.text = value; } - } - - /// 文本颜色 - public UnityEngine.Color TextColor - { - get => _targetText != null ? _targetText.color : UnityEngine.Color.white; - set { if (_targetText) _targetText.color = value; } - } - - private void Awake() - { - if (_targetText == null) - _targetText = GetComponent(); - } -#endif - - [UnityEngine.SerializeField] - [UnityEngine.Tooltip("Entry 功能标记,用于自动绑定匹配")] - private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.Label; - - [UnityEngine.SerializeField] - [UnityEngine.Tooltip("同一 Tag 存在多个时的索引区分")] - private int _tagIndex = 0; - - public XuiavrgFieldTag EntryTag => _entryTag; - public int TagIndex => _tagIndex; - - #region 组件替换 - -#if UNITY_EDITOR - [UnityEditor.MenuItem("CONTEXT/TextMeshProUGUI/Replace To/Xeric (Attribute)", true)] - private static bool ValidateReplaceToAttribute(UnityEditor.MenuCommand command) - { -#if dUI_TextMeshPro - return command.context is TextMeshProUGUI && !(command.context is XericUIrAttributeTextMeshProText); -#else - return false; -#endif - } - - [UnityEditor.MenuItem("CONTEXT/TextMeshProUGUI/Replace To/Xeric (Attribute)", false, 10)] - private static void ReplaceToAttribute(UnityEditor.MenuCommand command) - { -#if dUI_TextMeshPro - var tmpComp = command.context as Component; - if (tmpComp != null) - tmpComp.gameObject.AddComponent(); -#endif - } - - [UnityEditor.MenuItem("CONTEXT/TextMeshProUGUI/Replace To/Unity TextMeshPro", true)] - private static bool ValidateReplaceToUnity(UnityEditor.MenuCommand command) - => command.context is XericUIrAttributeTextMeshProText; - - [UnityEditor.MenuItem("CONTEXT/TextMeshProUGUI/Replace To/Unity TextMeshPro", false, 100)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) - { - var comp = command.context as XericUIrAttributeTextMeshProText; - if (comp != null) - UnityEngine.Object.DestroyImmediate(comp); - } -#endif - - #endregion - } -} diff --git a/Runtime/OverrideUI/XericUIrAttributeTextMeshProText.cs.meta b/Runtime/OverrideUI/XericUIrAttributeTextMeshProText.cs.meta deleted file mode 100644 index 777b281..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeTextMeshProText.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: cd08262713d01a24bb929ead3cc680ab -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/OverrideUI/XericUIrAttributeToggle.cs b/Runtime/OverrideUI/XericUIrAttributeToggle.cs deleted file mode 100644 index 7bb318f..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeToggle.cs +++ /dev/null @@ -1,64 +0,0 @@ -using UnityEngine; -using UnityEngine.UI; -using XericUI.Helper; -using XericUI.ReflectionUIGenerator; - -namespace XericUI.OverrideUI -{ - /// - /// 带字段标记的增强开关组件 - /// 继承 XericToggle,额外支持 XuiavrgFieldTag 标记,便于反射界面生成器自动绑定 - /// - [AddComponentMenu("Xeric UI Vessel/Attribute/Toggle", 32)] - public class XericUIrAttributeToggle : XericToggle, IXuiavrgFieldTagProvider - { - [SerializeField] - [Tooltip("Entry 功能标记,用于自动绑定匹配")] - private XuiavrgFieldTag _entryTag = XuiavrgFieldTag.Toggle; - - [SerializeField] - [Tooltip("同一 Tag 存在多个时的索引区分")] - private int _tagIndex = 0; - - public XuiavrgFieldTag EntryTag => _entryTag; - public int TagIndex => _tagIndex; - - #region 组件替换 - -#if UNITY_EDITOR - [UnityEditor.MenuItem("CONTEXT/Toggle/Replace To/Xeric (Attribute)", true)] - private static bool ValidateReplaceToAttribute(UnityEditor.MenuCommand command) - => command.context is Toggle and not XericUIrAttributeToggle; - - [UnityEditor.MenuItem("CONTEXT/Toggle/Replace To/Xeric (Attribute)", false, 12)] - private static void ReplaceToAttribute(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => (o.graphic, o.group, o.onValueChanged), - (t, d) => - { - t.graphic = d.graphic; - t.group = d.group; - t.onValueChanged = d.onValueChanged; - }); - - [UnityEditor.MenuItem("CONTEXT/Toggle/Replace To/Unity Toggle (Attribute)", true)] - private static bool ValidateReplaceToUnity(UnityEditor.MenuCommand command) - => command.context is XericUIrAttributeToggle; - - [UnityEditor.MenuItem("CONTEXT/Toggle/Replace To/Unity Toggle (Attribute)", false, 100)] - private static void ReplaceToUnity(UnityEditor.MenuCommand command) - => command.ReplaceCommandContext( - o => (o.graphic, o.group, o.onValueChanged), - (t, d) => - { - t.graphic = d.graphic; - t.group = d.group; - t.onValueChanged = d.onValueChanged; - }); -#endif - - #endregion - } -} diff --git a/Runtime/OverrideUI/XericUIrAttributeToggle.cs.meta b/Runtime/OverrideUI/XericUIrAttributeToggle.cs.meta deleted file mode 100644 index d52702e..0000000 --- a/Runtime/OverrideUI/XericUIrAttributeToggle.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: c20b871bced43a34a89dd5a7cf05c90b -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Runtime/OverrideUI/XuiAvrgMDGroup.cs b/Runtime/OverrideUI/XuiAvrgMDGroup.cs new file mode 100644 index 0000000..af8f5c3 --- /dev/null +++ b/Runtime/OverrideUI/XuiAvrgMDGroup.cs @@ -0,0 +1,52 @@ +using UnityEngine; + +namespace XericUI.ReflectionUIGenerator +{ + /// + /// MDVM 编组容器脚本 + /// 管理 GroupPath 对应的编组容器,容纳子 UI 组件 + /// + /// 外部组件挂载后尺寸由父级影响 + /// 内部子元素通过锚点系统获得布局 + /// 可通过挂载 TMP_Text / Image 等组件实现组标题/背景装饰 + /// + [AddComponentMenu("Xeric Library/UI Action Vessel/XuiAvrgMDGroup")] + public class XuiAvrgMDGroup : MonoBehaviour + { + [SerializeField] + [Tooltip("编组路径,对应元数据的 GroupPath")] + private string _groupName; + + [SerializeField] + [Tooltip("编组排序值,越小越靠前")] + private int _groupOrder; + + /// 内容挂载点 — 子 UI 组件应放入此 Transform + public Transform contentRoot; + + /// 编组路径对应的组名 + public string GroupName => _groupName; + + /// 编组排序值 + public int GroupOrder => _groupOrder; + + /// 上下文层级深度(如 "group1/subgroup" → depth=2) + public int ContextDepth { get; private set; } + + /// + /// 初始化编组容器 + /// + public void Initialize(string groupPath, int order) + { + _groupName = groupPath; + _groupOrder = order; + ContextDepth = string.IsNullOrEmpty(groupPath) ? 0 : groupPath.Split('/').Length; + + gameObject.name = $"Group [{groupPath}]"; + + // 如果没有指定 contentRoot,自动使用自身作为挂载点 + if (contentRoot == null) + contentRoot = transform; + } + } +} diff --git a/Runtime/OverrideUI/XuiAvrgMDGroup.cs.meta b/Runtime/OverrideUI/XuiAvrgMDGroup.cs.meta new file mode 100644 index 0000000..2fabb29 --- /dev/null +++ b/Runtime/OverrideUI/XuiAvrgMDGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 28ce72a98719bc740aef7059228f741e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: