diff --git a/Editor/OverrideUI/XericStyleButtonEditor.cs b/Editor/OverrideUI/XericStyleButtonEditor.cs
new file mode 100644
index 0000000..1fcef77
--- /dev/null
+++ b/Editor/OverrideUI/XericStyleButtonEditor.cs
@@ -0,0 +1,85 @@
+using UnityEditor;
+using UnityEditor.UI;
+
+using UnityEngine;
+using UnityEngine.UI;
+
+using XericUI.OverrideUI;
+
+namespace XericUIEditor.OverrideUI
+{
+ ///
+ /// XericStyleButton 的 Inspector 编辑器。
+ /// 完全替换 Selectable 的 Transition/ColorBlock/SpriteState 区域为 StyleSheet 状态块,
+ /// 保留 Interactable、Navigation、onClick 等基础功能。
+ ///
+ [CustomEditor(typeof(XericStyleButton), true)]
+ [CanEditMultipleObjects]
+ internal class XericStyleButtonEditor : SelectableEditor
+ {
+ private SerializedProperty m_InteractableProp;
+ private SerializedProperty m_TargetGraphicProp;
+ private SerializedProperty m_NavigationProp;
+
+ private SerializedProperty m_ColorStateProp;
+ private SerializedProperty m_TextureStateProp;
+ private SerializedProperty m_EnableStyleTransitionProp;
+ private SerializedProperty m_OnClickProp;
+
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+
+ m_InteractableProp = serializedObject.FindProperty("m_Interactable");
+ m_TargetGraphicProp = serializedObject.FindProperty("m_TargetGraphic");
+ m_NavigationProp = serializedObject.FindProperty("m_Navigation");
+
+ m_ColorStateProp = serializedObject.FindProperty("m_ColorState");
+ m_TextureStateProp = serializedObject.FindProperty("m_TextureState");
+ m_EnableStyleTransitionProp = serializedObject.FindProperty("m_EnableStyleTransition");
+ m_OnClickProp = serializedObject.FindProperty("m_OnClick");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+
+ // === 1. Interactable ===
+ EditorGUILayout.PropertyField(m_InteractableProp);
+
+ // === 2. 样式表状态块(替代 Transition + ColorBlock + SpriteState) ===
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("样式表配置(替代 Transition)", EditorStyles.boldLabel);
+
+ EditorGUILayout.PropertyField(m_ColorStateProp, new GUIContent("颜色状态块"), true);
+ EditorGUILayout.Space(4);
+ EditorGUILayout.PropertyField(m_TextureStateProp, new GUIContent("纹理状态块"), true);
+
+ if (m_ColorStateProp.isExpanded || m_TextureStateProp.isExpanded)
+ {
+ EditorGUILayout.Space(2);
+ EditorGUILayout.PropertyField(m_EnableStyleTransitionProp);
+ }
+
+ // === 3. Target Graphic ===
+ EditorGUILayout.Space();
+ EditorGUILayout.PropertyField(m_TargetGraphicProp);
+
+ Graphic graphic = m_TargetGraphicProp.objectReferenceValue as Graphic;
+ if (graphic == null)
+ graphic = (target as Selectable)?.GetComponent();
+ if (graphic == null)
+ EditorGUILayout.HelpBox("你需要指定一个 Graphic 目标才能使用样式表颜色过渡。", MessageType.Warning);
+
+ // === 4. Navigation ===
+ EditorGUILayout.Space();
+ EditorGUILayout.PropertyField(m_NavigationProp);
+
+ // === 5. onClick ===
+ EditorGUILayout.Space();
+ EditorGUILayout.PropertyField(m_OnClickProp);
+
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Editor/OverrideUI/XericStyleButtonEditor.cs.meta b/Editor/OverrideUI/XericStyleButtonEditor.cs.meta
new file mode 100644
index 0000000..bcfb760
--- /dev/null
+++ b/Editor/OverrideUI/XericStyleButtonEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 93a0ee7d0089fb141a33b3db631b645a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/OverrideUI/XericStyleImageEditor.cs b/Editor/OverrideUI/XericStyleImageEditor.cs
new file mode 100644
index 0000000..f345d08
--- /dev/null
+++ b/Editor/OverrideUI/XericStyleImageEditor.cs
@@ -0,0 +1,47 @@
+using UnityEditor;
+using UnityEditor.UI;
+
+using XericUI.OverrideUI;
+
+namespace XericUIEditor.OverrideUI
+{
+ ///
+ /// XericStyleImage 的 Inspector 编辑器——继承 GraphicEditor,
+ /// 额外展示颜色、纹理、材质三个 StyleField 和自动刷新开关。
+ ///
+ [CustomEditor(typeof(XericStyleImage), true)]
+ [CanEditMultipleObjects]
+ internal class XericStyleImageEditor : GraphicEditor
+ {
+ private SerializedProperty m_ColorStyleProp;
+ private SerializedProperty m_TextureStyleProp;
+ private SerializedProperty m_MaterialStyleProp;
+ private SerializedProperty m_AutoRefreshProp;
+
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+
+ m_ColorStyleProp = serializedObject.FindProperty("m_ColorStyle");
+ m_TextureStyleProp = serializedObject.FindProperty("m_TextureStyle");
+ m_MaterialStyleProp = serializedObject.FindProperty("m_MaterialStyle");
+ m_AutoRefreshProp = serializedObject.FindProperty("m_AutoRefresh");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ base.OnInspectorGUI();
+ EditorGUILayout.Space();
+
+ serializedObject.Update();
+
+ EditorGUILayout.LabelField("样式表配置", EditorStyles.boldLabel);
+ EditorGUILayout.PropertyField(m_ColorStyleProp);
+ EditorGUILayout.PropertyField(m_TextureStyleProp);
+ EditorGUILayout.PropertyField(m_MaterialStyleProp);
+ EditorGUILayout.PropertyField(m_AutoRefreshProp);
+
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Editor/OverrideUI/XericStyleImageEditor.cs.meta b/Editor/OverrideUI/XericStyleImageEditor.cs.meta
new file mode 100644
index 0000000..7893ef6
--- /dev/null
+++ b/Editor/OverrideUI/XericStyleImageEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8f9db8103c790b3449487b618b5a5db7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/OverrideUI/XericStyleToggleEditor.cs b/Editor/OverrideUI/XericStyleToggleEditor.cs
new file mode 100644
index 0000000..74d8268
--- /dev/null
+++ b/Editor/OverrideUI/XericStyleToggleEditor.cs
@@ -0,0 +1,135 @@
+using UnityEditor;
+using UnityEditor.SceneManagement;
+using UnityEditor.UI;
+
+using UnityEngine;
+
+using UnityEngine.UI;
+
+using XericUI.OverrideUI;
+
+namespace XericUIEditor.OverrideUI
+{
+ ///
+ /// XericStyleToggle 的 Inspector 编辑器。
+ /// 完全替换 Selectable 的 Transition/ColorBlock/SpriteState 区域为 StyleSheet 状态块,
+ /// 保留 Interactable、Navigation、isOn、graphic、group、onValueChanged 等基础功能。
+ ///
+ [CustomEditor(typeof(XericStyleToggle), true)]
+ [CanEditMultipleObjects]
+ internal class XericStyleToggleEditor : SelectableEditor
+ {
+ // Selectable 基础
+ private SerializedProperty m_InteractableProp;
+ private SerializedProperty m_NavigationProp;
+
+ // Toggle 基础
+ private SerializedProperty m_IsOnProp;
+ private SerializedProperty m_TransitionProp;
+ private SerializedProperty m_GraphicProp;
+ private SerializedProperty m_GroupProp;
+ private SerializedProperty m_OnValueChangedProp;
+
+ // StyleSheet 专用
+ private SerializedProperty m_ColorStateProp;
+ private SerializedProperty m_TextureStateProp;
+ private SerializedProperty m_OnOverrideColorProp;
+ private SerializedProperty m_OnOverrideTextureProp;
+ private SerializedProperty m_CheckmarkStyleProp;
+ private SerializedProperty m_EnableStyleTransitionProp;
+
+ protected override void OnEnable()
+ {
+ base.OnEnable();
+
+ m_InteractableProp = serializedObject.FindProperty("m_Interactable");
+ m_NavigationProp = serializedObject.FindProperty("m_Navigation");
+
+ m_IsOnProp = serializedObject.FindProperty("m_IsOn");
+ m_TransitionProp = serializedObject.FindProperty("toggleTransition");
+ m_GraphicProp = serializedObject.FindProperty("graphic");
+ m_GroupProp = serializedObject.FindProperty("m_Group");
+ m_OnValueChangedProp = serializedObject.FindProperty("onValueChanged");
+
+ m_ColorStateProp = serializedObject.FindProperty("m_ColorState");
+ m_TextureStateProp = serializedObject.FindProperty("m_TextureState");
+ m_OnOverrideColorProp = serializedObject.FindProperty("m_OnOverrideColor");
+ m_OnOverrideTextureProp = serializedObject.FindProperty("m_OnOverrideTexture");
+ m_CheckmarkStyleProp = serializedObject.FindProperty("m_CheckmarkStyle");
+ m_EnableStyleTransitionProp = serializedObject.FindProperty("m_EnableStyleTransition");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+ XericStyleToggle toggle = target as XericStyleToggle;
+
+ // === 1. Interactable ===
+ EditorGUILayout.PropertyField(m_InteractableProp);
+
+ // === 2. 样式表状态块(替代 Transition + ColorBlock + SpriteState) ===
+ EditorGUILayout.Space();
+ EditorGUILayout.LabelField("样式表配置(替代 Transition)", EditorStyles.boldLabel);
+
+ EditorGUILayout.PropertyField(m_ColorStateProp, new GUIContent("颜色状态块"), true);
+ EditorGUILayout.Space(4);
+ EditorGUILayout.PropertyField(m_TextureStateProp, new GUIContent("纹理状态块"), true);
+
+ bool anyExpanded = m_ColorStateProp.isExpanded || m_TextureStateProp.isExpanded;
+ if (anyExpanded)
+ {
+ EditorGUILayout.Space(2);
+ EditorGUILayout.PropertyField(m_OnOverrideColorProp);
+ EditorGUILayout.PropertyField(m_OnOverrideTextureProp);
+ EditorGUILayout.PropertyField(m_CheckmarkStyleProp);
+ EditorGUILayout.PropertyField(m_EnableStyleTransitionProp);
+ }
+
+ // === 3. isOn ===
+ EditorGUILayout.Space();
+ EditorGUI.BeginChangeCheck();
+ EditorGUILayout.PropertyField(m_IsOnProp);
+ if (EditorGUI.EndChangeCheck())
+ {
+ if (!Application.isPlaying)
+ EditorSceneManager.MarkSceneDirty(toggle!.gameObject.scene);
+
+ ToggleGroup group = m_GroupProp.objectReferenceValue as ToggleGroup;
+ toggle!.isOn = m_IsOnProp.boolValue;
+
+ if (group != null && group.isActiveAndEnabled && toggle!.IsActive())
+ {
+ if (toggle!.isOn || (!group.AnyTogglesOn() && !group.allowSwitchOff))
+ {
+ toggle!.isOn = true;
+ group.NotifyToggleOn(toggle!);
+ }
+ }
+ }
+
+ // === 4. Toggle 切换效果 & Graphic ===
+ EditorGUILayout.PropertyField(m_TransitionProp);
+ EditorGUILayout.PropertyField(m_GraphicProp);
+
+ // === 5. Group ===
+ EditorGUI.BeginChangeCheck();
+ EditorGUILayout.PropertyField(m_GroupProp);
+ if (EditorGUI.EndChangeCheck())
+ {
+ if (!Application.isPlaying)
+ EditorSceneManager.MarkSceneDirty(toggle!.gameObject.scene);
+ toggle!.group = m_GroupProp.objectReferenceValue as ToggleGroup;
+ }
+
+ // === 6. Navigation ===
+ EditorGUILayout.Space();
+ EditorGUILayout.PropertyField(m_NavigationProp);
+
+ // === 7. onValueChanged ===
+ EditorGUILayout.Space();
+ EditorGUILayout.PropertyField(m_OnValueChangedProp);
+
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+}
diff --git a/Editor/OverrideUI/XericStyleToggleEditor.cs.meta b/Editor/OverrideUI/XericStyleToggleEditor.cs.meta
new file mode 100644
index 0000000..2398ca3
--- /dev/null
+++ b/Editor/OverrideUI/XericStyleToggleEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 333577a929d25434e87d0a53360fce29
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/SuperStyleSheet.meta b/Editor/SuperStyleSheet.meta
new file mode 100644
index 0000000..668124b
--- /dev/null
+++ b/Editor/SuperStyleSheet.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 44c3980d2307e514a869459f8153cda2
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/SuperStyleSheet/StyleFieldDrawer.cs b/Editor/SuperStyleSheet/StyleFieldDrawer.cs
new file mode 100644
index 0000000..888e6db
--- /dev/null
+++ b/Editor/SuperStyleSheet/StyleFieldDrawer.cs
@@ -0,0 +1,235 @@
+using UnityEditor;
+
+using UnityEngine;
+
+using XericLibrary.Runtime.SuperStyleSheet;
+
+namespace XericUIEditor
+{
+ ///
+ /// StyleField 的自定义 Inspector 属性绘制器。
+ ///
+ /// 默认折叠状态:仅显示样式路径下拉框。
+ /// 展开状态:显示命名空间、路径、资源值类型(有值时)、注解描述(有注解时)。
+ /// 无值/无注解的行不显示。
+ ///
+ [CustomPropertyDrawer(typeof(StyleField))]
+ public class StyleFieldDrawer : PropertyDrawer
+ {
+ private const float LINE_HEIGHT = 18f;
+ private const float PADDING = 2f;
+ private const float FOLD_BUTTON_WIDTH = 14f;
+
+ /// 折叠状态(按 property path 缓存)
+ private static readonly System.Collections.Generic.Dictionary s_FoldoutStates
+ = new System.Collections.Generic.Dictionary();
+
+ public override float GetPropertyHeight(SerializedProperty property, GUIContent label)
+ {
+ string key = GetFoldKey(property);
+ bool expanded = s_FoldoutStates.TryGetValue(key, out bool f) && f;
+
+ if (!expanded)
+ return LINE_HEIGHT + PADDING * 2;
+
+ // 基础:命名空间 + 路径 = 2 行
+ int rowCount = 2;
+
+ // 有值才显示值类型行
+ string ns = GetNamespace(property);
+ string path = property.FindPropertyRelative("StylePath").stringValue;
+ StyleValue val = StyleManager.GetValue(ns, path);
+ if (val != null && val.ValueType != null)
+ rowCount++;
+
+ // 有注解才显示注解行
+ var annotations = StyleManager.GetAnnotations(ns, path);
+ if (annotations != null && annotations.Count > 0)
+ rowCount++;
+
+ return (LINE_HEIGHT + PADDING) * rowCount + PADDING;
+ }
+
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+ string key = GetFoldKey(property);
+ bool expanded = s_FoldoutStates.TryGetValue(key, out bool fold) && fold;
+
+ // 获取子属性
+ SerializedProperty nsProp = property.FindPropertyRelative("Namespace");
+ SerializedProperty pathProp = property.FindPropertyRelative("StylePath");
+
+ // === 标题行:折叠箭头 + 标签 + 路径下拉 ===
+ Rect headerRect = new Rect(position.x, position.y + PADDING, position.width, LINE_HEIGHT);
+
+ // 折叠按钮
+ Rect foldRect = new Rect(headerRect.x, headerRect.y, FOLD_BUTTON_WIDTH, LINE_HEIGHT);
+ bool newFold = EditorGUI.Foldout(foldRect, expanded, GUIContent.none, true);
+ if (newFold != expanded)
+ {
+ s_FoldoutStates[key] = newFold;
+ expanded = newFold;
+ }
+
+ // 标签
+ Rect labelRect = new Rect(headerRect.x + FOLD_BUTTON_WIDTH, headerRect.y,
+ EditorGUIUtility.labelWidth - FOLD_BUTTON_WIDTH, LINE_HEIGHT);
+ EditorGUI.LabelField(labelRect, label);
+
+ // 路径下拉框(始终可见)
+ Rect pathRect = new Rect(headerRect.x + EditorGUIUtility.labelWidth + 4f, headerRect.y,
+ headerRect.width - EditorGUIUtility.labelWidth - 8f, LINE_HEIGHT);
+
+ string currentNs = GetNamespace(property);
+
+ DrawPathDropdown(pathRect, pathProp, currentNs);
+
+ if (!expanded)
+ return;
+
+ // === 展开区域 ===
+ float y = headerRect.y + LINE_HEIGHT + PADDING;
+ float indent = 16f;
+ float fieldWidth = position.width - indent;
+
+ // --- 命名空间 ---
+ Rect nsLabelRect = new Rect(position.x + indent, y, 60f, LINE_HEIGHT);
+ Rect nsFieldRect = new Rect(position.x + indent + 64f, y, fieldWidth - 64f, LINE_HEIGHT);
+ EditorGUI.LabelField(nsLabelRect, "命名空间");
+ DrawNamespaceDropdown(nsFieldRect, nsProp);
+ y += LINE_HEIGHT + PADDING;
+
+ // --- 路径 ---
+ Rect pathLabelRect = new Rect(position.x + indent, y, 60f, LINE_HEIGHT);
+ Rect pathFieldRect = new Rect(position.x + indent + 64f, y, fieldWidth - 64f, LINE_HEIGHT);
+ EditorGUI.LabelField(pathLabelRect, "路径");
+ DrawPathDropdown(pathFieldRect, pathProp, currentNs);
+ y += LINE_HEIGHT + PADDING;
+
+ // --- 资源值类型(仅在有值时显示) ---
+ StyleValue currentValue = StyleManager.GetValue(currentNs, pathProp.stringValue);
+ if (currentValue != null && currentValue.ValueType != null)
+ {
+ Rect valLabelRect = new Rect(position.x + indent, y, 80f, LINE_HEIGHT);
+ Rect valFieldRect = new Rect(position.x + indent + 84f, y, fieldWidth - 84f, LINE_HEIGHT);
+
+ EditorGUI.LabelField(valLabelRect, "值类型");
+
+ string typeDisplay = GetShortTypeName(currentValue.ValueType);
+ string valuePreview = currentValue.RawValue != null
+ ? currentValue.RawValue.ToString()
+ : "";
+
+ EditorGUI.LabelField(valFieldRect, $"{typeDisplay} = {valuePreview}");
+ y += LINE_HEIGHT + PADDING;
+ }
+
+ // --- 注解描述(仅在有注解时显示) ---
+ var annotations = StyleManager.GetAnnotations(currentNs, pathProp.stringValue);
+ if (annotations != null && annotations.Count > 0)
+ {
+ Rect annoLabelRect = new Rect(position.x + indent, y, 80f, LINE_HEIGHT);
+ Rect annoFieldRect = new Rect(position.x + indent + 84f, y, fieldWidth - 84f, LINE_HEIGHT);
+
+ EditorGUI.LabelField(annoLabelRect, "注解");
+
+ // 优先显示 @Desc
+ annotations.TryGetValue("Desc", out string desc);
+ if (!string.IsNullOrEmpty(desc))
+ EditorGUI.LabelField(annoFieldRect, desc);
+ else
+ {
+ foreach (var a in annotations)
+ {
+ EditorGUI.LabelField(annoFieldRect, $"@{a.Key}: {a.Value}");
+ break;
+ }
+ }
+ }
+
+ // 变更检测
+ if (GUI.changed)
+ {
+ property.serializedObject.ApplyModifiedProperties();
+ }
+ }
+
+ #region 下拉框绘制
+
+ private void DrawNamespaceDropdown(Rect rect, SerializedProperty nsProp)
+ {
+ var namespaces = StyleManager.GetAvailableNamespaces();
+ if (namespaces.Count == 0)
+ namespaces.Add("default");
+
+ int currentIndex = namespaces.IndexOf(nsProp.stringValue);
+ if (currentIndex < 0) currentIndex = 0;
+
+ int newIndex = EditorGUI.Popup(rect, currentIndex, namespaces.ToArray());
+ if (newIndex >= 0 && newIndex < namespaces.Count)
+ {
+ nsProp.stringValue = namespaces[newIndex];
+ }
+ }
+
+ private void DrawPathDropdown(Rect rect, SerializedProperty pathProp, string namespace_)
+ {
+ var paths = StyleManager.GetPathsForNamespace(namespace_);
+
+ var displayOptions = new System.Collections.Generic.List();
+ if (paths.Count == 0)
+ {
+ displayOptions.Add("(无可用路径)");
+ }
+ else
+ {
+ foreach (var p in paths)
+ {
+ StyleValue val = StyleManager.GetValue(namespace_, p);
+ string typeStr = val?.ValueType != null ? GetShortTypeName(val.ValueType) : "?";
+ string valStr = val?.RawValue != null ? val.RawValue.ToString() : "";
+ if (valStr.Length > 20) valStr = valStr.Substring(0, 17) + "...";
+ displayOptions.Add($"{p} [{typeStr}] {valStr}");
+ }
+ }
+
+ int currentIndex = System.Math.Max(0, paths.IndexOf(pathProp.stringValue));
+
+ int newIndex = EditorGUI.Popup(rect, currentIndex, displayOptions.ToArray());
+ if (newIndex >= 0 && newIndex < paths.Count)
+ {
+ pathProp.stringValue = paths[newIndex];
+ }
+ }
+
+ #endregion
+
+ #region 工具方法
+
+ private static string GetFoldKey(SerializedProperty property)
+ {
+ return $"{property.serializedObject.targetObject.GetInstanceID()}_{property.propertyPath}";
+ }
+
+ private static string GetNamespace(SerializedProperty property)
+ {
+ string ns = property.FindPropertyRelative("Namespace").stringValue;
+ return string.IsNullOrEmpty(ns) ? "default" : ns;
+ }
+
+ private static string GetShortTypeName(System.Type type)
+ {
+ if (type == null) return "?";
+ switch (type.Name)
+ {
+ case "Single": return "float";
+ case "Int32": return "int";
+ case "String": return "string";
+ case "Boolean": return "bool";
+ default: return type.Name;
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Editor/SuperStyleSheet/StyleFieldDrawer.cs.meta b/Editor/SuperStyleSheet/StyleFieldDrawer.cs.meta
new file mode 100644
index 0000000..ce7f7a4
--- /dev/null
+++ b/Editor/SuperStyleSheet/StyleFieldDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8ee1f2c601403504faea388e1f91f07b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/XTable.meta b/Editor/XTable.meta
new file mode 100644
index 0000000..c33bc99
--- /dev/null
+++ b/Editor/XTable.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 4110db94896e7434a8eb8182809da2ad
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/XTable/XTableEditorOverlay.cs b/Editor/XTable/XTableEditorOverlay.cs
new file mode 100644
index 0000000..27b632a
--- /dev/null
+++ b/Editor/XTable/XTableEditorOverlay.cs
@@ -0,0 +1,172 @@
+#if UNITY_EDITOR
+
+using UnityEditor;
+
+using UnityEngine;
+
+using XericUI.XTable.Rendering.Component;
+
+namespace XericUIEditor.XTable
+{
+ ///
+ /// 场景视图表格编辑覆盖工具——选中表格组件后,在世界编辑器视口中显示网格线和编辑辅助 UI。
+ /// 包含合并工具、拖拽引用工具、单元格坐标提示等。
+ ///
+ public static class XTableEditorOverlay
+ {
+ /// 是否启用覆盖层(可通过菜单开关)
+ private static bool s_Enabled = true;
+
+ private const string MENU_PATH = "Xeric UI/表格编辑器覆盖层";
+
+ [MenuItem(MENU_PATH, false, 200)]
+ private static void ToggleOverlay()
+ {
+ s_Enabled = !s_Enabled;
+ Menu.SetChecked(MENU_PATH, s_Enabled);
+ SceneView.RepaintAll();
+ }
+
+ [MenuItem(MENU_PATH, true)]
+ private static bool ToggleOverlayValidate()
+ {
+ Menu.SetChecked(MENU_PATH, s_Enabled);
+ return true;
+ }
+
+ ///
+ /// 在 Scene View 中绘制表格编辑辅助线。
+ /// 调用时机:挂到 SceneView.duringSceneGui 事件上。
+ /// 示例:SceneView.duringSceneGui += OnSceneGUI;
+ ///
+ public static void OnSceneGUI(SceneView sceneView)
+ {
+ if (!s_Enabled) return;
+
+ // 获取当前选中的表格组件
+ GameObject selected = Selection.activeGameObject;
+ if (selected == null) return;
+
+ XericUIActionTable table = selected.GetComponent();
+ if (table == null) return;
+
+ RectTransform rect = table.GetComponent();
+ if (rect == null) return;
+
+ DrawGridOverlay(table, rect);
+ DrawCellInfo(table, rect, sceneView);
+ }
+
+ /// 绘制网格线
+ private static void DrawGridOverlay(XericUIActionTable table, RectTransform rect)
+ {
+ float[] rowHeights = table.RowHeights;
+ float[] colWidths = table.ColWidths;
+ if (rowHeights == null || colWidths == null) return;
+
+ Vector3[] corners = new Vector3[4];
+ rect.GetWorldCorners(corners);
+
+ Vector3 topLeft = corners[1];
+ float totalWidth = rect.rect.width;
+ float totalHeight = rect.rect.height;
+
+ Handles.color = new Color(0.5f, 0.5f, 0.5f, 0.3f);
+
+ // 绘制行分割线
+ float accumulatedY = 0f;
+ for (int r = 0; r < rowHeights.Length && accumulatedY < totalHeight; r++)
+ {
+ float y = topLeft.y - accumulatedY - rowHeights[r];
+ Vector3 start = new Vector3(topLeft.x, y, topLeft.z);
+ Vector3 end = new Vector3(topLeft.x + Mathf.Min(totalWidth, 500f), y, topLeft.z);
+ Handles.DrawLine(start, end);
+ accumulatedY += rowHeights[r];
+ }
+
+ // 绘制列分割线
+ float accumulatedX = 0f;
+ for (int c = 0; c < colWidths.Length && accumulatedX < totalWidth; c++)
+ {
+ float x = topLeft.x + accumulatedX + colWidths[c];
+ Vector3 start = new Vector3(x, topLeft.y, topLeft.z);
+ Vector3 end = new Vector3(x, topLeft.y - Mathf.Min(totalHeight, 500f), topLeft.z);
+ Handles.DrawLine(start, end);
+ accumulatedX += colWidths[c];
+ }
+ }
+
+ /// 绘制单元格坐标提示
+ private static void DrawCellInfo(XericUIActionTable table, RectTransform rect, SceneView sceneView)
+ {
+ int selRow = table.SelectedRow;
+ int selCol = table.SelectedCol;
+ if (selRow < 0 || selCol < 0) return;
+
+ float[] rowHeights = table.RowHeights;
+ float[] colWidths = table.ColWidths;
+ if (rowHeights == null || colWidths == null) return;
+
+ // 计算选中单元格的世界位置
+ float cellX = 0f;
+ for (int c = 0; c < selCol && c < colWidths.Length; c++)
+ cellX += colWidths[c];
+
+ float cellY = 0f;
+ for (int r = 0; r < selRow && r < rowHeights.Length; r++)
+ cellY += rowHeights[r];
+
+ Vector3[] corners = new Vector3[4];
+ rect.GetWorldCorners(corners);
+ Vector3 topLeft = corners[1];
+
+ Vector3 cellCenter = new Vector3(
+ topLeft.x + cellX + (selCol < colWidths.Length ? colWidths[selCol] * 0.5f : 0),
+ topLeft.y - cellY - (selRow < rowHeights.Length ? rowHeights[selRow] * 0.5f : 0),
+ topLeft.z);
+
+ // 绘制选中高亮框
+ Handles.color = new Color(0.2f, 0.6f, 1f, 0.5f);
+ float cellW = selCol < colWidths.Length ? colWidths[selCol] : 50f;
+ float cellH = selRow < rowHeights.Length ? rowHeights[selRow] : 30f;
+ Vector3 cellTL = new Vector3(cellCenter.x - cellW * 0.5f, cellCenter.y + cellH * 0.5f, cellCenter.z);
+ Vector3 cellTR = new Vector3(cellCenter.x + cellW * 0.5f, cellCenter.y + cellH * 0.5f, cellCenter.z);
+ Vector3 cellBR = new Vector3(cellCenter.x + cellW * 0.5f, cellCenter.y - cellH * 0.5f, cellCenter.z);
+ Vector3 cellBL = new Vector3(cellCenter.x - cellW * 0.5f, cellCenter.y - cellH * 0.5f, cellCenter.z);
+ Handles.DrawSolidRectangleWithOutline(
+ new Vector3[] { cellTL, cellTR, cellBR, cellBL },
+ new Color(0.2f, 0.6f, 1f, 0.2f),
+ new Color(0.2f, 0.6f, 1f, 0.8f));
+
+ // 显示单元格坐标标签
+ Handles.Label(cellCenter + Vector3.up * 10f,
+ $"({selRow}, {selCol})",
+ new GUIStyle(EditorStyles.label)
+ {
+ normal = { textColor = Color.white },
+ fontStyle = FontStyle.Bold
+ });
+
+ // 工具栏按钮(在 Scene View 顶部绘制)
+ Handles.BeginGUI();
+ GUILayout.BeginArea(new Rect(10, 10, 200, 80));
+ GUILayout.BeginVertical("表格编辑工具", GUI.skin.window);
+
+ if (GUILayout.Button("合并选区"))
+ {
+ Debug.Log($"[XTable] 合并起始于 ({selRow}, {selCol})");
+ }
+
+ if (GUILayout.Button("取消合并"))
+ {
+ Debug.Log($"[XTable] 取消合并 ({selRow}, {selCol})");
+ }
+
+ GUILayout.EndVertical();
+ GUILayout.EndArea();
+ Handles.EndGUI();
+ }
+ }
+}
+
+#endif
diff --git a/Editor/XTable/XTableEditorOverlay.cs.meta b/Editor/XTable/XTableEditorOverlay.cs.meta
new file mode 100644
index 0000000..02a471d
--- /dev/null
+++ b/Editor/XTable/XTableEditorOverlay.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 220060be4d5df6a40afa23e4a7af1d64
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Editor/XTable/XericUIActionTableEditor.cs b/Editor/XTable/XericUIActionTableEditor.cs
new file mode 100644
index 0000000..d75153e
--- /dev/null
+++ b/Editor/XTable/XericUIActionTableEditor.cs
@@ -0,0 +1,334 @@
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.UI;
+
+using XericUI.XTable.Rendering.Component;
+
+namespace XericUIEditor.XTable
+{
+ ///
+ /// XericUIActionTable 的 Inspector 编辑器——
+ /// 折叠分组:[样式设定] [尺寸设定] [编辑测试] [数据浏览]。
+ /// 使用 EditorGUILayout.Foldout 避免与数组 PropertyField 内部的折叠冲突。
+ ///
+ [CustomEditor(typeof(XericUIActionTable))]
+ public class XericUIActionTableEditor : Editor
+ {
+ private XericUIActionTable m_Table;
+
+ private SerializedProperty m_RowHeightsProp;
+ private SerializedProperty m_ColWidthsProp;
+ private SerializedProperty m_DefaultStyleNsProp;
+ private SerializedProperty m_ScrollRectProp;
+
+ // 折叠状态
+ private static bool s_StyleFoldout = true;
+ private static bool s_SizeFoldout = true;
+ private static bool s_EditTestFoldout;
+ private static bool s_DataBrowseFoldout;
+
+ // 编辑测试参数
+ private int m_BatchRowCount = 10;
+ private int m_BatchColCount = 10;
+ private float m_BatchCellHeight = 40f;
+ private float m_BatchCellWidth = 100f;
+
+ private int m_MergeStartRow;
+ private int m_MergeStartCol;
+ private int m_MergeRowSpan = 2;
+ private int m_MergeColSpan = 2;
+
+ private int m_EditRow;
+ private int m_EditCol;
+ private string m_EditText = "";
+
+ private void OnEnable()
+ {
+ m_Table = (XericUIActionTable)target;
+
+ m_RowHeightsProp = serializedObject.FindProperty("m_RowHeights");
+ m_ColWidthsProp = serializedObject.FindProperty("m_ColWidths");
+ m_DefaultStyleNsProp = serializedObject.FindProperty("m_DefaultStyleNamespace");
+ m_ScrollRectProp = serializedObject.FindProperty("m_ScrollRect");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ serializedObject.Update();
+
+ // ===== 样式设定 =====
+ s_StyleFoldout = EditorGUILayout.Foldout(s_StyleFoldout, "样式设定", true);
+ if (s_StyleFoldout)
+ {
+ EditorGUI.indentLevel++;
+ if (m_DefaultStyleNsProp != null)
+ EditorGUILayout.PropertyField(m_DefaultStyleNsProp, new GUIContent("默认命名空间"));
+ if (m_ScrollRectProp != null)
+ EditorGUILayout.PropertyField(m_ScrollRectProp, new GUIContent("ScrollRect"));
+ EditorGUI.indentLevel--;
+ }
+
+ // ===== 尺寸设定 =====
+ EditorGUILayout.Space(4);
+ s_SizeFoldout = EditorGUILayout.Foldout(s_SizeFoldout, "尺寸设定", true);
+ if (s_SizeFoldout)
+ {
+ EditorGUI.indentLevel++;
+ if (m_RowHeightsProp != null)
+ EditorGUILayout.PropertyField(m_RowHeightsProp, new GUIContent("行高"), true);
+ if (m_ColWidthsProp != null)
+ EditorGUILayout.PropertyField(m_ColWidthsProp, new GUIContent("列宽"), true);
+ EditorGUI.indentLevel--;
+ }
+
+ // ===== 编辑测试 =====
+ EditorGUILayout.Space(4);
+ s_EditTestFoldout = EditorGUILayout.Foldout(s_EditTestFoldout, "编辑测试", true);
+ if (s_EditTestFoldout)
+ {
+ EditorGUI.indentLevel++;
+
+ EditorGUILayout.LabelField("批量生成尺寸", EditorStyles.miniBoldLabel);
+ m_BatchRowCount = EditorGUILayout.IntField("行数", m_BatchRowCount);
+ m_BatchColCount = EditorGUILayout.IntField("列数", m_BatchColCount);
+ m_BatchCellHeight = EditorGUILayout.FloatField("单元格高度", m_BatchCellHeight);
+ m_BatchCellWidth = EditorGUILayout.FloatField("单元格宽度", m_BatchCellWidth);
+ if (GUILayout.Button("应用批量尺寸", GUILayout.Height(22)))
+ ApplyBatchRowColSizes();
+
+ EditorGUILayout.Space(6);
+
+ EditorGUILayout.LabelField("合并单元格", EditorStyles.miniBoldLabel);
+ m_MergeStartRow = EditorGUILayout.IntField("起始行", m_MergeStartRow);
+ m_MergeStartCol = EditorGUILayout.IntField("起始列", m_MergeStartCol);
+ m_MergeRowSpan = EditorGUILayout.IntField("行跨度", m_MergeRowSpan);
+ m_MergeColSpan = EditorGUILayout.IntField("列跨度", m_MergeColSpan);
+ EditorGUILayout.BeginHorizontal();
+ if (GUILayout.Button("合并", GUILayout.Height(22)))
+ EditorApplication.delayCall += () => m_Table.MergeCells(m_MergeStartRow, m_MergeStartCol, m_MergeRowSpan, m_MergeColSpan);
+ if (GUILayout.Button("取消合并", GUILayout.Height(22)))
+ EditorApplication.delayCall += () => m_Table.UnmergeCells(m_MergeStartRow, m_MergeStartCol);
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUILayout.Space(6);
+
+ EditorGUILayout.LabelField("单元格编辑", EditorStyles.miniBoldLabel);
+ m_EditRow = EditorGUILayout.IntField("行", m_EditRow);
+ m_EditCol = EditorGUILayout.IntField("列", m_EditCol);
+
+ var existing = m_Table.GetCellData(m_EditRow, m_EditCol);
+ if (existing != null && !string.IsNullOrEmpty(existing.Text) && string.IsNullOrEmpty(m_EditText))
+ m_EditText = existing.Text;
+
+ m_EditText = EditorGUILayout.TextField("文本内容", m_EditText);
+
+ EditorGUILayout.BeginHorizontal();
+ if (GUILayout.Button("应用文本", GUILayout.Height(22)))
+ EditorApplication.delayCall += () => m_Table.SetCellText(m_EditRow, m_EditCol, m_EditText);
+ if (GUILayout.Button("选中此格", GUILayout.Height(22)))
+ EditorApplication.delayCall += () => m_Table.SelectCell(m_EditRow, m_EditCol);
+ if (GUILayout.Button("清除选中", GUILayout.Height(22)))
+ EditorApplication.delayCall += () => m_Table.ClearSelection();
+ EditorGUILayout.EndHorizontal();
+
+ EditorGUI.indentLevel--;
+ }
+
+ // ===== 数据浏览 =====
+ EditorGUILayout.Space(4);
+ s_DataBrowseFoldout = EditorGUILayout.Foldout(s_DataBrowseFoldout, "数据浏览", true);
+ if (s_DataBrowseFoldout)
+ {
+ EditorGUI.indentLevel++;
+
+ if (m_Table.TableData != null)
+ {
+ EditorGUI.BeginDisabledGroup(true);
+ EditorGUILayout.IntField("块数量", m_Table.TableData.BlockCount);
+ EditorGUILayout.TextField("块尺寸",
+ $"{m_Table.TableData.BlockSizeX} × {m_Table.TableData.BlockSizeY}");
+ EditorGUILayout.TextField("选中单元格",
+ m_Table.SelectedRow >= 0 ? $"({m_Table.SelectedRow}, {m_Table.SelectedCol})" : "(未选中)");
+ EditorGUI.EndDisabledGroup();
+ }
+ else
+ {
+ EditorGUILayout.HelpBox("TableData 为空——Awake() 执行后会自动创建默认实例。", MessageType.Info);
+ }
+
+ EditorGUILayout.Space(4);
+ if (GUILayout.Button("手动刷新视图", GUILayout.Height(22)))
+ EditorApplication.delayCall += () => m_Table.RefreshView();
+
+ EditorGUI.indentLevel--;
+ }
+
+ serializedObject.ApplyModifiedProperties();
+ }
+
+ private void ApplyBatchRowColSizes()
+ {
+ serializedObject.Update();
+
+ if (m_RowHeightsProp != null)
+ {
+ m_RowHeightsProp.arraySize = m_BatchRowCount;
+ for (int i = 0; i < m_BatchRowCount; i++)
+ m_RowHeightsProp.GetArrayElementAtIndex(i).floatValue = m_BatchCellHeight;
+ }
+
+ if (m_ColWidthsProp != null)
+ {
+ m_ColWidthsProp.arraySize = m_BatchColCount;
+ for (int i = 0; i < m_BatchColCount; i++)
+ m_ColWidthsProp.GetArrayElementAtIndex(i).floatValue = m_BatchCellWidth;
+ }
+
+ serializedObject.ApplyModifiedProperties();
+ }
+
+ #region Hierarchy 创建菜单
+
+ [MenuItem("GameObject/Xeric UI/Table/Xeric UI Action Table", false, 60)]
+ private static void CreateTable(MenuCommand menuCommand)
+ {
+ // 1. 创建表格内容节点
+ GameObject contentGo = new GameObject("Content",
+ typeof(RectTransform), typeof(CanvasRenderer), typeof(XericUIActionTable));
+ RectTransform contentRt = contentGo.GetComponent();
+ contentRt.anchorMin = new Vector2(0, 1);
+ contentRt.anchorMax = new Vector2(0, 1);
+ contentRt.pivot = new Vector2(0, 1);
+ contentRt.anchoredPosition = Vector2.zero;
+ contentRt.sizeDelta = new Vector2(800, 600);
+ XericUIActionTable table = contentGo.GetComponent();
+
+ // 2. 创建 Viewport(Mask 裁剪区域)
+ GameObject viewportGo = new GameObject("Viewport",
+ typeof(RectTransform), typeof(Image), typeof(Mask));
+ viewportGo.GetComponent().color = new Color(1, 1, 1, 0.01f);
+ RectTransform vpRt = viewportGo.GetComponent();
+ vpRt.anchorMin = Vector2.zero;
+ vpRt.anchorMax = Vector2.one;
+ vpRt.sizeDelta = Vector2.zero;
+ contentRt.SetParent(vpRt, false);
+
+ // 3. 创建水平滚动条
+ GameObject hScrollbarGo = new GameObject("Scrollbar Horizontal",
+ typeof(RectTransform), typeof(Scrollbar), typeof(Image));
+ RectTransform hBarRt = hScrollbarGo.GetComponent();
+ hBarRt.anchorMin = new Vector2(0, 0);
+ hBarRt.anchorMax = new Vector2(1, 0);
+ hBarRt.pivot = new Vector2(0.5f, 0);
+ hBarRt.anchoredPosition = new Vector2(0, 4);
+ hBarRt.sizeDelta = new Vector2(-16, 20);
+ Image hBarImg = hScrollbarGo.GetComponent();
+ hBarImg.color = new Color(0.15f, 0.15f, 0.15f, 0.5f);
+ Scrollbar hBar = hScrollbarGo.GetComponent();
+ hBar.direction = Scrollbar.Direction.LeftToRight;
+
+ GameObject hSlidingGo = new GameObject("Sliding Area",
+ typeof(RectTransform));
+ hSlidingGo.transform.SetParent(hScrollbarGo.transform, false);
+ RectTransform hSlidingRt = hSlidingGo.GetComponent();
+ hSlidingRt.anchorMin = Vector2.zero;
+ hSlidingRt.anchorMax = Vector2.one;
+ hSlidingRt.sizeDelta = new Vector2(-20, -20);
+
+ GameObject hHandleGo = new GameObject("Handle",
+ typeof(RectTransform), typeof(Image));
+ hHandleGo.transform.SetParent(hSlidingGo.transform, false);
+ RectTransform hHandleRt = hHandleGo.GetComponent();
+ hHandleRt.anchorMin = Vector2.zero;
+ hHandleRt.anchorMax = Vector2.one;
+ hHandleRt.sizeDelta = new Vector2(-20, -20);
+ hHandleGo.GetComponent().color = new Color(0.4f, 0.4f, 0.4f, 0.8f);
+ hBar.handleRect = hHandleRt;
+
+ // 4. 创建垂直滚动条
+ GameObject vScrollbarGo = new GameObject("Scrollbar Vertical",
+ typeof(RectTransform), typeof(Scrollbar), typeof(Image));
+ RectTransform vBarRt = vScrollbarGo.GetComponent();
+ vBarRt.anchorMin = new Vector2(1, 0);
+ vBarRt.anchorMax = new Vector2(1, 1);
+ vBarRt.pivot = new Vector2(1, 0.5f);
+ vBarRt.anchoredPosition = new Vector2(-4, 0);
+ vBarRt.sizeDelta = new Vector2(20, -16);
+ Image vBarImg = vScrollbarGo.GetComponent();
+ vBarImg.color = new Color(0.15f, 0.15f, 0.15f, 0.5f);
+ Scrollbar vBar = vScrollbarGo.GetComponent();
+ vBar.direction = Scrollbar.Direction.BottomToTop;
+
+ GameObject vSlidingGo = new GameObject("Sliding Area",
+ typeof(RectTransform));
+ vSlidingGo.transform.SetParent(vScrollbarGo.transform, false);
+ RectTransform vSlidingRt = vSlidingGo.GetComponent();
+ vSlidingRt.anchorMin = Vector2.zero;
+ vSlidingRt.anchorMax = Vector2.one;
+ vSlidingRt.sizeDelta = new Vector2(-20, -20);
+
+ GameObject vHandleGo = new GameObject("Handle",
+ typeof(RectTransform), typeof(Image));
+ vHandleGo.transform.SetParent(vSlidingGo.transform, false);
+ RectTransform vHandleRt = vHandleGo.GetComponent();
+ vHandleRt.anchorMin = Vector2.zero;
+ vHandleRt.anchorMax = Vector2.one;
+ vHandleRt.sizeDelta = new Vector2(-20, -20);
+ vHandleGo.GetComponent().color = new Color(0.4f, 0.4f, 0.4f, 0.8f);
+ vBar.handleRect = vHandleRt;
+
+ // 5. 创建 ScrollView 根节点
+ GameObject scrollGo = new GameObject("Xeric UI Action Table",
+ typeof(RectTransform), typeof(ScrollRect), typeof(Image));
+ RectTransform scrollRt = scrollGo.GetComponent();
+ scrollRt.sizeDelta = new Vector2(800, 600);
+ scrollGo.GetComponent().color = new Color(0.1f, 0.1f, 0.1f, 0.3f);
+
+ ScrollRect scrollRect = scrollGo.GetComponent();
+ scrollRect.content = contentRt;
+ scrollRect.viewport = vpRt;
+ scrollRect.horizontalScrollbar = hBar;
+ scrollRect.verticalScrollbar = vBar;
+ scrollRect.horizontalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;
+ scrollRect.verticalScrollbarVisibility = ScrollRect.ScrollbarVisibility.AutoHide;
+ scrollRect.movementType = ScrollRect.MovementType.Clamped;
+
+ // 组装层级
+ vpRt.SetParent(scrollRt, false);
+ hScrollbarGo.transform.SetParent(scrollRt, false);
+ vScrollbarGo.transform.SetParent(scrollRt, false);
+
+ // 绑定 ScrollRect 到表格(通过 SerializedObject 正确回写)
+ using (SerializedObject so = new SerializedObject(table))
+ {
+ SerializedProperty prop = so.FindProperty("m_ScrollRect");
+ if (prop != null)
+ {
+ prop.objectReferenceValue = scrollRect;
+ so.ApplyModifiedProperties();
+ }
+ }
+
+ // 挂载到 Canvas
+ GameObject parent = GetOrCreateCanvas();
+ scrollRt.SetParent(parent.transform, false);
+
+ Undo.RegisterCreatedObjectUndo(scrollGo, "Create Xeric UI Action Table");
+ Selection.activeGameObject = scrollGo;
+ }
+
+ private static GameObject GetOrCreateCanvas()
+ {
+ Canvas canvas = Object.FindObjectOfType