From aca1c5a94e394845fb379771b2c75d586861817c Mon Sep 17 00:00:00 2001 From: lrc <571244399@qq.com> Date: Mon, 29 Jun 2026 11:56:19 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A1=A8=E6=A0=BC=E5=86=8D20?= =?UTF-8?q?21=E7=AD=89=E4=BD=8E=E7=89=88=E6=9C=AC=E4=B8=AD=E4=BA=A4?= =?UTF-8?q?=E4=BA=92=E7=BC=96=E8=BE=91=E6=97=B6=EF=BC=8C=E4=BC=9A=E9=80=89?= =?UTF-8?q?=E4=B8=AD=E8=83=8C=E5=90=8E=E5=85=83=E7=B4=A0=E7=9A=84=E6=83=85?= =?UTF-8?q?=E5=86=B5=E3=80=82=20=E6=A0=BC=E5=BC=8F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Editor/XTable/XericUIActionTableEditor.cs | 5 +- .../XTable/XericUIActionTableSceneEditor.cs | 19 ++ .../Rendering/Component/XericUIActionTable.cs | 62 ++++ .../Rendering/Elements/StyleBoundElement.cs | 306 +++++++++--------- Runtime/XTable/Style/DefaultTableStyles.xsss | 51 +-- 5 files changed, 274 insertions(+), 169 deletions(-) diff --git a/Editor/XTable/XericUIActionTableEditor.cs b/Editor/XTable/XericUIActionTableEditor.cs index 2332bd3..a6c6533 100644 --- a/Editor/XTable/XericUIActionTableEditor.cs +++ b/Editor/XTable/XericUIActionTableEditor.cs @@ -80,14 +80,13 @@ namespace XericUIEditor.XTable if (GUILayout.Button("重新生成表格", GUILayout.Height(26))) { - m_Table.FullRebuild(); + m_Table.FullRebuildPreservingData(); m_Table.SetChildrenVisible(m_ShowChildren); } if (GUILayout.Button("强制刷新样式", GUILayout.Height(26))) { - XericLibrary.Runtime.SuperStyleSheet.StyleManager.ForceRefresh(); - m_Table.FullRebuild(); + m_Table.ForceStyleRefresh(); m_Table.SetChildrenVisible(m_ShowChildren); } diff --git a/Editor/XTable/XericUIActionTableSceneEditor.cs b/Editor/XTable/XericUIActionTableSceneEditor.cs index db3b599..412ad3b 100644 --- a/Editor/XTable/XericUIActionTableSceneEditor.cs +++ b/Editor/XTable/XericUIActionTableSceneEditor.cs @@ -217,6 +217,25 @@ namespace XericUIEditor.XTable // ★ 状态机:必须最先执行,后续所有逻辑依赖 s_State UpdateState(); + // Unity 2021 兼容:阻止鼠标事件穿透 overlay 到背后 UI 对象 + // 原理:在 overlay 区域注册默认控件,Scene View 拣选系统检测到控件后 + // 不再选中背后 GameObject(Canvas、ScrollRect 等) + if (s_State == EditorState.TableNavigation) + { + // 覆盖区域:行标签 + 列表头 + 表格主体 + 底部工具栏 + Rect overlayArea = new Rect( + tableRect.x - COL_W - MARGIN, // 包含行标签 + tableRect.y - ROW_H - MARGIN, // 包含列表头 + tableRect.width + COL_W + MARGIN + 4, // 包含行标签 + tableRect.height + ROW_H + MARGIN + 56); // 包含列表头+工具栏 + + if (overlayArea.Contains(Event.current.mousePosition)) + { + int blockId = GUIUtility.GetControlID(FocusType.Passive); + HandleUtility.AddDefaultControl(blockId); + } + } + DrawRowLabels(rh, tableRect, xScale, yScale, table); DrawColumnLabels(cw, tableRect, xScale, yScale, table); DrawGridLines(rh, cw, tableRect, xScale, yScale); diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs index aacd167..407f5ad 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs @@ -9,6 +9,9 @@ using XericUI.XTable.Core; using XericUI.XTable.Mapping; using XericUI.XTable.Rendering.Elements; using XericLibrary.Runtime.SuperStyleSheet; +#if UNITY_EDITOR +using UnityEditor; +#endif namespace XericUI.XTable.Rendering.Component { @@ -721,6 +724,48 @@ namespace XericUI.XTable.Rendering.Component SetChildrenVisible(m_ChildrenVisible); } + /// 重新生成表格但保留已有文本数据——先保存,重建后再回填 + public void FullRebuildPreservingData() + { + // 保存当前单元格文本 + var savedTexts = new Dictionary<(int, int), string>(); + if (m_TableData != null) + { + int rows = m_RowHeights?.Length ?? 0; + int cols = m_ColWidths?.Length ?? 0; + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + var cell = m_TableData.GetCell(r, c); + if (cell != null && !string.IsNullOrEmpty(cell.Text)) + savedTexts[(r, c)] = cell.Text; + } + } + } + + FullRebuild(); + + // 恢复文本数据 + foreach (var kv in savedTexts) + { + int r = kv.Key.Item1, c = kv.Key.Item2; + if (r < (m_RowHeights?.Length ?? 0) && c < (m_ColWidths?.Length ?? 0)) + m_TableData.GetOrCreateCell(r, c).Text = kv.Value; + } + + RefreshView(); + SetChildrenVisible(m_ChildrenVisible); + } + + /// 仅强制刷新样式,不重建数据结构 + public void ForceStyleRefresh() + { + StyleManager.ForceRefresh(); + MarkDirty(TableDirtyType.StyleChanged); + RefreshView(); + } + #endregion #region 单元格渲染 @@ -806,6 +851,7 @@ namespace XericUI.XTable.Rendering.Component txt.Text.color = GetStyleColor(cellNS, "/cell/fg/color", fgFallback); txt.Text.fontSize = GetStyleInt(cellNS, "/cell/font/size", 14); txt.Text.alignment = TextAlignmentOptions.Midline; + txt.Text.font = GetStyleFontAsset(cellNS, "/cell/font/fontAsset") ?? txt.Text.font; } } } @@ -884,6 +930,7 @@ namespace XericUI.XTable.Rendering.Component txt.Text.color = GetStyleColor(cellNS, "/cell/fg/color", fgFallback); txt.Text.fontSize = GetStyleInt(cellNS, "/cell/font/size", 14); txt.Text.alignment = TextAlignmentOptions.Midline; + txt.Text.font = GetStyleFontAsset(cellNS, "/cell/font/fontAsset") ?? txt.Text.font; } #endif } @@ -1005,6 +1052,14 @@ namespace XericUI.XTable.Rendering.Component return val != null ? (float)val : fallback; } + private TMP_FontAsset GetStyleFontAsset(string ns, string path, TMP_FontAsset fallback = null) + { + StyleValue val = StyleManager.GetValue(ns, path); + if (val == null) return fallback; + try { return val.GetValueAs(); } + catch { return fallback; } + } + private void EnsureDefaultStyleNamespace() { // 避免在样式表尚未加载时触发 HasStyle → GetValue 的命名空间不存在错误 @@ -1035,6 +1090,13 @@ namespace XericUI.XTable.Rendering.Component StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, "/cell/font/size", 14); StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, "/cell/font/alignment", "MiddleCenter"); StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, "/cell/font/richText", true); +#if UNITY_EDITOR + // 编辑器下尝试加载默认字体资源 + var defaultFont = UnityEditor.AssetDatabase.LoadAssetAtPath( + "Packages/com.lrss3.xericuiactionvessel/Runtime/Font/SourceHanSansSC-Regular-2 SDF.asset"); + if (defaultFont != null) + StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, "/cell/font/fontAsset", defaultFont); +#endif StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, "/cell/default/height", 40f); StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, "/cell/default/width", 100f); StyleManager.ForceRefresh(); diff --git a/Runtime/XTable/Rendering/Elements/StyleBoundElement.cs b/Runtime/XTable/Rendering/Elements/StyleBoundElement.cs index 3336671..90dfb87 100644 --- a/Runtime/XTable/Rendering/Elements/StyleBoundElement.cs +++ b/Runtime/XTable/Rendering/Elements/StyleBoundElement.cs @@ -1,183 +1,197 @@ using TMPro; - using UnityEngine; using UnityEngine.UI; - using XericLibrary.Runtime.SuperStyleSheet; namespace XericUI.XTable.Rendering.Elements { - /// - /// 样式绑定元素——将 Image 或 TMP_Text 挂勾到样式表路径。 - /// 自动从 StyleManager 拉取值并应用,样式变更时自动刷新。 - /// 通过对象池复用时调用 Bind/Unbind 切换绑定目标。 - /// - public class StyleBoundElement - { - #region 字段 + /// + /// 样式绑定元素——将 Image 或 TMP_Text 挂勾到样式表路径。 + /// 自动从 StyleManager 拉取值并应用,样式变更时自动刷新。 + /// 通过对象池复用时调用 Bind/Unbind 切换绑定目标。 + /// + public class StyleBoundElement + { + #region 字段 - /// 绑定的 Image 组件(可能为 null) - public Image ImageComponent; + /// 绑定的 Image 组件(可能为 null) + public Image ImageComponent; - /// 绑定的 TMP_Text 组件(可能为 null) - public TMP_Text TextComponent; + /// 绑定的 TMP_Text 组件(可能为 null) + public TMP_Text TextComponent; - /// 样式命名空间 - private string m_StyleNamespace; + /// 样式命名空间 + private string m_StyleNamespace; - /// 各路径的 StyleField 缓存 - private System.Collections.Generic.Dictionary m_Fields - = new System.Collections.Generic.Dictionary(); + /// 各路径的 StyleField 缓存 + private System.Collections.Generic.Dictionary m_Fields + = new System.Collections.Generic.Dictionary(); - /// 是否已绑定 - private bool m_IsBound; + /// 是否已绑定 + private bool m_IsBound; - /// 样式变更时触发(通知表格重绘) - public event System.Action OnStyleChangedCallback; + /// 样式变更时触发(通知表格重绘) + public event System.Action OnStyleChangedCallback; - /// 当前绑定的命名空间(空字符串表示未绑定) - public string BoundNamespace => m_IsBound ? (m_StyleNamespace ?? "") : ""; + /// 当前绑定的命名空间(空字符串表示未绑定) + public string BoundNamespace => m_IsBound ? (m_StyleNamespace ?? "") : ""; - #endregion + #endregion - #region 公开方法 + #region 公开方法 - /// - /// 绑定到指定命名空间并开始监听样式。 - /// 运行时注册 StyleField 实现被动更新(push);编辑器下在渲染时直接 pull。 - /// - public void Bind(string styleNamespace) - { - if (m_IsBound) Unbind(); - m_StyleNamespace = styleNamespace; + /// + /// 绑定到指定命名空间并开始监听样式。 + /// 运行时注册 StyleField 实现被动更新(push);编辑器下在渲染时直接 pull。 + /// + public void Bind(string styleNamespace) + { + if (m_IsBound) Unbind(); + m_StyleNamespace = styleNamespace; - if (string.IsNullOrEmpty(styleNamespace)) return; + if (string.IsNullOrEmpty(styleNamespace)) return; - // 检查命名空间是否存在,避免 Register → GetValue 报错 - var namespaces = StyleManager.GetAvailableNamespaces(); - if (namespaces == null || !namespaces.Contains(styleNamespace)) - return; + // 检查命名空间是否存在,避免 Register → GetValue 报错 + var namespaces = StyleManager.GetAvailableNamespaces(); + if (namespaces == null || !namespaces.Contains(styleNamespace)) + return; - // 预注册常用路径 - RegisterField("/cell/bg/color", OnStyleChanged); - RegisterField("/cell/fg/color", OnStyleChanged); - RegisterField("/cell/border/top/color", OnStyleChanged); - RegisterField("/cell/border/bottom/color", OnStyleChanged); - RegisterField("/cell/border/left/color", OnStyleChanged); - RegisterField("/cell/border/right/color", OnStyleChanged); - RegisterField("/cell/font/size", OnStyleChanged); - RegisterField("/cell/font/alignment", OnStyleChanged); - RegisterField("/cell/font/richText", OnStyleChanged); + // 预注册常用路径 + RegisterField("/cell/bg/color", OnStyleChanged); + RegisterField("/cell/fg/color", OnStyleChanged); + RegisterField("/cell/border/top/color", OnStyleChanged); + RegisterField("/cell/border/bottom/color", OnStyleChanged); + RegisterField("/cell/border/left/color", OnStyleChanged); + RegisterField("/cell/border/right/color", OnStyleChanged); + RegisterField("/cell/font/size", OnStyleChanged); + RegisterField("/cell/font/alignment", OnStyleChanged); + RegisterField("/cell/font/richText", OnStyleChanged); + RegisterField("/cell/font/fontAsset", OnStyleChanged); - m_IsBound = true; - RefreshAll(); - } + m_IsBound = true; + RefreshAll(); + } - /// - /// 解绑并停止监听样式。 - /// - public void Unbind() - { - if (!m_IsBound) return; + /// + /// 解绑并停止监听样式。 + /// + public void Unbind() + { + if (!m_IsBound) return; - foreach (var kvp in m_Fields) - { - if (kvp.Value != null) - { - kvp.Value.Member.OnValueChanged -= OnStyleChanged; - kvp.Value.Unregister(); - } - } - m_Fields.Clear(); - m_IsBound = false; - } + foreach (var kvp in m_Fields) + { + if (kvp.Value != null) + { + kvp.Value.Member.OnValueChanged -= OnStyleChanged; + kvp.Value.Unregister(); + } + } - /// - /// 刷新全部样式值到组件。 - /// - public void RefreshAll() - { - if (!m_IsBound || string.IsNullOrEmpty(m_StyleNamespace)) return; + m_Fields.Clear(); + m_IsBound = false; + } - // 背景颜色 → Image.color - StyleValue bgVal = GetStyleValue("/cell/bg/color"); - if (bgVal != null && ImageComponent != null) - ImageComponent.color = bgVal; + /// + /// 刷新全部样式值到组件。 + /// + public void RefreshAll() + { + if (!m_IsBound || string.IsNullOrEmpty(m_StyleNamespace)) return; - // 前景颜色 → Text.color - StyleValue fgVal = GetStyleValue("/cell/fg/color"); - if (fgVal != null && TextComponent != null) - TextComponent.color = fgVal; + // 背景颜色 → Image.color + StyleValue bgVal = GetStyleValue("/cell/bg/color"); + if (bgVal != null && ImageComponent != null) + ImageComponent.color = bgVal; - // 字体大小 - StyleValue sizeVal = GetStyleValue("/cell/font/size"); - if (sizeVal != null && TextComponent != null) - TextComponent.fontSize = (int)sizeVal; + // 前景颜色 → Text.color + StyleValue fgVal = GetStyleValue("/cell/fg/color"); + if (fgVal != null && TextComponent != null) + TextComponent.color = fgVal; - // 对齐 - StyleValue alignVal = GetStyleValue("/cell/font/alignment"); - if (alignVal != null && TextComponent != null) - TextComponent.alignment = alignVal.Convert(); + // 字体大小 + StyleValue sizeVal = GetStyleValue("/cell/font/size"); + if (sizeVal != null && TextComponent != null) + TextComponent.fontSize = (int)sizeVal; - // 富文本 - StyleValue richVal = GetStyleValue("/cell/font/richText"); - if (richVal != null && TextComponent != null) - TextComponent.richText = (bool)richVal; - } + // 对齐 + StyleValue alignVal = GetStyleValue("/cell/font/alignment"); + if (alignVal != null && TextComponent != null) + TextComponent.alignment = alignVal.Convert(); - #endregion + // 富文本 + StyleValue richVal = GetStyleValue("/cell/font/richText"); + if (richVal != null && TextComponent != null) + TextComponent.richText = (bool)richVal; - #region 私有方法 + // 字体资源 + StyleValue fontVal = GetStyleValue("/cell/font/fontAsset"); + if (fontVal != null && TextComponent != null) + { + try + { + TextComponent.font = fontVal.GetValueAs(); + } + catch + { + /* 类型不匹配时忽略 */ + } + } + } - private void RegisterField(string path, System.Action callback) - { - var field = new StyleField - { - Namespace = m_StyleNamespace, - StylePath = path - }; - field.Register(); - field.Member.OnValueChanged += callback; - m_Fields[path] = field; - } + #endregion - private StyleValue GetStyleValue(string path) - { - return StyleManager.GetValue(m_StyleNamespace, path); - } + #region 私有方法 - private void OnStyleChanged(StyleValue val) - { - RefreshAll(); - OnStyleChangedCallback?.Invoke(); - } + private void RegisterField(string path, System.Action callback) + { + var field = new StyleField + { + Namespace = m_StyleNamespace, + StylePath = path + }; + field.Register(); + field.Member.OnValueChanged += callback; + m_Fields[path] = field; + } - #endregion - } + private StyleValue GetStyleValue(string path) + { + return StyleManager.GetValue(m_StyleNamespace, path); + } - /// - /// 用于 TextAnchor → TextAlignmentOptions 的转换扩展。 - /// - internal static class StyleBoundElementExtensions - { - public static TextAlignmentOptions Convert(this StyleValue val) - { - string s = val?.GetValueAs(); - if (string.IsNullOrEmpty(s)) return TextAlignmentOptions.Midline; + private void OnStyleChanged(StyleValue val) + { + RefreshAll(); + OnStyleChangedCallback?.Invoke(); + } - switch (s) - { - case "UpperLeft": return TextAlignmentOptions.TopLeft; - case "UpperCenter": return TextAlignmentOptions.Top; - case "UpperRight": return TextAlignmentOptions.TopRight; - case "MiddleLeft": return TextAlignmentOptions.MidlineLeft; - case "MiddleCenter": return TextAlignmentOptions.Midline; - case "MiddleRight": return TextAlignmentOptions.MidlineRight; - case "LowerLeft": return TextAlignmentOptions.BottomLeft; - case "LowerCenter": return TextAlignmentOptions.Bottom; - case "LowerRight": return TextAlignmentOptions.BottomRight; - default: return TextAlignmentOptions.Midline; - } - } - } -} + #endregion + } + + /// + /// 用于 TextAnchor → TextAlignmentOptions 的转换扩展。 + /// + internal static class StyleBoundElementExtensions + { + public static TextAlignmentOptions Convert(this StyleValue val) + { + string s = val?.GetValueAs(); + if (string.IsNullOrEmpty(s)) return TextAlignmentOptions.Midline; + + switch (s) + { + case "UpperLeft": return TextAlignmentOptions.TopLeft; + case "UpperCenter": return TextAlignmentOptions.Top; + case "UpperRight": return TextAlignmentOptions.TopRight; + case "MiddleLeft": return TextAlignmentOptions.MidlineLeft; + case "MiddleCenter": return TextAlignmentOptions.Midline; + case "MiddleRight": return TextAlignmentOptions.MidlineRight; + case "LowerLeft": return TextAlignmentOptions.BottomLeft; + case "LowerCenter": return TextAlignmentOptions.Bottom; + case "LowerRight": return TextAlignmentOptions.BottomRight; + default: return TextAlignmentOptions.Midline; + } + } + } +} \ No newline at end of file diff --git a/Runtime/XTable/Style/DefaultTableStyles.xsss b/Runtime/XTable/Style/DefaultTableStyles.xsss index f286501..05b6131 100644 --- a/Runtime/XTable/Style/DefaultTableStyles.xsss +++ b/Runtime/XTable/Style/DefaultTableStyles.xsss @@ -3,36 +3,47 @@ // 表格默认样式定义 // ========================================== + namespace: xeric_table_default -// ---- 单元格背景 ---- cell/bg/color = (1,1,1,1):UnityEngine.Color @Desc:单元格背景颜色 -// ---- 单元格前景 ---- cell/fg/color = (0.1,0.1,0.1,1):UnityEngine.Color @Desc:单元格前景文字颜色 -// ---- 边框 ---- -cell/border/top/color = (0.8,0.8,0.8,1):UnityEngine.Color @Desc:上边框颜色 -cell/border/top/width = 1:System.Single @Desc:上边框宽度 -cell/border/bottom/color = (0.8,0.8,0.8,1):UnityEngine.Color @Desc:下边框颜色 -cell/border/bottom/width = 1:System.Single @Desc:下边框宽度 -cell/border/left/color = (0.8,0.8,0.8,1):UnityEngine.Color @Desc:左边框颜色 -cell/border/left/width = 1:System.Single @Desc:左边框宽度 -cell/border/right/color = (0.8,0.8,0.8,1):UnityEngine.Color @Desc:右边框颜色 -cell/border/right/width = 1:System.Single @Desc:右边框宽度 +cell/border/top/ { + color = (0.8,0.8,0.8,1):UnityEngine.Color @Desc:上边框颜色 + width = 1:System.Single @Desc:上边框宽度 +} + +cell/border/bottom/ { + color = (0.8,0.8,0.8,1):UnityEngine.Color @Desc:下边框颜色 + width = 1:System.Single @Desc:下边框宽度 +} + +cell/border/left/ { + color = (0.8,0.8,0.8,1):UnityEngine.Color @Desc:左边框颜色 + width = 1:System.Single @Desc:左边框宽度 +} + +cell/border/right/ { + color = (0.8,0.8,0.8,1):UnityEngine.Color @Desc:右边框颜色 + width = 1:System.Single @Desc:右边框宽度 +} -// ---- 选中高亮 ---- cell/selection/bg/color = (0.2,0.5,1,0.3):UnityEngine.Color @Desc:选中单元格高亮颜色 -// ---- 表头样式 ---- header/bg/color = (0.9,0.9,0.9,1):UnityEngine.Color @Desc:表头背景颜色 + header/fg/color = (0,0,0,1):UnityEngine.Color @Desc:表头文字颜色 -// ---- 字体 ---- -cell/font/size = 14:System.Int32 @Desc:默认字体大小 -cell/font/alignment = MiddleCenter:UnityEngine.TextAnchor @Desc:默认文本对齐 -cell/font/richText = true:System.Boolean @Desc:是否启用富文本 +cell/font/ { + size = 14:System.Int32 @Desc:默认字体大小 + alignment = MiddleCenter:UnityEngine.TextAnchor @Desc:默认文本对齐 + richText = true:System.Boolean @Desc:是否启用富文本 + fontAsset = guid:68dace674a028544187e7ffe55cdae8b:TMPro.TMP_FontAsset:TMPro.TMP_FontAsset @Desc:字体资源 @ResourceSoftReference:Packages/com.lrss3.xericuiactionvessel/Runtime/Font/SourceHanSansSC-Regular-2 SDF.asset +} -// ---- 行高 / 列宽(缺省值) ---- -cell/default/height = 40:System.Single @Desc:默认行高 -cell/default/width = 100:System.Single @Desc:默认列宽 +cell/default/ { + height = 40:System.Single @Desc:默认行高 + width = 100:System.Single @Desc:默认列宽 +}