From 0c89d93ee2c49978a77d25daf2fc94f097fd0215 Mon Sep 17 00:00:00 2001 From: LiRuoChen <571244399@qq.com> Date: Thu, 9 Jul 2026 19:22:09 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=8D=95=E5=85=83=E6=A0=BC=E5=86=85?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A0=B7=E5=BC=8F=E5=B1=9E=E6=80=A7=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=8C=E4=BB=A5=E5=8F=8A=E5=86=85=E5=AE=B9=E7=B1=BB?= =?UTF-8?q?=E5=9E=8B=E5=88=87=E6=8D=A2=E7=9A=84=E5=8A=9F=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Editor/XTable/XericUIActionTableEditor.cs | 94 +++-- Runtime/XTable/Core/XTableCellData.cs | 107 ++++-- .../Rendering/Component/TableRenderer.cs | 133 ++++--- .../Rendering/Component/XericUIActionTable.cs | 326 +++++++++++++----- 4 files changed, 466 insertions(+), 194 deletions(-) diff --git a/Editor/XTable/XericUIActionTableEditor.cs b/Editor/XTable/XericUIActionTableEditor.cs index af00346..b816a65 100644 --- a/Editor/XTable/XericUIActionTableEditor.cs +++ b/Editor/XTable/XericUIActionTableEditor.cs @@ -2,6 +2,7 @@ using UnityEditor; using UnityEngine; using UnityEngine.UI; using XericLibrary.Runtime.MacroLibrary; +using XericLibrary.Runtime.SuperStyleSheet; using XericUI.XTable.Core; using XericUI.XTable.Rendering.Component; @@ -177,9 +178,9 @@ namespace XericUIEditor.XTable if (GUILayout.Button("手动刷新视图", GUILayout.Height(22))) EditorApplication.delayCall += () => m_Table.RefreshView(); - // ── 预制体配置 ── + // ── 单元格配置 ── EditorGUILayout.Space(4); - EditorGUILayout.LabelField("单元格预制体配置", EditorStyles.boldLabel); + EditorGUILayout.LabelField("单元格配置", EditorStyles.boldLabel); int selRow = m_Table.SelectedRow; int selCol = m_Table.SelectedCol; if (selRow < 0 || selCol < 0) @@ -189,30 +190,79 @@ namespace XericUIEditor.XTable else { var data = m_Table.TableData?.GetCell(selRow, selCol); - GameObject currentPrefab = data?.Prefab; - EditorGUI.BeginDisabledGroup(true); - EditorGUILayout.TextField("目标单元格", $"({selRow}, {selCol})"); - EditorGUI.EndDisabledGroup(); - - GameObject newPrefab = (GameObject)EditorGUILayout.ObjectField( - "预制体", currentPrefab, typeof(GameObject), false); - - EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("应用预制体", GUILayout.Height(22))) + if (data == null) { - Undo.RecordObject(m_Table, "Set Cell Prefab"); - m_Table.SetCellPrefab(selRow, selCol, newPrefab); - EditorUtility.SetDirty(m_Table); - Repaint(); + EditorGUILayout.HelpBox("单元格数据为空", MessageType.Warning); } - if (GUILayout.Button("清除预制体", GUILayout.Height(22))) + else { - Undo.RecordObject(m_Table, "Clear Cell Prefab"); - m_Table.ClearCellPrefab(selRow, selCol); - EditorUtility.SetDirty(m_Table); - Repaint(); + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.TextField("目标单元格", $"({selRow}, {selCol})"); + EditorGUI.EndDisabledGroup(); + + EditorGUI.BeginChangeCheck(); + + // 内容类型 + CellContentType ct = (CellContentType)EditorGUILayout.EnumPopup("内容类型", data.ContentType); + + // 根据类型显示对应字段 + switch (ct) + { + case CellContentType.Text: + EditorGUILayout.LabelField("文本内容", data.Text ?? "(空)"); + break; + case CellContentType.Image: + EditorGUILayout.ObjectField("图片纹理", data.Image, typeof(Texture2D), false); + break; + case CellContentType.Prefab: + GameObject newPrefab = (GameObject)EditorGUILayout.ObjectField( + "预制体", data.Prefab, typeof(GameObject), false); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(m_Table, "Set Cell Prefab"); + data.Prefab = newPrefab; + data.ContentType = ct; + m_Table.SetCellPrefab(selRow, selCol, newPrefab); + EditorUtility.SetDirty(m_Table); + Repaint(); + } + goto skipEndChangeCheck; + } + // 默认 Text 类型 + string newText = EditorGUILayout.TextField("文本内容", data.Text ?? ""); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(m_Table, "Edit Cell"); + data.Text = newText; + data.ContentType = ct; + EditorUtility.SetDirty(m_Table); + m_Table.MarkDirty(TableDirtyType.DataChanged); + EditorUtility.SetDirty(m_Table); + Repaint(); + } + skipEndChangeCheck: + ; + + // 样式 + EditorGUILayout.Space(2); + EditorGUILayout.LabelField("单元格样式 (StyleMember)", EditorStyles.miniBoldLabel); + EditorGUI.BeginChangeCheck(); + string ns = EditorGUILayout.TextField("命名空间", + data.CellStyle?.CurrentNamespace ?? ""); + string path = EditorGUILayout.TextField("样式路径", + data.CellStyle?.StylePath ?? ""); + if (EditorGUI.EndChangeCheck()) + { + Undo.RecordObject(m_Table, "Edit Cell Style"); + if (data.CellStyle == null) + data.CellStyle = new StyleMember(); + data.CellStyle.CurrentNamespace = ns; + data.CellStyle.StylePath = path; + EditorUtility.SetDirty(m_Table); + m_Table.MarkDirty(TableDirtyType.DataChanged); + Repaint(); + } } - EditorGUILayout.EndHorizontal(); } EditorGUI.indentLevel--; diff --git a/Runtime/XTable/Core/XTableCellData.cs b/Runtime/XTable/Core/XTableCellData.cs index a5257fb..e4a3614 100644 --- a/Runtime/XTable/Core/XTableCellData.cs +++ b/Runtime/XTable/Core/XTableCellData.cs @@ -1,53 +1,84 @@ using System; using UnityEngine; +using XericLibrary.Runtime.SuperStyleSheet; namespace XericUI.XTable.Core { - /// - /// 单元格数据模型——仅保存数据内容,不保存自身尺寸。 - /// 尺寸由渲染阶段的行列标题(行高/列宽)决定。 - /// 样式通过 StyleNamespace 字符串引用 StyleManager 中的命名空间。 - /// - [Serializable] - public class XTableCellData - { - /// 文本内容 - public string Text; + /// 单元格内容类型——决定渲染时使用哪种内容 + public enum CellContentType + { + /// 文本内容 + Text, + /// 图片纹理 + Image, + /// 预制体实例 + Prefab, + } - /// 图片纹理 - public Texture2D Image; + /// + /// 单元格数据模型——仅保存数据内容,不保存自身尺寸。 + /// 尺寸由渲染阶段的行列标题(行高/列宽)决定。 + /// 样式通过 StyleNamespace 字符串引用 StyleManager 中的命名空间。 + /// + [Serializable] + public class XTableCellData + { + /// 内容类型——决定渲染时使用 Text / Image / Prefab 中的哪一个 + public CellContentType ContentType; - /// 预制体对象(用于嵌入复杂 UI 元素) - public GameObject Prefab; + /// 文本内容(ContentType == Text 时生效) + public string Text; - /// - /// 样式命名空间——对应 StyleManager 中的命名空间。 - /// 为空或 "xeric_table_default" 时使用默认表格样式。 - /// 渲染时从此命名空间读取 /cell/bg/color、/cell/font/size 等路径。 - /// - public string StyleNamespace; + /// 图片纹理(ContentType == Image 时生效) + public Texture2D Image; - /// 预制体实例从对象池取出时回调——外部可在此初始化实例内容 - [NonSerialized] public System.Action OnPrefabAcquire; + /// 预制体对象(ContentType == Prefab 时生效,用于嵌入复杂 UI 元素) + public GameObject Prefab; - /// 预制体实例归还对象池前回调——外部可在此清理引用 - [NonSerialized] public System.Action OnPrefabRelease; + /// + /// 样式命名空间——对应 StyleManager 中的命名空间。 + /// 为空或 "xeric_table_default" 时使用默认表格样式。 + /// 渲染时从此命名空间读取 /cell/bg/color、/cell/font/size 等路径。 + /// + public string StyleNamespace; - public XTableCellData() - { } + /// + /// 单元格级别样式覆盖——通过命名空间 + 路径引用样式表中的值。 + /// 渲染时优先使用此样式路径派生颜色/尺寸(如 {StylePath}/bg/color),未设置时回退到 StyleNamespace。 + /// + public StyleMember CellStyle; - public XTableCellData(string text) => Text = text; + /// 预制体实例从对象池取出时回调——外部可在此初始化实例内容 + [NonSerialized] public System.Action OnPrefabAcquire; - public XTableCellData(string text, string styleNamespace = null) : this(text) => - StyleNamespace = styleNamespace; + /// 预制体实例归还对象池前回调——外部可在此清理引用 + [NonSerialized] public System.Action OnPrefabRelease; - public XTableCellData(string text, Texture2D image, string styleNamespace = null) : - this(text, styleNamespace) => Image = image; + public XTableCellData() + { } - public void ClearEvent() - { - OnPrefabAcquire = null; - OnPrefabRelease = null; - } - } -} \ No newline at end of file + public XTableCellData(string text) => Text = text; + + public XTableCellData(string text, string styleNamespace = null) : this(text) => + StyleNamespace = styleNamespace; + + public XTableCellData(string text, Texture2D image, string styleNamespace = null) : + this(text, styleNamespace) => Image = image; + + /// 获取该单元格渲染时使用的样式命名空间(CellStyle 优先,否则 fallback 到 StyleNamespace) + public string GetEffectiveStyleNamespace(string fallbackNamespace) + { + if (CellStyle != null && !string.IsNullOrEmpty(CellStyle.CurrentNamespace)) + return CellStyle.CurrentNamespace; + if (!string.IsNullOrEmpty(StyleNamespace)) + return StyleNamespace; + return fallbackNamespace; + } + + public void ClearEvent() + { + OnPrefabAcquire = null; + OnPrefabRelease = null; + } + } +} diff --git a/Runtime/XTable/Rendering/Component/TableRenderer.cs b/Runtime/XTable/Rendering/Component/TableRenderer.cs index 35a7c03..6f7760d 100644 --- a/Runtime/XTable/Rendering/Component/TableRenderer.cs +++ b/Runtime/XTable/Rendering/Component/TableRenderer.cs @@ -26,6 +26,9 @@ namespace XericUI.XTable.Rendering.Component /// 共享活跃预制体映射表(由 XericUIActionTable 传入) public Dictionary<(int, int), GameObject> ActivePrefabMap; + /// Texture2D → Sprite 缓存,避免每帧分配 + private Dictionary m_SpriteCache; + #endregion #region 公开属性 @@ -249,30 +252,46 @@ namespace XericUI.XTable.Rendering.Component if (data == null) continue; - if (!string.IsNullOrEmpty(data.Text)) + switch (data.ContentType) { - var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); - txt.Text.text = data.Text; - txt.Text.color = GetStyleColor(cellNS, XericUIActionTable.STYLE_CELL_FG_COLOR, - XericUIActionTable.FG_COLOR_FALLBACK); - txt.Text.fontSize = GetStyleInt(cellNS, XericUIActionTable.STYLE_CELL_FONT_SIZE, - XericUIActionTable.FONT_SIZE_FALLBACK); - txt.Text.alignment = TextAlignmentOptions.Midline; - txt.Text.font = GetStyleFontAsset?.Invoke(cellNS, - XericUIActionTable.STYLE_CELL_FONT_ASSET) ?? txt.Text.font; - } - - if (data.Prefab != null && PrefabPool != null) - { - GameObject instance = PrefabPool.Acquire(data.Prefab, r, c); - RectTransform prt = instance.GetComponent(); - prt.anchoredPosition = new Vector2(cellX, cellY); - prt.sizeDelta = new Vector2(cellW, cellH); - if (ActivePrefabMap != null) - ActivePrefabMap[(r, c)] = instance; - data.OnPrefabAcquire?.Invoke(instance); + case CellContentType.Text: + if (!string.IsNullOrEmpty(data.Text)) + { + var txt = m_Assembler.GetText("cell_text", cellNS); + txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Text.text = data.Text; + txt.Text.color = GetStyleColor(cellNS, XericUIActionTable.STYLE_CELL_FG_COLOR, + XericUIActionTable.FG_COLOR_FALLBACK); + txt.Text.fontSize = GetStyleInt(cellNS, XericUIActionTable.STYLE_CELL_FONT_SIZE, + XericUIActionTable.FONT_SIZE_FALLBACK); + txt.Text.alignment = TextAlignmentOptions.Midline; + txt.Text.font = GetStyleFontAsset?.Invoke(cellNS, + XericUIActionTable.STYLE_CELL_FONT_ASSET) ?? txt.Text.font; + } + break; + case CellContentType.Image: + if (data.Image != null) + { + Sprite sprite = GetOrCreateSprite(data.Image); + var img = m_Assembler.GetImage("cell_image", cellNS); + img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Image.sprite = sprite; + } + break; + case CellContentType.Prefab: + if (data.Prefab != null && PrefabPool != null) + { + GameObject instance = PrefabPool.Acquire(data.Prefab, r, c); + RectTransform prt = instance.GetComponent(); + prt.anchoredPosition = new Vector2(cellX, cellY); + prt.sizeDelta = new Vector2(cellW, cellH); + if (ActivePrefabMap != null) + ActivePrefabMap[(r, c)] = instance; + data.OnPrefabAcquire?.Invoke(instance); + } + break; } } } @@ -338,23 +357,39 @@ namespace XericUI.XTable.Rendering.Component if (data == null) continue; - if (!string.IsNullOrEmpty(data.Text)) + switch (data.ContentType) { - var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); - txt.Text.text = data.Text; - } - - if (data.Prefab != null && PrefabPool != null) - { - GameObject instance = PrefabPool.Acquire(data.Prefab, r, c); - RectTransform prt = instance.GetComponent(); - prt.anchoredPosition = new Vector2(cellX, cellY); - prt.sizeDelta = new Vector2(cellW, cellH); - if (ActivePrefabMap != null) - ActivePrefabMap[(r, c)] = instance; - data.OnPrefabAcquire?.Invoke(instance); + case CellContentType.Text: + if (!string.IsNullOrEmpty(data.Text)) + { + var txt = m_Assembler.GetText("cell_text", cellNS); + txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Text.text = data.Text; + } + break; + case CellContentType.Image: + if (data.Image != null) + { + Sprite sprite = GetOrCreateSprite(data.Image); + var img = m_Assembler.GetImage("cell_image", cellNS); + img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Image.sprite = sprite; + } + break; + case CellContentType.Prefab: + if (data.Prefab != null && PrefabPool != null) + { + GameObject instance = PrefabPool.Acquire(data.Prefab, r, c); + RectTransform prt = instance.GetComponent(); + prt.anchoredPosition = new Vector2(cellX, cellY); + prt.sizeDelta = new Vector2(cellW, cellH); + if (ActivePrefabMap != null) + ActivePrefabMap[(r, c)] = instance; + data.OnPrefabAcquire?.Invoke(instance); + } + break; } } } @@ -362,6 +397,26 @@ namespace XericUI.XTable.Rendering.Component #endregion + #region 辅助方法 + + /// 将 Texture2D 转为 Sprite(带缓存),用于 Image 类型单元格渲染 + private Sprite GetOrCreateSprite(Texture2D texture) + { + if (texture == null) return null; + + if (m_SpriteCache == null) + m_SpriteCache = new Dictionary(); + + if (!m_SpriteCache.TryGetValue(texture, out Sprite sprite)) + { + sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); + m_SpriteCache[texture] = sprite; + } + return sprite; + } + + #endregion + #region 坐标计算 private float CalcRowTopY(int row, float[] rowHeights) diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs index 8940bac..87c3883 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs @@ -76,6 +76,9 @@ namespace XericUI.XTable.Rendering.Component /// 活跃预制体实例映射 (row, col) → instance,用于回调编排 [System.NonSerialized] private Dictionary<(int, int), GameObject> m_ActivePrefabMap; + /// Texture2D → Sprite 缓存,避免每帧 Sprite.Create 分配 + [System.NonSerialized] private Dictionary m_SpriteCache; + /// 表格四叉树——按 StyleNamespace 分区,渲染阶段查询可见象限 [System.NonSerialized] private XTableQuadtree m_Quadtree; @@ -250,26 +253,42 @@ namespace XericUI.XTable.Rendering.Component if (data == null) continue; - if (!string.IsNullOrEmpty(data.Text)) + switch (data.ContentType) { - var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); - txt.Text.text = data.Text; - txt.Text.color = GetCellFgColor(cellNS); - txt.Text.fontSize = GetCellFontSize(cellNS); - txt.Text.alignment = TextAlignmentOptions.Midline; - txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font; - } - - if (data.Prefab != null) - { - GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col); - RectTransform prt = instance.GetComponent(); - prt.anchoredPosition = new Vector2(cellX, cellY); - prt.sizeDelta = new Vector2(cellW, cellH); - m_ActivePrefabMap[(row, col)] = instance; - data.OnPrefabAcquire?.Invoke(instance); + case CellContentType.Text: + if (!string.IsNullOrEmpty(data.Text)) + { + var txt = m_Assembler.GetText("cell_text", cellNS); + txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Text.text = data.Text; + txt.Text.color = GetCellFgColor(cellNS); + txt.Text.fontSize = GetCellFontSize(cellNS); + txt.Text.alignment = TextAlignmentOptions.Midline; + txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font; + } + break; + case CellContentType.Image: + if (data.Image != null) + { + Sprite sprite = GetOrCreateSprite(data.Image); + var img = m_Assembler.GetImage("cell_image", cellNS); + img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Image.sprite = sprite; + } + break; + case CellContentType.Prefab: + if (data.Prefab != null) + { + GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col); + RectTransform prt = instance.GetComponent(); + prt.anchoredPosition = new Vector2(cellX, cellY); + prt.sizeDelta = new Vector2(cellW, cellH); + m_ActivePrefabMap[(row, col)] = instance; + data.OnPrefabAcquire?.Invoke(instance); + } + break; } } } @@ -333,22 +352,38 @@ namespace XericUI.XTable.Rendering.Component if (data == null) continue; - if (!string.IsNullOrEmpty(data.Text)) + switch (data.ContentType) { - var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); - txt.Text.text = data.Text; - } - - if (data.Prefab != null) - { - GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col); - RectTransform prt = instance.GetComponent(); - prt.anchoredPosition = new Vector2(cellX, cellY); - prt.sizeDelta = new Vector2(cellW, cellH); - m_ActivePrefabMap[(row, col)] = instance; - data.OnPrefabAcquire?.Invoke(instance); + case CellContentType.Text: + if (!string.IsNullOrEmpty(data.Text)) + { + var txt = m_Assembler.GetText("cell_text", cellNS); + txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Text.text = data.Text; + } + break; + case CellContentType.Image: + if (data.Image != null) + { + Sprite sprite = GetOrCreateSprite(data.Image); + var img = m_Assembler.GetImage("cell_image", cellNS); + img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Image.sprite = sprite; + } + break; + case CellContentType.Prefab: + if (data.Prefab != null) + { + GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col); + RectTransform prt = instance.GetComponent(); + prt.anchoredPosition = new Vector2(cellX, cellY); + prt.sizeDelta = new Vector2(cellW, cellH); + m_ActivePrefabMap[(row, col)] = instance; + data.OnPrefabAcquire?.Invoke(instance); + } + break; } } } @@ -517,38 +552,49 @@ namespace XericUI.XTable.Rendering.Component MarkDirty(TableDirtyType.DataChanged); } + /// 设置文本内容(ContentType 自动设为 Text) public XTableCellData SetCellText(int row, int col, string text) { var data = TableData.GetOrCreateCell(row, col); data.Text = text; + data.ContentType = CellContentType.Text; MarkDirty(TableDirtyType.DataChanged); return data; } - public XTableCellData GetCellData(int row, int col) - => TableData.GetOrCreateCell(row, col); - - /// 为指定单元格设置预制体 - public XTableCellData SetCellPrefab(int row, int col, GameObject prefab) - { - var data = TableData.GetOrCreateCell(row, col); - data.Prefab = prefab; - MarkDirty(TableDirtyType.DataChanged); + /// 设置图片纹理(ContentType 自动设为 Image) + public XTableCellData SetCellImage(int row, int col, Texture2D image) + { + var data = TableData.GetOrCreateCell(row, col); + data.Image = image; + data.ContentType = CellContentType.Image; + MarkDirty(TableDirtyType.DataChanged); return data; - } + } - /// 清除指定单元格的预制体 - public void ClearCellPrefab(int row, int col) - { - var data = TableData.GetCell(row, col); - if (data == null) return; - data.Prefab = null; - data.OnPrefabAcquire = null; - data.OnPrefabRelease = null; - MarkDirty(TableDirtyType.DataChanged); - } + /// 设置预制体(ContentType 自动设为 Prefab) + public XTableCellData SetCellPrefab(int row, int col, GameObject prefab) + { + var data = TableData.GetOrCreateCell(row, col); + data.Prefab = prefab; + data.ContentType = CellContentType.Prefab; + MarkDirty(TableDirtyType.DataChanged); + return data; + } - /// 设置单元格的样式命名空间 + /// 设置单元格样式覆盖(StyleMember:命名空间 + 路径) + public XTableCellData SetCellStyle(int row, int col, string styleNamespace, string stylePath) + { + var data = TableData.GetOrCreateCell(row, col); + if (data.CellStyle == null) + data.CellStyle = new XericLibrary.Runtime.SuperStyleSheet.StyleMember(); + data.CellStyle.CurrentNamespace = styleNamespace ?? ""; + data.CellStyle.StylePath = stylePath ?? ""; + MarkDirty(TableDirtyType.StyleChanged); + return data; + } + + /// 设置单元格的样式命名空间(兼容旧版,等同于 StyleNamespace 字段) public void SetCellStyleNamespace(int row, int col, string styleNamespace) { var data = TableData.GetOrCreateCell(row, col); @@ -556,10 +602,54 @@ namespace XericUI.XTable.Rendering.Component MarkDirty(TableDirtyType.StyleChanged); } - /// 获取单元格的样式命名空间 + /// 清除文本内容 + public void ClearCellText(int row, int col) + { + var data = TableData.GetCell(row, col); + if (data == null) return; + data.Text = null; + if (data.ContentType == CellContentType.Text) + data.ContentType = CellContentType.Text; // 保持类型不变,仅清空内容 + MarkDirty(TableDirtyType.DataChanged); + } + + /// 清除图片纹理 + public void ClearCellImage(int row, int col) + { + var data = TableData.GetCell(row, col); + if (data == null) return; + data.Image = null; + MarkDirty(TableDirtyType.DataChanged); + } + + /// 清除预制体及委托 + public void ClearCellPrefab(int row, int col) + { + var data = TableData.GetCell(row, col); + if (data == null) return; + data.Prefab = null; + data.OnPrefabAcquire = null; + data.OnPrefabRelease = null; + MarkDirty(TableDirtyType.DataChanged); + } + + /// 清除单元格样式覆盖 + public void ClearCellStyle(int row, int col) + { + var data = TableData.GetCell(row, col); + if (data == null) return; + data.CellStyle = null; + MarkDirty(TableDirtyType.StyleChanged); + } + + public XTableCellData GetCellData(int row, int col) + => TableData.GetOrCreateCell(row, col); + + /// 获取单元格的样式命名空间(含回退逻辑) public string GetCellStyleNamespace(int row, int col) { - return TableData.GetCell(row, col)?.StyleNamespace ?? m_DefaultStyleNamespace; + var data = TableData.GetCell(row, col); + return data != null ? data.GetEffectiveStyleNamespace(m_DefaultStyleNamespace) : m_DefaultStyleNamespace; } public void MergeCells(int startRow, int startCol, int rowSpan, int colSpan) @@ -1201,27 +1291,42 @@ namespace XericUI.XTable.Rendering.Component if (data == null) continue; - if (!string.IsNullOrEmpty(data.Text)) + switch (data.ContentType) { - var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); - txt.Text.text = data.Text; - txt.Text.color = GetCellFgColor(cellNS); - txt.Text.fontSize = GetCellFontSize(cellNS); - txt.Text.alignment = TextAlignmentOptions.Midline; - txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font; - } - - // 预制体渲染 - if (data.Prefab != null) - { - GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c); - RectTransform prt = instance.GetComponent(); - prt.anchoredPosition = new Vector2(cellX, cellY); - prt.sizeDelta = new Vector2(cellW, cellH); - m_ActivePrefabMap[(r, c)] = instance; - data.OnPrefabAcquire?.Invoke(instance); + case CellContentType.Text: + if (!string.IsNullOrEmpty(data.Text)) + { + var txt = m_Assembler.GetText("cell_text", cellNS); + txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Text.text = data.Text; + txt.Text.color = GetCellFgColor(cellNS); + txt.Text.fontSize = GetCellFontSize(cellNS); + txt.Text.alignment = TextAlignmentOptions.Midline; + txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font; + } + break; + case CellContentType.Image: + if (data.Image != null) + { + Sprite sprite = GetOrCreateSprite(data.Image); + var img = m_Assembler.GetImage("cell_image", cellNS); + img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Image.sprite = sprite; + } + break; + case CellContentType.Prefab: + if (data.Prefab != null) + { + GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c); + RectTransform prt = instance.GetComponent(); + prt.anchoredPosition = new Vector2(cellX, cellY); + prt.sizeDelta = new Vector2(cellW, cellH); + m_ActivePrefabMap[(r, c)] = instance; + data.OnPrefabAcquire?.Invoke(instance); + } + break; } } } @@ -1287,24 +1392,39 @@ namespace XericUI.XTable.Rendering.Component if (data == null) continue; - if (!string.IsNullOrEmpty(data.Text)) + switch (data.ContentType) { - var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); - txt.Text.text = data.Text; - // 运行时样式由 StyleBoundElement 被动更新,此处仅设置文本内容 - } - - // 预制体渲染 - if (data.Prefab != null) - { - GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c); - RectTransform prt = instance.GetComponent(); - prt.anchoredPosition = new Vector2(cellX, cellY); - prt.sizeDelta = new Vector2(cellW, cellH); - m_ActivePrefabMap[(r, c)] = instance; - data.OnPrefabAcquire?.Invoke(instance); + case CellContentType.Text: + if (!string.IsNullOrEmpty(data.Text)) + { + var txt = m_Assembler.GetText("cell_text", cellNS); + txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Text.text = data.Text; + // 运行时样式由 StyleBoundElement 被动更新,此处仅设置文本内容 + } + break; + case CellContentType.Image: + if (data.Image != null) + { + Sprite sprite = GetOrCreateSprite(data.Image); + var img = m_Assembler.GetImage("cell_image", cellNS); + img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); + img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Image.sprite = sprite; + } + break; + case CellContentType.Prefab: + if (data.Prefab != null) + { + GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c); + RectTransform prt = instance.GetComponent(); + prt.anchoredPosition = new Vector2(cellX, cellY); + prt.sizeDelta = new Vector2(cellW, cellH); + m_ActivePrefabMap[(r, c)] = instance; + data.OnPrefabAcquire?.Invoke(instance); + } + break; } } } @@ -1330,6 +1450,22 @@ namespace XericUI.XTable.Rendering.Component return x; } + /// 将 Texture2D 转为 Sprite(带缓存),用于 Image 类型单元格渲染 + private Sprite GetOrCreateSprite(Texture2D texture) + { + if (texture == null) return null; + + if (m_SpriteCache == null) + m_SpriteCache = new Dictionary(); + + if (!m_SpriteCache.TryGetValue(texture, out Sprite sprite)) + { + sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); + m_SpriteCache[texture] = sprite; + } + return sprite; + } + private void RecalculateTotalSize() { m_TotalWidth = 0; @@ -1466,7 +1602,7 @@ namespace XericUI.XTable.Rendering.Component #region 其他 - private void MarkDirty(TableDirtyType type) + public void MarkDirty(TableDirtyType type) { m_DirtyFlag.Mark(type); From 8d3cf4348a5b8c7b7738d4ef2e945c200480a702 Mon Sep 17 00:00:00 2001 From: LiRuoChen <571244399@qq.com> Date: Fri, 10 Jul 2026 08:36:25 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E8=A1=A8=E6=A0=BC?= =?UTF-8?q?=E6=AF=8F=E6=AC=A1=E9=80=89=E4=B8=AD=E5=8D=95=E5=85=83=E6=A0=BC?= =?UTF-8?q?=E4=BC=9A=E5=AF=BC=E8=87=B4=E7=BA=BF=E6=A1=86=E5=88=87=E6=8D=A2?= =?UTF-8?q?=E4=B8=80=E6=AC=A1=E6=B8=B2=E6=9F=93=E7=8A=B6=E6=80=81=E7=9A=84?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E3=80=82=20=E4=BF=AE=E5=A4=8D=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E7=A7=BB=E5=8A=A8=E8=B6=85=E8=BF=87=E4=B8=89=E4=B8=AA?= =?UTF-8?q?=E5=8D=95=E5=85=83=E6=A0=BC=E4=BC=9A=E7=AA=81=E7=84=B6=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E9=9A=90=E8=97=8F=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Rendering/Component/XericUIActionTable.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs index 87c3883..cfc3f97 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs @@ -215,6 +215,8 @@ namespace XericUI.XTable.Rendering.Component { // 跳过被合并覆盖的单元格 if (TableData.IsCellMerged(row, col)) continue; + // 跳过不可见的单元格 + if (!IsCellVisible(row, col)) continue; float cellX = GetColLeftX(col); float cellY = -GetRowTopY(row); @@ -315,6 +317,8 @@ namespace XericUI.XTable.Rendering.Component { // 跳过被合并覆盖的单元格 if (TableData.IsCellMerged(row, col)) continue; + // 跳过不可见的单元格 + if (!IsCellVisible(row, col)) continue; float cellX = GetColLeftX(col); float cellY = -GetRowTopY(row); @@ -932,6 +936,11 @@ namespace XericUI.XTable.Rendering.Component RecalculateTotalSize(); UpdateVisibleRect(); + // 确保 ScrollRect Content 尺寸正确(否则滚动时可能裁剪掉全部内容) + RectTransform ctRt = ContentTransform as RectTransform; + if (ctRt != null) + ctRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight); + // 更新背景尺寸 if (m_BackgroundGo != null) { @@ -960,12 +969,14 @@ namespace XericUI.XTable.Rendering.Component Transform poolRoot = m_Assembler.PoolRoot; Transform prefabPoolRoot = m_PrefabPool?.PoolRoot; Transform contentT = ContentTransform; + Transform borderT = contentT.Find("_TableBorders"); for (int i = contentT.childCount - 1; i >= 0; i--) { var child = contentT.GetChild(i); if (poolRoot != null && child == poolRoot) continue; if (prefabPoolRoot != null && child == prefabPoolRoot) continue; if (m_BackgroundGo != null && child.gameObject == m_BackgroundGo) continue; + if (borderT != null && child == borderT) continue; child.gameObject.hideFlags = HideFlags.None; if (Application.isPlaying) @@ -1434,6 +1445,18 @@ namespace XericUI.XTable.Rendering.Component #region 坐标计算 + /// 判断指定单元格是否与当前可见矩形有重叠(用于 RenderQuadrants 裁剪) + private bool IsCellVisible(int row, int col) + { + float cellTop = GetRowTopY(row); + float cellBottom = cellTop + m_RowHeights[row]; + float cellLeft = GetColLeftX(col); + float cellRight = cellLeft + m_ColWidths[col]; + + return cellBottom > m_VisibleRect.y && cellTop < m_VisibleRect.yMax && + cellRight > m_VisibleRect.x && cellLeft < m_VisibleRect.xMax; + } + private float GetRowTopY(int row) { float y = 0; From 76d0dfbcb4f15f22ad00e5f99109f1e30beb15a3 Mon Sep 17 00:00:00 2001 From: LiRuoChen <571244399@qq.com> Date: Fri, 10 Jul 2026 10:59:37 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=8A=8A=E6=A0=B7=E5=BC=8F=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E9=83=BD=E6=94=BE=E5=88=B0resources=E4=B8=8B=EF=BC=8C?= =?UTF-8?q?=E7=84=B6=E5=90=8E=E8=A1=A5=E5=85=85=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/Resources.meta | 8 ++ Runtime/{XTable => Resources}/Style.meta | 2 +- .../Style/DefaultTableStyles.xsss | 0 .../Style/DefaultTableStyles.xsss.meta | 0 .../Style}/DefaultXericStyleSheet.xsss | 0 .../Style}/DefaultXericStyleSheet.xsss.meta | 0 Runtime/XTable/Core/XTableConfig.cs | 124 ++++++++++++++++++ Runtime/XTable/Core/XTableConfig.cs.meta | 11 ++ Runtime/XTable/Core/XTableQuadtree.cs | 31 +++-- .../Rendering/Component/TableRenderer.cs | 43 +++--- .../Rendering/Component/XericUIActionTable.cs | 120 ++++++++++------- 11 files changed, 264 insertions(+), 75 deletions(-) create mode 100644 Runtime/Resources.meta rename Runtime/{XTable => Resources}/Style.meta (77%) rename Runtime/{XTable => Resources}/Style/DefaultTableStyles.xsss (100%) rename Runtime/{XTable => Resources}/Style/DefaultTableStyles.xsss.meta (100%) rename Runtime/{Core/StyleSheetUI => Resources/Style}/DefaultXericStyleSheet.xsss (100%) rename Runtime/{Core/StyleSheetUI => Resources/Style}/DefaultXericStyleSheet.xsss.meta (100%) create mode 100644 Runtime/XTable/Core/XTableConfig.cs create mode 100644 Runtime/XTable/Core/XTableConfig.cs.meta diff --git a/Runtime/Resources.meta b/Runtime/Resources.meta new file mode 100644 index 0000000..3de318b --- /dev/null +++ b/Runtime/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f6a0a99a3d243d2429a9ca17b9b35149 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/XTable/Style.meta b/Runtime/Resources/Style.meta similarity index 77% rename from Runtime/XTable/Style.meta rename to Runtime/Resources/Style.meta index a2485a1..9345f8a 100644 --- a/Runtime/XTable/Style.meta +++ b/Runtime/Resources/Style.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 64d88208839a5034db31c330796453aa +guid: 6da8337ecebc7074b9f37bc69feab5c1 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Runtime/XTable/Style/DefaultTableStyles.xsss b/Runtime/Resources/Style/DefaultTableStyles.xsss similarity index 100% rename from Runtime/XTable/Style/DefaultTableStyles.xsss rename to Runtime/Resources/Style/DefaultTableStyles.xsss diff --git a/Runtime/XTable/Style/DefaultTableStyles.xsss.meta b/Runtime/Resources/Style/DefaultTableStyles.xsss.meta similarity index 100% rename from Runtime/XTable/Style/DefaultTableStyles.xsss.meta rename to Runtime/Resources/Style/DefaultTableStyles.xsss.meta diff --git a/Runtime/Core/StyleSheetUI/DefaultXericStyleSheet.xsss b/Runtime/Resources/Style/DefaultXericStyleSheet.xsss similarity index 100% rename from Runtime/Core/StyleSheetUI/DefaultXericStyleSheet.xsss rename to Runtime/Resources/Style/DefaultXericStyleSheet.xsss diff --git a/Runtime/Core/StyleSheetUI/DefaultXericStyleSheet.xsss.meta b/Runtime/Resources/Style/DefaultXericStyleSheet.xsss.meta similarity index 100% rename from Runtime/Core/StyleSheetUI/DefaultXericStyleSheet.xsss.meta rename to Runtime/Resources/Style/DefaultXericStyleSheet.xsss.meta diff --git a/Runtime/XTable/Core/XTableConfig.cs b/Runtime/XTable/Core/XTableConfig.cs new file mode 100644 index 0000000..ff7efcb --- /dev/null +++ b/Runtime/XTable/Core/XTableConfig.cs @@ -0,0 +1,124 @@ +using UnityEngine; + +namespace XericUI.XTable.Core +{ + /// + /// 表格系统全局配置资源——统一管理布局、样式回退值、运行时颜色等参数。 + /// 表格组件在 Awake 时自动加载 Resources 中的 "XTableDefaultConfig", + /// 或由外部脚本在初始化阶段显式赋值。 + /// + [CreateAssetMenu(fileName = "XTableConfig", menuName = "Xeric UI/Table/Table Config", order = 100)] + public class XTableConfig : ScriptableObject + { + #region 布局 + + [Header("布局")] + [Tooltip("新增行时的默认行高(像素)")] + public float DefaultRowHeight = 40f; + + [Tooltip("新增列时的默认列宽(像素)")] + public float DefaultColumnWidth = 100f; + + [Tooltip("单元格内容水平内边距(左右各此值)")] + public float CellHorizontalPadding = 4f; + + #endregion + + #region 四叉树 + + [Header("四叉树")] + [Tooltip("单节点最大单元格数,超过此值强制空间分裂以提升可见性裁剪效率")] + public int QuadtreeMaxCellsPerNode = 64; + + #endregion + + #region 对象池 + + [Header("对象池")] + [Tooltip("通用对象池初始容量")] + public int PoolInitialCapacity = 64; + + #endregion + + #region 样式回退值 + + [Header("样式回退值(当 StyleManager 中查不到时使用)")] + [Tooltip("默认样式命名空间")] + public string DefaultStyleNamespace = "xeric_table_default"; + + [Tooltip("单元格背景色")] + public Color BgColor = new Color(0.18f, 0.18f, 0.18f, 1f); + + [Tooltip("单元格前景色(文字颜色)")] + public Color FgColor = new Color(0.05f, 0.05f, 0.05f); + + [Tooltip("编辑器选中色")] + public Color SelectionColor = new Color(0.2f, 0.5f, 1f, 0.3f); + + [Tooltip("边框颜色")] + public Color BorderColor = new Color(0.35f, 0.35f, 0.35f, 0.5f); + + [Tooltip("边框宽度")] + public float BorderWidth = 1f; + + [Tooltip("字体大小")] + public int FontSize = 14; + + #endregion + + #region 运行时颜色 + + [Header("运行时选中/边框颜色")] + [Tooltip("运行时单选高亮")] + public Color RuntimeSelectionColor = new Color(0.18f, 0.42f, 0.82f, 0.25f); + + [Tooltip("运行时多选高亮")] + public Color RuntimeMultiSelectColor = new Color(0.18f, 0.42f, 0.82f, 0.12f); + + [Tooltip("运行时焦点高亮(当前选中格)")] + public Color RuntimeFocusColor = new Color(0.22f, 0.55f, 0.95f, 0.35f); + + [Tooltip("运行时边框色")] + public Color RuntimeBorderColor = new Color(0.35f, 0.35f, 0.35f, 0.5f); + + [Tooltip("运行时边框宽度")] + public float RuntimeBorderWidth = 1f; + + #endregion + + #region 编辑器多选颜色因子 + + [Header("编辑器多选/焦点颜色因子(基于 SelectionColor 计算)")] + [Tooltip("多选透明因子")] + [Range(0, 1)] public float MultiSelectAlphaFactor = 0.4f; + + [Tooltip("焦点色 R 增量")] + [Range(0, 0.5f)] public float FocusRAdd = 0.04f; + + [Tooltip("焦点色 G 增量")] + [Range(0, 0.5f)] public float FocusGAdd = 0.13f; + + [Tooltip("焦点色 B 增量")] + [Range(0, 0.5f)] public float FocusBAdd = 0.13f; + + [Tooltip("焦点色 Alpha 倍数")] + [Range(0.1f, 3f)] public float FocusAlphaMul = 1.4f; + + #endregion + + #region 资源路径 + + /// Resources 目录下的默认配置文件名(不含扩展名) + public const string DEFAULT_RESOURCE_PATH = "XTableDefaultConfig"; + + /// 从 Resources 加载默认配置,若失败则返回 ScriptableObject.CreateInstance 的默认值 + public static XTableConfig LoadOrDefault() + { + var cfg = Resources.Load(DEFAULT_RESOURCE_PATH); + if (cfg != null) return cfg; + return CreateInstance(); + } + + #endregion + } +} diff --git a/Runtime/XTable/Core/XTableConfig.cs.meta b/Runtime/XTable/Core/XTableConfig.cs.meta new file mode 100644 index 0000000..57f4c9b --- /dev/null +++ b/Runtime/XTable/Core/XTableConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 94d64cced79c6ef41af29f7473ff5105 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/XTable/Core/XTableQuadtree.cs b/Runtime/XTable/Core/XTableQuadtree.cs index 1ec3b63..997f757 100644 --- a/Runtime/XTable/Core/XTableQuadtree.cs +++ b/Runtime/XTable/Core/XTableQuadtree.cs @@ -65,12 +65,17 @@ namespace XericUI.XTable.Core private Node m_Root; private float[] m_RowHeights; private float[] m_ColWidths; + private int m_MaxCellsPerNode; /// 四叉树是否已构建完成 public bool IsBuilt { get; private set; } #endregion + #region 常量 + + #endregion + #region 构建 /// @@ -83,7 +88,7 @@ namespace XericUI.XTable.Core /// 所有有数据的单元格元数据列表 public void Build(float totalWidth, float totalHeight, float[] rowHeights, float[] colWidths, - List cells) + List cells, int maxCellsPerNode = 64) { if (rowHeights == null || colWidths == null || cells == null) { @@ -93,6 +98,7 @@ namespace XericUI.XTable.Core m_RowHeights = rowHeights; m_ColWidths = colWidths; + m_MaxCellsPerNode = maxCellsPerNode; m_Root = new Node { Region = new Rect(0, 0, totalWidth, totalHeight) }; int maxRow = rowHeights.Length - 1; @@ -109,10 +115,10 @@ namespace XericUI.XTable.Core /// 强制重建(外部入口,通常由表格组件在数据/布局变更后调用) public void ForceRebuild(float totalWidth, float totalHeight, float[] rowHeights, float[] colWidths, - List cells) + List cells, int maxCellsPerNode = 64) { Clear(); - Build(totalWidth, totalHeight, rowHeights, colWidths, cells); + Build(totalWidth, totalHeight, rowHeights, colWidths, cells, maxCellsPerNode); } private void BuildNode(Node node, List allCells, @@ -149,11 +155,20 @@ namespace XericUI.XTable.Core if (allSame) { - node.UniformNamespace = firstNs; - node.Cells = new List<(int, int)>(cellsInRange.Count); - for (int i = 0; i < cellsInRange.Count; i++) - node.Cells.Add((cellsInRange[i].Row, cellsInRange[i].Col)); - return; + // 命名空间统一,但若空间跨度过大则继续分裂以便按块跳过不可见区域 + if (cellsInRange.Count > m_MaxCellsPerNode && + (minRow < maxRow || minCol < maxCol)) + { + // 继续执行分裂逻辑(不要 return) + } + else + { + node.UniformNamespace = firstNs; + node.Cells = new List<(int, int)>(cellsInRange.Count); + for (int i = 0; i < cellsInRange.Count; i++) + node.Cells.Add((cellsInRange[i].Row, cellsInRange[i].Col)); + return; + } } // 无法继续分裂(单行单列),强制标记为均匀 diff --git a/Runtime/XTable/Rendering/Component/TableRenderer.cs b/Runtime/XTable/Rendering/Component/TableRenderer.cs index 6f7760d..691d2e6 100644 --- a/Runtime/XTable/Rendering/Component/TableRenderer.cs +++ b/Runtime/XTable/Rendering/Component/TableRenderer.cs @@ -29,6 +29,13 @@ namespace XericUI.XTable.Rendering.Component /// Texture2D → Sprite 缓存,避免每帧分配 private Dictionary m_SpriteCache; + /// 单元格内容水平内边距(单侧),由 XericUIActionTable 设置 + public float CellPadding; + private float CellPadding2 => CellPadding * 2f; + + /// 表格全局配置资源,由 XericUIActionTable 设置 + public XTableConfig Config; + #endregion #region 公开属性 @@ -194,8 +201,8 @@ namespace XericUI.XTable.Rendering.Component float borderWidth = GetStyleFloat(defaultNs, XericUIActionTable.STYLE_CELL_BORDER_BOTTOM_WIDTH, XericUIActionTable.BORDER_WIDTH_FALLBACK); - Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * 0.4f); - Color focusColor = new Color(selectionColor.r + 0.04f, selectionColor.g + 0.13f, selectionColor.b + 0.13f, selectionColor.a * 1.4f); + Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * Config.MultiSelectAlphaFactor); + Color focusColor = new Color(selectionColor.r + Config.FocusRAdd, selectionColor.g + Config.FocusGAdd, selectionColor.b + Config.FocusBAdd, selectionColor.a * Config.FocusAlphaMul); int selRow = GetSelectedRow(); int selCol = GetSelectedCol(); @@ -258,8 +265,8 @@ namespace XericUI.XTable.Rendering.Component if (!string.IsNullOrEmpty(data.Text)) { var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); txt.Text.text = data.Text; txt.Text.color = GetStyleColor(cellNS, XericUIActionTable.STYLE_CELL_FG_COLOR, XericUIActionTable.FG_COLOR_FALLBACK); @@ -275,8 +282,8 @@ namespace XericUI.XTable.Rendering.Component { Sprite sprite = GetOrCreateSprite(data.Image); var img = m_Assembler.GetImage("cell_image", cellNS); - img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); img.Image.sprite = sprite; } break; @@ -300,9 +307,9 @@ namespace XericUI.XTable.Rendering.Component private void RenderCellsRuntime(int sr, int er, int sc, int ec, float[] rowHeights, float[] colWidths, XTableData tableData, string defaultNs) { - Color rtSelectionColor = XericUIActionTable.RT_SELECTION_COLOR; - Color rtMultiSelectColor = XericUIActionTable.RT_MULTI_SELECT_COLOR; - Color rtFocusColor = XericUIActionTable.RT_FOCUS_COLOR; + Color rtSelectionColor = Config.RuntimeSelectionColor; + Color rtMultiSelectColor = Config.RuntimeMultiSelectColor; + Color rtFocusColor = Config.RuntimeFocusColor; int selRow = GetSelectedRow(); int selCol = GetSelectedCol(); @@ -347,13 +354,13 @@ namespace XericUI.XTable.Rendering.Component var hLine = m_Assembler.GetImage("cell_hline"); hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH); - hLine.Rect.sizeDelta = new Vector2(cellW, XericUIActionTable.RT_BORDER_WIDTH); - hLine.Image.color = XericUIActionTable.RT_BORDER_COLOR; + hLine.Rect.sizeDelta = new Vector2(cellW, Config.RuntimeBorderWidth); + hLine.Image.color = Config.RuntimeBorderColor; var vLine = m_Assembler.GetImage("cell_vline"); - vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - XericUIActionTable.RT_BORDER_WIDTH, cellY); - vLine.Rect.sizeDelta = new Vector2(XericUIActionTable.RT_BORDER_WIDTH, cellH); - vLine.Image.color = XericUIActionTable.RT_BORDER_COLOR; + vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - Config.RuntimeBorderWidth, cellY); + vLine.Rect.sizeDelta = new Vector2(Config.RuntimeBorderWidth, cellH); + vLine.Image.color = Config.RuntimeBorderColor; if (data == null) continue; @@ -363,8 +370,8 @@ namespace XericUI.XTable.Rendering.Component if (!string.IsNullOrEmpty(data.Text)) { var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); txt.Text.text = data.Text; } break; @@ -373,8 +380,8 @@ namespace XericUI.XTable.Rendering.Component { Sprite sprite = GetOrCreateSprite(data.Image); var img = m_Assembler.GetImage("cell_image", cellNS); - img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); img.Image.sprite = sprite; } break; diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs index cfc3f97..c1f0894 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs @@ -10,7 +10,6 @@ using XericUI.XTable.Mapping; using XericUI.XTable.Rendering.Elements; using XericLibrary.Runtime.SuperStyleSheet; using XericLibrary.Runtime.UIGraph; -using XericUI.XTable.Core; #if UNITY_EDITOR using UnityEditor; #endif @@ -26,12 +25,6 @@ namespace XericUI.XTable.Rendering.Component [AddComponentMenu("Xeric UI Vessel/Table/Xeric UI Action Table", 60)] public partial class XericUIActionTable : XericUIBehaciour, IPointerClickHandler { - #region 常量 - - private const string DEFAULT_STYLE_NS = "xeric_table_default"; - - #endregion - #region 序列化字段 [SerializeField] private XTableData m_TableData; @@ -40,7 +33,11 @@ namespace XericUI.XTable.Rendering.Component [SerializeField] private float[] m_ColWidths = new float[] { 100f, 80f, 120f }; - [SerializeField] private string m_DefaultStyleNamespace = DEFAULT_STYLE_NS; + [SerializeField] private string m_DefaultStyleNamespace = ""; + + [SerializeField] + [Tooltip("表格全局配置资源")] + private XTableConfig m_Config; [SerializeField] private ScrollRect m_ScrollRect; @@ -186,6 +183,23 @@ namespace XericUI.XTable.Rendering.Component } } + /// 表格全局配置资源 + public XTableConfig Config + { + get + { + if (m_Config == null) + m_Config = XTableConfig.LoadOrDefault(); + return m_Config; + } + set => m_Config = value; + } + + /// 单元格内容水平内边距(单侧) + private float CellPadding => Config.CellHorizontalPadding; + /// 单元格内容宽度扣除量 + private float CellPadding2 => Config.CellHorizontalPadding * 2f; + /// /// 基于四叉树查询结果渲染可见象限内的单元格内容(文本/预制体/选择高亮)。 /// 边框由 统一绘制。 @@ -198,8 +212,8 @@ namespace XericUI.XTable.Rendering.Component if (!Application.isPlaying) { Color selectionColor = GetCellSelectionBgColor(ns); - Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * 0.4f); - Color focusColor = new Color(selectionColor.r + 0.04f, selectionColor.g + 0.13f, selectionColor.b + 0.13f, selectionColor.a * 1.4f); + Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * Config.MultiSelectAlphaFactor); + Color focusColor = new Color(selectionColor.r + Config.FocusRAdd, selectionColor.g + Config.FocusGAdd, selectionColor.b + Config.FocusBAdd, selectionColor.a * Config.FocusAlphaMul); foreach (var q in quadrants) { @@ -261,8 +275,8 @@ namespace XericUI.XTable.Rendering.Component if (!string.IsNullOrEmpty(data.Text)) { var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); txt.Text.text = data.Text; txt.Text.color = GetCellFgColor(cellNS); txt.Text.fontSize = GetCellFontSize(cellNS); @@ -275,8 +289,8 @@ namespace XericUI.XTable.Rendering.Component { Sprite sprite = GetOrCreateSprite(data.Image); var img = m_Assembler.GetImage("cell_image", cellNS); - img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); img.Image.sprite = sprite; } break; @@ -300,9 +314,9 @@ namespace XericUI.XTable.Rendering.Component #endif // 运行时模式 - Color rtSelectionColor = RT_SELECTION_COLOR; - Color rtMultiSelectColor = RT_MULTI_SELECT_COLOR; - Color rtFocusColor = RT_FOCUS_COLOR; + Color rtSelectionColor = Config.RuntimeSelectionColor; + Color rtMultiSelectColor = Config.RuntimeMultiSelectColor; + Color rtFocusColor = Config.RuntimeFocusColor; foreach (var q in quadrants) { @@ -362,8 +376,8 @@ namespace XericUI.XTable.Rendering.Component if (!string.IsNullOrEmpty(data.Text)) { var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); txt.Text.text = data.Text; } break; @@ -372,8 +386,8 @@ namespace XericUI.XTable.Rendering.Component { Sprite sprite = GetOrCreateSprite(data.Image); var img = m_Assembler.GetImage("cell_image", cellNS); - img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); img.Image.sprite = sprite; } break; @@ -405,6 +419,12 @@ namespace XericUI.XTable.Rendering.Component // 域重载后旧子对象可能残留在场景中(DontSave 不自动销毁),先清空再重建 ClearAllChildren(); + if (m_Config == null) + m_Config = XTableConfig.LoadOrDefault(); + + if (string.IsNullOrEmpty(m_DefaultStyleNamespace)) + m_DefaultStyleNamespace = Config.DefaultStyleNamespace; + if (m_TableData == null) m_TableData = new XTableData(); @@ -675,9 +695,10 @@ namespace XericUI.XTable.Rendering.Component public bool IsMergeSource(int row, int col, out int rowSpan, out int colSpan) => TableData.IsMergeSource(row, col, out rowSpan, out colSpan); - /// 在末尾追加一行,默认行高 40 - public void AddRow(float height = 40f) + /// 在末尾追加一行,默认行高来自 Config + public void AddRow(float height = -1f) { + if (height <= 0) height = Config.DefaultRowHeight; #if UNITY_EDITOR UnityEditor.Undo.RecordObject(this, "Add Table Row"); #endif @@ -692,9 +713,10 @@ namespace XericUI.XTable.Rendering.Component #endif } - /// 在末尾追加一列,默认列宽 100 - public void AddColumn(float width = 100f) + /// 在末尾追加一列,默认列宽来自 Config + public void AddColumn(float width = -1f) { + if (width <= 0) width = Config.DefaultColumnWidth; #if UNITY_EDITOR UnityEditor.Undo.RecordObject(this, "Add Table Column"); #endif @@ -1243,8 +1265,8 @@ namespace XericUI.XTable.Rendering.Component Color borderColor = GetCellBorderColor(ns); float borderWidth = GetCellBorderWidth(ns); - Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * 0.4f); - Color focusColor = new Color(selectionColor.r + 0.04f, selectionColor.g + 0.13f, selectionColor.b + 0.13f, selectionColor.a * 1.4f); + Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * Config.MultiSelectAlphaFactor); + Color focusColor = new Color(selectionColor.r + Config.FocusRAdd, selectionColor.g + Config.FocusGAdd, selectionColor.b + Config.FocusBAdd, selectionColor.a * Config.FocusAlphaMul); for (int r = sr; r < er && r < m_RowHeights.Length; r++) { @@ -1308,8 +1330,8 @@ namespace XericUI.XTable.Rendering.Component if (!string.IsNullOrEmpty(data.Text)) { var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); txt.Text.text = data.Text; txt.Text.color = GetCellFgColor(cellNS); txt.Text.fontSize = GetCellFontSize(cellNS); @@ -1322,8 +1344,8 @@ namespace XericUI.XTable.Rendering.Component { Sprite sprite = GetOrCreateSprite(data.Image); var img = m_Assembler.GetImage("cell_image", cellNS); - img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); img.Image.sprite = sprite; } break; @@ -1346,9 +1368,9 @@ namespace XericUI.XTable.Rendering.Component #endif // 运行时模式 - Color rtSelectionColor = RT_SELECTION_COLOR; - Color rtMultiSelectColor = RT_MULTI_SELECT_COLOR; - Color rtFocusColor = RT_FOCUS_COLOR; + Color rtSelectionColor = Config.RuntimeSelectionColor; + Color rtMultiSelectColor = Config.RuntimeMultiSelectColor; + Color rtFocusColor = Config.RuntimeFocusColor; for (int r = sr; r < er && r < m_RowHeights.Length; r++) { @@ -1393,13 +1415,13 @@ namespace XericUI.XTable.Rendering.Component var hLine = m_Assembler.GetImage("cell_hline"); hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH); - hLine.Rect.sizeDelta = new Vector2(cellW, RT_BORDER_WIDTH); - hLine.Image.color = RT_BORDER_COLOR; + hLine.Rect.sizeDelta = new Vector2(cellW, Config.RuntimeBorderWidth); + hLine.Image.color = Config.RuntimeBorderColor; var vLine = m_Assembler.GetImage("cell_vline"); - vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - RT_BORDER_WIDTH, cellY); - vLine.Rect.sizeDelta = new Vector2(RT_BORDER_WIDTH, cellH); - vLine.Image.color = RT_BORDER_COLOR; + vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - Config.RuntimeBorderWidth, cellY); + vLine.Rect.sizeDelta = new Vector2(Config.RuntimeBorderWidth, cellH); + vLine.Image.color = Config.RuntimeBorderColor; if (data == null) continue; @@ -1409,8 +1431,8 @@ namespace XericUI.XTable.Rendering.Component if (!string.IsNullOrEmpty(data.Text)) { var txt = m_Assembler.GetText("cell_text", cellNS); - txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); txt.Text.text = data.Text; // 运行时样式由 StyleBoundElement 被动更新,此处仅设置文本内容 } @@ -1420,8 +1442,8 @@ namespace XericUI.XTable.Rendering.Component { Sprite sprite = GetOrCreateSprite(data.Image); var img = m_Assembler.GetImage("cell_image", cellNS); - img.Rect.anchoredPosition = new Vector2(cellX + 4, cellY); - img.Rect.sizeDelta = new Vector2(cellW - 8, cellH); + img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY); + img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH); img.Image.sprite = sprite; } break; @@ -1676,7 +1698,7 @@ namespace XericUI.XTable.Rendering.Component } } - m_Quadtree.Build(m_TotalWidth, m_TotalHeight, m_RowHeights, m_ColWidths, cells); + m_Quadtree.Build(m_TotalWidth, m_TotalHeight, m_RowHeights, m_ColWidths, cells, Config.QuadtreeMaxCellsPerNode); } #endregion @@ -1756,12 +1778,12 @@ namespace XericUI.XTable.Rendering.Component } else { - borderColor = RT_BORDER_COLOR; - borderWidth = RT_BORDER_WIDTH; + borderColor = Config.RuntimeBorderColor; + borderWidth = Config.RuntimeBorderWidth; } #else - Color borderColor = RT_BORDER_COLOR; - float borderWidth = RT_BORDER_WIDTH; + Color borderColor = Config.RuntimeBorderColor; + float borderWidth = Config.RuntimeBorderWidth; #endif int rows = m_RowHeights.Length; @@ -1850,6 +1872,8 @@ namespace XericUI.XTable.Rendering.Component // 共享预制体对象池 m_ViewportRenderer.PrefabPool = m_PrefabPool; m_ViewportRenderer.ActivePrefabMap = m_ActivePrefabMap; + m_ViewportRenderer.CellPadding = Config.CellHorizontalPadding; + m_ViewportRenderer.Config = Config; } /// 渲染 Viewport 层冻结单元格,随滚动偏移调整位置 From 4e92cd6c7fbfe617007240050514db431d72349a Mon Sep 17 00:00:00 2001 From: LiRuoChen <571244399@qq.com> Date: Fri, 10 Jul 2026 11:21:10 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E7=BB=9F=E4=B8=80=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E5=90=8D=E7=A7=B0=EF=BC=8C=E6=B7=BB=E5=8A=A0=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E9=A2=84=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/XTable/Core/XTableConfig.cs | 2 +- .../Rendering/Component/XericUIActionTable.cs | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Runtime/XTable/Core/XTableConfig.cs b/Runtime/XTable/Core/XTableConfig.cs index ff7efcb..e8a2db1 100644 --- a/Runtime/XTable/Core/XTableConfig.cs +++ b/Runtime/XTable/Core/XTableConfig.cs @@ -7,7 +7,7 @@ namespace XericUI.XTable.Core /// 表格组件在 Awake 时自动加载 Resources 中的 "XTableDefaultConfig", /// 或由外部脚本在初始化阶段显式赋值。 /// - [CreateAssetMenu(fileName = "XTableConfig", menuName = "Xeric UI/Table/Table Config", order = 100)] + [CreateAssetMenu(fileName = "XTableConfig", menuName = "Xeric Library/Table/Table Config", order = 100)] public class XTableConfig : ScriptableObject { #region 布局 diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs index c1f0894..000ca60 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs @@ -412,10 +412,32 @@ namespace XericUI.XTable.Rendering.Component #region 生命周期 + private static bool s_StyleSheetsPreloaded; + + /// + /// 预加载 Resources 目录下所有 XericStyleSheet 资产到 StyleManager。 + /// 仅首次调用生效,后续调用直接跳过。 + /// + private static void PreloadAllStyleSheets() + { + if (s_StyleSheetsPreloaded) return; + s_StyleSheetsPreloaded = true; + + var sheets = Resources.LoadAll(""); + foreach (var sheet in sheets) + { + if (sheet != null) + StyleManager.LoadStyleSheet(sheet); + } + } + protected override void Awake() { base.Awake(); + // 预加载 Resources 中所有 .xsss 样式表到 StyleManager(仅首次) + PreloadAllStyleSheets(); + // 域重载后旧子对象可能残留在场景中(DontSave 不自动销毁),先清空再重建 ClearAllChildren();