From 3e63822495630b9e0cf552b1fcbf2d8c624cc787 Mon Sep 17 00:00:00 2001 From: LiRuoChen <571244399@qq.com> Date: Tue, 28 Jul 2026 16:53:15 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=86=99=E8=A1=A8=E6=A0=BC=E7=9A=84?= =?UTF-8?q?=E9=87=8D=E5=BB=BA=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=BE=B9=E6=A1=86=E7=9A=84=E4=BF=9D=E5=AD=98=E9=80=BB=E8=BE=91?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E6=A0=B7=E5=BC=8F=E6=A0=91=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E6=97=B6=E6=9C=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/XTable/Core/XTableBlock.cs | 45 +-- Runtime/XTable/Core/XTableData.cs | 313 ++++++++++-------- Runtime/XTable/Core/XTableQuadtree.cs | 19 ++ .../Rendering/Component/TableRenderer.cs | 43 +-- .../Component/XericUIActionTable.Api.cs | 50 ++- .../Component/XericUIActionTable.Border.cs | 189 ++++------- .../Component/XericUIActionTable.Data.cs | 81 +++-- .../Component/XericUIActionTable.ExcelIO.cs | 16 +- .../XericUIActionTable.RendererFilter.cs | 143 ++++---- .../Rendering/Component/XericUIActionTable.cs | 20 +- .../Elements/XTableBackgroundRenderer.cs | 54 +++ .../Elements/XTableBackgroundRenderer.cs.meta | 11 + 12 files changed, 514 insertions(+), 470 deletions(-) create mode 100644 Runtime/XTable/Rendering/Elements/XTableBackgroundRenderer.cs create mode 100644 Runtime/XTable/Rendering/Elements/XTableBackgroundRenderer.cs.meta diff --git a/Runtime/XTable/Core/XTableBlock.cs b/Runtime/XTable/Core/XTableBlock.cs index d9cd94f..43f2566 100644 --- a/Runtime/XTable/Core/XTableBlock.cs +++ b/Runtime/XTable/Core/XTableBlock.cs @@ -33,10 +33,9 @@ namespace XericUI.XTable.Core /// 合并单元格描述列表 public List MergeDescriptors; - /// 每个单元格右侧边框的样式命名空间缓存(线性数组,同 Cells[] 布局) - public string[] BorderRightNs; - /// 每个单元格底部边框的样式命名空间缓存(线性数组,同 Cells[] 布局) - public string[] BorderBottomNs; + // 仅用于读取旧资产;新块不分配、运行时不使用这些逐格边框数组。 + [HideInInspector] public string[] BorderRightNs; + [HideInInspector] public string[] BorderBottomNs; /// /// 创建指定尺寸的块 @@ -47,8 +46,6 @@ namespace XericUI.XTable.Core BlockSizeY = blockSizeY; Cells = new XTableCellData[blockSizeX * blockSizeY]; MergeDescriptors = new List(); - BorderRightNs = new string[blockSizeX * blockSizeY]; - BorderBottomNs = new string[blockSizeX * blockSizeY]; } /// @@ -81,38 +78,6 @@ namespace XericUI.XTable.Core Cells[index] = data; } - /// 通过块内局部坐标获取右侧边框缓存 - public string GetBorderRight(int localRow, int localCol) - { - int index = localRow * BlockSizeX + localCol; - if (index < 0 || index >= (BorderRightNs?.Length ?? 0)) return null; - return BorderRightNs[index]; - } - - /// 通过块内局部坐标设置右侧边框缓存 - public void SetBorderRight(int localRow, int localCol, string ns) - { - int index = localRow * BlockSizeX + localCol; - if (index < 0 || index >= (BorderRightNs?.Length ?? 0)) return; - BorderRightNs[index] = ns; - } - - /// 通过块内局部坐标获取底部边框缓存 - public string GetBorderBottom(int localRow, int localCol) - { - int index = localRow * BlockSizeX + localCol; - if (index < 0 || index >= (BorderBottomNs?.Length ?? 0)) return null; - return BorderBottomNs[index]; - } - - /// 通过块内局部坐标设置底部边框缓存 - public void SetBorderBottom(int localRow, int localCol, string ns) - { - int index = localRow * BlockSizeX + localCol; - if (index < 0 || index >= (BorderBottomNs?.Length ?? 0)) return; - BorderBottomNs[index] = ns; - } - /// /// 检查是否有合并描述重定向此单元格 /// @@ -142,10 +107,6 @@ namespace XericUI.XTable.Core // 确保数组和列表非空 if (Cells == null) Cells = new XTableCellData[BlockSizeX * BlockSizeY]; if (MergeDescriptors == null) MergeDescriptors = new List(); - if (BorderRightNs == null || BorderRightNs.Length != BlockSizeX * BlockSizeY) - BorderRightNs = new string[BlockSizeX * BlockSizeY]; - if (BorderBottomNs == null || BorderBottomNs.Length != BlockSizeX * BlockSizeY) - BorderBottomNs = new string[BlockSizeX * BlockSizeY]; } #endregion diff --git a/Runtime/XTable/Core/XTableData.cs b/Runtime/XTable/Core/XTableData.cs index 34465f1..4db4294 100644 --- a/Runtime/XTable/Core/XTableData.cs +++ b/Runtime/XTable/Core/XTableData.cs @@ -7,6 +7,24 @@ using XericUI.XTable.Mapping; namespace XericUI.XTable.Core { + public enum XTableBorderAxis : byte + { + Horizontal, + Vertical, + } + + [Serializable] + public struct XTableBorderRange + { + public XTableBorderAxis Axis; + public int FixedIndex; + public int StartIndex; + public int Length; + public int StyleIndex; + + public int EndIndex => StartIndex + Length; + } + /// /// 表格数据主类——持有块字典,提供单元格级别的读写接口。 /// 块按需创建(SetCell 时自动创建不存在的块),通过 Z 曲线索引映射。 @@ -38,6 +56,16 @@ namespace XericUI.XTable.Core [SerializeField] private List m_BlockKeys = new List(); [SerializeField] private List m_BlockValues = new List(); + [SerializeField] private List m_BorderStyles = new List(); + [SerializeField] private List m_BorderRanges = new List(); + + // 仅用于读取旧资产的外边框序列化代理;迁移后不再写入。 + [SerializeField] private List m_OuterBorderRows = new List(); + [SerializeField] private List m_OuterBorderCols = new List(); + [SerializeField] private List m_OuterBorderValues = new List(); + + [NonSerialized] private Dictionary<(XTableBorderAxis axis, int fixedIndex), List> m_BorderRangeIndex; + /// /// 获取或创建单元格——如果单元格不存在则自动创建空数据。 /// 在编辑器和运行时都是安全的,保证不会返回 null。 @@ -83,6 +111,8 @@ namespace XericUI.XTable.Core public void OnBeforeSerialize() { + if (m_BorderRangeIndex != null) + RebuildSerializedBorderRanges(); // Dictionary → 平行列表 m_BlockKeys.Clear(); m_BlockValues.Clear(); @@ -98,6 +128,9 @@ namespace XericUI.XTable.Core public void OnAfterDeserialize() { + if (m_BorderStyles == null) m_BorderStyles = new List(); + if (m_BorderRanges == null) m_BorderRanges = new List(); + m_BorderRangeIndex = null; if (BlockSizeX <= 0) BlockSizeX = DefaultBlockSize; if (BlockSizeY <= 0) BlockSizeY = DefaultBlockSize; @@ -111,6 +144,7 @@ namespace XericUI.XTable.Core if (m_BlockValues[i] != null) BlockMap[m_BlockKeys[i]] = m_BlockValues[i]; } + MigrateLegacyBorderCache(); } #endregion @@ -408,166 +442,183 @@ namespace XericUI.XTable.Core #endregion - #region 边框缓存 + #region 边框范围 - /// - /// 外边框样式管理器——维护 -1 行 / -1 列的边框样式命名空间。 - /// key = (row, col),其中 row / col 可为 -1。 - /// (-1, -1) = 表格最外框, - /// (-1, c) = 第 c 列顶部边框, - /// (r, -1) = 第 r 行左侧边框。 - /// - [NonSerialized] public Dictionary<(int row, int col), string> OuterBorderNs; + public IReadOnlyList BorderRanges => m_BorderRanges; - // OuterBorderNs 序列化代理 - [SerializeField] private List m_OuterBorderRows = new List(); - [SerializeField] private List m_OuterBorderCols = new List(); - [SerializeField] private List m_OuterBorderValues = new List(); - - /// - /// 获取右侧边框样式命名空间。 - /// col < 0 时退回到 OuterBorderNs。 - /// - public string GetBorderRightNs(int row, int col) - { - if (col < 0 || row < 0) - return GetOuterBorderNs(row, col); - - XTableCoordinateUtility.CellToBlockCoordinate(row, col, BlockSizeX, BlockSizeY, - out int blockRow, out int blockCol, - out int localRow, out int localCol); - ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol); - - if (!BlockMap.TryGetValue(zIndex, out XTableBlock block)) - return null; - return block.GetBorderRight(localRow, localCol); - } - - /// - /// 设置右侧边框样式命名空间。 - /// col < 0 时写入 OuterBorderNs。 - /// + public string GetBorderRightNs(int row, int col) => + row < 0 || col < 0 ? GetOuterBorderNs(row, col) : GetBorderNamespace(XTableBorderAxis.Vertical, col + 1, row); + public string GetBorderBottomNs(int row, int col) => + row < 0 || col < 0 ? GetOuterBorderNs(row, col) : GetBorderNamespace(XTableBorderAxis.Horizontal, row + 1, col); public void SetBorderRightNs(int row, int col, string ns) { - if (col < 0 || row < 0) - { - SetOuterBorderNs(row, col, ns); - return; - } - - XTableCoordinateUtility.CellToBlockCoordinate(row, col, BlockSizeX, BlockSizeY, - out int blockRow, out int blockCol, - out int localRow, out int localCol); - ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol); - - if (!BlockMap.TryGetValue(zIndex, out XTableBlock block)) - { - block = new XTableBlock(BlockSizeX, BlockSizeY, blockRow, blockCol); - BlockMap[zIndex] = block; - } - block.SetBorderRight(localRow, localCol, ns); + if (row < 0 || col < 0) SetOuterBorderNs(row, col, ns); + else SetBorderRange(XTableBorderAxis.Vertical, col + 1, row, 1, ns); } - - /// - /// 获取底部边框样式命名空间。 - /// row < 0 时退回到 OuterBorderNs。 - /// - public string GetBorderBottomNs(int row, int col) - { - if (col < 0 || row < 0) - return GetOuterBorderNs(row, col); - - XTableCoordinateUtility.CellToBlockCoordinate(row, col, BlockSizeX, BlockSizeY, - out int blockRow, out int blockCol, - out int localRow, out int localCol); - ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol); - - if (!BlockMap.TryGetValue(zIndex, out XTableBlock block)) - return null; - return block.GetBorderBottom(localRow, localCol); - } - - /// - /// 设置底部边框样式命名空间。 - /// row < 0 时写入 OuterBorderNs。 - /// public void SetBorderBottomNs(int row, int col, string ns) { - if (col < 0 || row < 0) - { - SetOuterBorderNs(row, col, ns); - return; - } - - XTableCoordinateUtility.CellToBlockCoordinate(row, col, BlockSizeX, BlockSizeY, - out int blockRow, out int blockCol, - out int localRow, out int localCol); - ulong zIndex = XTableBlockMapping.BlockCoordToZIndex(blockRow, blockCol); - - if (!BlockMap.TryGetValue(zIndex, out XTableBlock block)) - { - block = new XTableBlock(BlockSizeX, BlockSizeY, blockRow, blockCol); - BlockMap[zIndex] = block; - } - block.SetBorderBottom(localRow, localCol, ns); + if (row < 0 || col < 0) SetOuterBorderNs(row, col, ns); + else SetBorderRange(XTableBorderAxis.Horizontal, row + 1, col, 1, ns); } - /// 获取外边框样式(-1 行 / -1 列) public string GetOuterBorderNs(int row, int col) { - if (OuterBorderNs == null) return null; - OuterBorderNs.TryGetValue((row, col), out string ns); - return ns; + if (row < 0 && col >= 0) return GetBorderNamespace(XTableBorderAxis.Horizontal, 0, col); + if (col < 0 && row >= 0) return GetBorderNamespace(XTableBorderAxis.Vertical, 0, row); + return null; } - /// 设置外边框样式(-1 行 / -1 列) public void SetOuterBorderNs(int row, int col, string ns) { - if (OuterBorderNs == null) - OuterBorderNs = new Dictionary<(int, int), string>(); - OuterBorderNs[(row, col)] = ns; + if (row < 0 && col >= 0) SetBorderRange(XTableBorderAxis.Horizontal, 0, col, 1, ns); + else if (col < 0 && row >= 0) SetBorderRange(XTableBorderAxis.Vertical, 0, row, 1, ns); } - /// 清空所有边框缓存 - public void ClearAllBorderCache() + public void SetBorderRange(XTableBorderAxis axis, int fixedIndex, int startIndex, int length, string ns) { - OuterBorderNs?.Clear(); - foreach (var kv in BlockMap) + if (length <= 0) return; + EnsureBorderIndex(); + var key = (axis, fixedIndex); + if (!m_BorderRangeIndex.TryGetValue(key, out var ranges)) { - var b = kv.Value; - if (b.BorderRightNs != null) - System.Array.Clear(b.BorderRightNs, 0, b.BorderRightNs.Length); - if (b.BorderBottomNs != null) - System.Array.Clear(b.BorderBottomNs, 0, b.BorderBottomNs.Length); + ranges = new List(); + m_BorderRangeIndex[key] = ranges; } - } - // ── OuterBorderNs 序列化 ── - private void SerializeOuterBorder() - { - m_OuterBorderRows.Clear(); - m_OuterBorderCols.Clear(); - m_OuterBorderValues.Clear(); - if (OuterBorderNs != null) + int end = startIndex + length; + var updated = new List(ranges.Count + 1); + for (int i = 0; i < ranges.Count; i++) { - foreach (var kv in OuterBorderNs) + var old = ranges[i]; + if (old.EndIndex <= startIndex || old.StartIndex >= end) { - m_OuterBorderRows.Add(kv.Key.row); - m_OuterBorderCols.Add(kv.Key.col); - m_OuterBorderValues.Add(kv.Value); + updated.Add(old); + continue; + } + if (old.StartIndex < startIndex) + { + old.Length = startIndex - old.StartIndex; + updated.Add(old); + } + if (old.EndIndex > end) + { + int oldEnd = old.EndIndex; + old.StartIndex = end; + old.Length = oldEnd - end; + updated.Add(old); } } + + if (!string.IsNullOrEmpty(ns)) + { + updated.Add(new XTableBorderRange + { + Axis = axis, FixedIndex = fixedIndex, StartIndex = startIndex, + Length = length, StyleIndex = GetOrAddBorderStyle(ns) + }); + } + updated.Sort((a, b) => a.StartIndex.CompareTo(b.StartIndex)); + MergeAdjacentRanges(updated); + ranges.Clear(); + ranges.AddRange(updated); + RebuildSerializedBorderRanges(); } - private void DeserializeOuterBorder() + public string GetBorderNamespace(XTableBorderAxis axis, int fixedIndex, int index) { - OuterBorderNs = new Dictionary<(int, int), string>(); - int count = System.Math.Min(m_OuterBorderRows.Count, - System.Math.Min(m_OuterBorderCols.Count, m_OuterBorderValues.Count)); - for (int i = 0; i < count; i++) + EnsureBorderIndex(); + if (!m_BorderRangeIndex.TryGetValue((axis, fixedIndex), out var ranges)) return null; + for (int i = 0; i < ranges.Count; i++) + if (index >= ranges[i].StartIndex && index < ranges[i].EndIndex) + return GetBorderStyle(ranges[i].StyleIndex); + return null; + } + + public void ClearAllBorderCache() + { + m_BorderStyles.Clear(); + m_BorderRanges.Clear(); + m_BorderRangeIndex?.Clear(); + } + + private void MigrateLegacyBorderCache() + { + if (m_BorderRanges.Count > 0 || BlockMap == null) return; + int outerCount = Math.Min(m_OuterBorderRows?.Count ?? 0, Math.Min(m_OuterBorderCols?.Count ?? 0, m_OuterBorderValues?.Count ?? 0)); + for (int i = 0; i < outerCount; i++) + SetOuterBorderNs(m_OuterBorderRows[i], m_OuterBorderCols[i], m_OuterBorderValues[i]); + m_OuterBorderRows?.Clear(); + m_OuterBorderCols?.Clear(); + m_OuterBorderValues?.Clear(); + foreach (var pair in BlockMap) { - if (m_OuterBorderValues[i] != null) - OuterBorderNs[(m_OuterBorderRows[i], m_OuterBorderCols[i])] = m_OuterBorderValues[i]; + var block = pair.Value; + if (block == null) continue; + int count = block.BlockSizeX * block.BlockSizeY; + for (int i = 0; i < count; i++) + { + int row = block.BlockRow * block.BlockSizeY + i / block.BlockSizeX; + int col = block.BlockCol * block.BlockSizeX + i % block.BlockSizeX; + if (block.BorderRightNs != null && i < block.BorderRightNs.Length && !string.IsNullOrEmpty(block.BorderRightNs[i])) + SetBorderRange(XTableBorderAxis.Vertical, col + 1, row, 1, block.BorderRightNs[i]); + if (block.BorderBottomNs != null && i < block.BorderBottomNs.Length && !string.IsNullOrEmpty(block.BorderBottomNs[i])) + SetBorderRange(XTableBorderAxis.Horizontal, row + 1, col, 1, block.BorderBottomNs[i]); + } + block.BorderRightNs = null; + block.BorderBottomNs = null; + } + } + + private int GetOrAddBorderStyle(string ns) + { + for (int i = 0; i < m_BorderStyles.Count; i++) + if (m_BorderStyles[i] == ns) return i; + m_BorderStyles.Add(ns); + return m_BorderStyles.Count - 1; + } + + private string GetBorderStyle(int styleIndex) + { + return styleIndex >= 0 && styleIndex < m_BorderStyles.Count ? m_BorderStyles[styleIndex] : null; + } + + private void EnsureBorderIndex() + { + if (m_BorderRangeIndex != null) return; + m_BorderRangeIndex = new Dictionary<(XTableBorderAxis axis, int fixedIndex), List>(); + for (int i = 0; i < m_BorderRanges.Count; i++) + { + var range = m_BorderRanges[i]; + var key = (range.Axis, range.FixedIndex); + if (!m_BorderRangeIndex.TryGetValue(key, out var ranges)) + { + ranges = new List(); + m_BorderRangeIndex[key] = ranges; + } + ranges.Add(range); + } + } + + private void RebuildSerializedBorderRanges() + { + EnsureBorderIndex(); + m_BorderRanges.Clear(); + foreach (var pair in m_BorderRangeIndex) + m_BorderRanges.AddRange(pair.Value); + } + + private static void MergeAdjacentRanges(List ranges) + { + for (int i = ranges.Count - 2; i >= 0; i--) + { + var current = ranges[i]; + var next = ranges[i + 1]; + if (current.EndIndex == next.StartIndex && current.StyleIndex == next.StyleIndex) + { + current.Length += next.Length; + ranges[i] = current; + ranges.RemoveAt(i + 1); + } } } diff --git a/Runtime/XTable/Core/XTableQuadtree.cs b/Runtime/XTable/Core/XTableQuadtree.cs index 997f757..0a1f690 100644 --- a/Runtime/XTable/Core/XTableQuadtree.cs +++ b/Runtime/XTable/Core/XTableQuadtree.cs @@ -232,6 +232,25 @@ namespace XericUI.XTable.Core QueryNode(m_Root, visibleRect, results); } + /// 获取完整表格的所有均匀叶子,用于背景分区提交。 + public void QueryAll(List results) + { + if (results == null || m_Root == null) return; + QueryAllNodes(m_Root, results); + } + + private void QueryAllNodes(Node node, List results) + { + if (node.IsUniform) + { + results.Add(new QuadrantResult { Region = node.Region, Namespace = node.UniformNamespace, Cells = node.Cells }); + return; + } + if (node.Children == null) return; + for (int i = 0; i < node.Children.Length; i++) + if (node.Children[i] != null) QueryAllNodes(node.Children[i], results); + } + private void QueryNode(Node node, Rect visibleRect, List results) { // 不相交则跳过 diff --git a/Runtime/XTable/Rendering/Component/TableRenderer.cs b/Runtime/XTable/Rendering/Component/TableRenderer.cs index 21e9bb6..63c5314 100644 --- a/Runtime/XTable/Rendering/Component/TableRenderer.cs +++ b/Runtime/XTable/Rendering/Component/TableRenderer.cs @@ -196,10 +196,6 @@ namespace XericUI.XTable.Rendering.Component { Color selectionColor = GetStyleColor(defaultNs, XericUIActionTable.STYLE_CELL_SELECTION_BG_COLOR, XericUIActionTable.SELECTION_COLOR_FALLBACK); - Color borderColor = GetStyleColor(defaultNs, XericUIActionTable.STYLE_CELL_BORDER_BOTTOM_COLOR, - XericUIActionTable.BORDER_COLOR_FALLBACK); - 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 * Config.MultiSelectAlphaFactor); Color focusColor = new Color(selectionColor.r + Config.FocusRAdd, selectionColor.g + Config.FocusGAdd, selectionColor.b + Config.FocusBAdd, selectionColor.a * Config.FocusAlphaMul); @@ -227,8 +223,7 @@ namespace XericUI.XTable.Rendering.Component } XTableCellData data = tableData?.GetCell(r, c); - string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace)) - ? data.StyleNamespace : defaultNs; + string cellNS = data != null ? data.GetEffectiveStyleNamespace(defaultNs) : defaultNs; int cellId = r * colWidths.Length + c; // 选择高亮 @@ -253,23 +248,6 @@ namespace XericUI.XTable.Rendering.Component } } - // 网格线 - var hLine = m_Assembler.GetImage(cellId, "cell_hline"); - if (hLine != null) - { - hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH); - hLine.Rect.sizeDelta = new Vector2(cellW, borderWidth); - hLine.Image.color = borderColor; - } - - var vLine = m_Assembler.GetImage(cellId, "cell_vline"); - if (vLine != null) - { - vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - borderWidth, cellY); - vLine.Rect.sizeDelta = new Vector2(borderWidth, cellH); - vLine.Image.color = borderColor; - } - if (data == null) continue; switch (data.ContentType) @@ -356,8 +334,7 @@ namespace XericUI.XTable.Rendering.Component } XTableCellData data = tableData?.GetCell(r, c); - string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace)) - ? data.StyleNamespace : defaultNs; + string cellNS = data != null ? data.GetEffectiveStyleNamespace(defaultNs) : defaultNs; int cellId = r * colWidths.Length + c; if (IsInSelectionRange != null && IsInSelectionRange(r, c)) @@ -381,22 +358,6 @@ namespace XericUI.XTable.Rendering.Component } } - var hLine = m_Assembler.GetImage(cellId, "cell_hline"); - if (hLine != null) - { - hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH); - hLine.Rect.sizeDelta = new Vector2(cellW, Config.RuntimeBorderWidth); - hLine.Image.color = Config.RuntimeBorderColor; - } - - var vLine = m_Assembler.GetImage(cellId, "cell_vline"); - if (vLine != null) - { - 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; switch (data.ContentType) diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs index 84a8e43..2a5e60b 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs @@ -79,17 +79,13 @@ namespace XericUI.XTable.Rendering.Component /// 设置单元格的样式命名空间(兼容旧版,等同于 StyleNamespace 字段) public void SetCellStyleNamespace(int row, int col, string styleNamespace) { - // 合并源:遍历所有被合并单元格,全部写入该样式 if (TableData.IsMergeSource(row, col, out int rowSpan, out int colSpan)) { - for (int r = row; r < row + rowSpan; r++) - { - for (int c = col; c < col + colSpan; c++) - { - if (r < m_RowHeights.Length && c < m_ColWidths.Length) - ApplyCellStyleToCell(r, c, styleNamespace); - } - } + for (int r = row; r < row + rowSpan && r < m_RowHeights.Length; r++) + for (int c = col; c < col + colSpan && c < m_ColWidths.Length; c++) + TableData.GetOrCreateCell(r, c).StyleNamespace = styleNamespace ?? string.Empty; + + ApplyCellStyleBorders(row, col, rowSpan, colSpan, styleNamespace); } else { @@ -99,13 +95,26 @@ namespace XericUI.XTable.Rendering.Component MarkDirty(TableDirtyType.StyleChanged); } - /// 在单个单元格上设置样式,同时写入其右侧和底部的边框缓存 + /// 设置单元格样式并写入其可见右侧、底部边框范围。 private void ApplyCellStyleToCell(int row, int col, string ns) { var data = TableData.GetOrCreateCell(row, col); - data.StyleNamespace = ns; - TableData.SetBorderRightNs(row, col, ns); - TableData.SetBorderBottomNs(row, col, ns); + data.StyleNamespace = ns ?? string.Empty; + + int rowSpan = 1; + int colSpan = 1; + if (TableData.IsMergeSource(row, col, out int mergedRows, out int mergedCols)) + { + rowSpan = mergedRows; + colSpan = mergedCols; + } + ApplyCellStyleBorders(row, col, rowSpan, colSpan, ns); + } + + private void ApplyCellStyleBorders(int row, int col, int rowSpan, int colSpan, string ns) + { + TableData.SetBorderRange(XTableBorderAxis.Vertical, col + colSpan, row, rowSpan, ns); + TableData.SetBorderRange(XTableBorderAxis.Horizontal, row + rowSpan, col, colSpan, ns); } /// 清除文本内容 @@ -431,10 +440,13 @@ namespace XericUI.XTable.Rendering.Component m_Assembler?.DestroyAll(); m_PrefabPool?.DestroyAll(); ClearAllChildren(); - m_BackgroundGo = null; + m_BackgroundRenderer = null; m_BorderRenderer = null; m_TableData = new XTableData(); + m_Quadtree?.Clear(); + m_QuadtreeDirty = true; + m_StyleNamespaceDirty = true; m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace); m_PrefabPool = new PrefabPrototypePool(ContentTransform); @@ -483,10 +495,13 @@ namespace XericUI.XTable.Rendering.Component m_Assembler?.DestroyAll(); m_PrefabPool?.DestroyAll(); ClearAllChildren(); - m_BackgroundGo = null; + m_BackgroundRenderer = null; m_BorderRenderer = null; m_TableData = new XTableData(); + m_Quadtree?.Clear(); + m_QuadtreeDirty = true; + m_StyleNamespaceDirty = true; m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace); m_PrefabPool = new PrefabPrototypePool(ContentTransform); @@ -539,6 +554,7 @@ namespace XericUI.XTable.Rendering.Component m_TableData.ClearAllMerges(); m_TableData.BlockMap.Clear(); + m_StyleNamespaceDirty = true; } m_Quadtree?.Clear(); ClearSelection(); @@ -571,10 +587,12 @@ namespace XericUI.XTable.Rendering.Component m_Assembler?.DestroyAll(); m_PrefabPool?.DestroyAll(); ClearAllChildren(); - m_BackgroundGo = null; + m_BackgroundRenderer = null; m_BorderRenderer = null; m_TableData = new XTableData(); + m_QuadtreeDirty = true; + m_StyleNamespaceDirty = true; ClearSelection(); m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace); diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs index b312ed2..cc5a0d4 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs @@ -65,139 +65,88 @@ namespace XericUI.XTable.Rendering.Component } } - /// - /// 重建表格的全部边框线段。 - /// 第一阶段:从 OuterBorderNs 绘制 -1 行(顶部外框)和 -1 列(左侧外框)。 - /// 第二阶段:遍历非合并覆盖单元格,从边框缓存读取样式命名空间,按 per-cell 样式绘制右侧和底部边框。 - /// 合并源在最外沿绘制,内部边框自动跳过。 - /// + /// 按边界范围提交表格边框;默认样式连续边会合并为单条线段。 private void RebuildBorders() { - if (m_RowHeights == null || m_ColWidths == null) return; - if (m_RowHeights.Length == 0 || m_ColWidths.Length == 0) return; + if (m_RowHeights == null || m_ColWidths == null || m_RowHeights.Length == 0 || m_ColWidths.Length == 0) + return; var renderer = GetOrCreateBorderRenderer(); renderer.ClearAll(); - - var borderRt = renderer.GetComponent(); - borderRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight); - -#if UNITY_EDITOR - Color borderColor; - float borderWidth; - if (!Application.isPlaying) - { - borderColor = GetCellBorderColor(m_DefaultStyleNamespace); - borderWidth = GetCellBorderWidth(m_DefaultStyleNamespace); - } - else - { - borderColor = Config.RuntimeBorderColor; - borderWidth = Config.RuntimeBorderWidth; - } -#else - Color borderColor = Config.RuntimeBorderColor; - float borderWidth = Config.RuntimeBorderWidth; -#endif + renderer.rectTransform.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight); int rows = m_RowHeights.Length; int cols = m_ColWidths.Length; + for (int boundary = 0; boundary <= rows; boundary++) + SubmitHorizontalBoundary(renderer, boundary, cols); + for (int boundary = 0; boundary <= cols; boundary++) + SubmitVerticalBoundary(renderer, boundary, rows); + } - // ════════════════════════════════════════════ - // 阶段 1:-1 行(顶部外框)和 -1 列(左侧外框) - // ════════════════════════════════════════════ - - // 顶部外框:从 col=0 到 col=cols-1,每列一段 - float topY = 0; - for (int c = 0; c < cols; c++) + private void SubmitHorizontalBoundary(UILineRendererV2 renderer, int boundary, int cols) + { + int start = 0; + while (start < cols) { - string ns = m_TableData?.GetOuterBorderNs(-1, c); - if (string.IsNullOrEmpty(ns)) ns = m_DefaultStyleNamespace; - Color color = GetCellBorderColor(ns); - float width = GetCellBorderWidth(ns); - float leftX = GetColLeftX(c); - renderer.DrawLine( - new Vector2(leftX, topY), - new Vector2(leftX + m_ColWidths[c], topY), - color, width, UVMode.ByDistance); - } - - // 左侧外框:从 row=0 到 row=rows-1,每行一段 - float xEdge = 0; - for (int r = 0; r < rows; r++) - { - string ns = m_TableData?.GetOuterBorderNs(r, -1); - if (string.IsNullOrEmpty(ns)) ns = m_DefaultStyleNamespace; - Color color = GetCellBorderColor(ns); - float width = GetCellBorderWidth(ns); - float rowTopY = -GetRowTopY(r); - renderer.DrawLine( - new Vector2(xEdge, rowTopY), - new Vector2(xEdge, rowTopY - m_RowHeights[r]), - color, width, UVMode.ByDistance); - } - - // ════════════════════════════════════════════ - // 阶段 2:每个单元格的右侧和底部边框 - // ════════════════════════════════════════════ - - for (int r = 0; r < rows; r++) - { - for (int c = 0; c < cols; c++) + if (IsMergedInteriorBoundary(XTableBorderAxis.Horizontal, boundary, start)) { - // 跳过被合并覆盖的单元格 - if (m_TableData != null && m_TableData.IsCellMerged(r, c)) - continue; - - float cellX = GetColLeftX(c); - float cellY = -GetRowTopY(r); - float cellW = m_ColWidths[c]; - float cellH = m_RowHeights[r]; - - int mergeRS = 1, mergeCS = 1; - if (m_TableData != null) - m_TableData.IsMergeSource(r, c, out mergeRS, out mergeCS); - - // 扩展合并尺寸 - for (int i = 1; i < mergeCS && c + i < cols; i++) - cellW += m_ColWidths[c + i]; - for (int i = 1; i < mergeRS && r + i < rows; i++) - cellH += m_RowHeights[r + i]; - - float rightX = cellX + cellW; - float bottomY = cellY - cellH; - - // —— 右侧边框 —— - string rightNs = m_TableData?.GetBorderRightNs(r, c); - if (string.IsNullOrEmpty(rightNs)) rightNs = m_DefaultStyleNamespace; - Color rightColor = GetCellBorderColor(rightNs); - float rightWidth = GetCellBorderWidth(rightNs); - - int rightCol = c + mergeCS; - if (rightCol <= cols) - { - renderer.DrawLine( - new Vector2(rightX, cellY), - new Vector2(rightX, bottomY), - rightColor, rightWidth, UVMode.ByDistance); - } - - // —— 底部边框 —— - string bottomNs = m_TableData?.GetBorderBottomNs(r, c); - if (string.IsNullOrEmpty(bottomNs)) bottomNs = m_DefaultStyleNamespace; - Color bottomColor = GetCellBorderColor(bottomNs); - float bottomWidth = GetCellBorderWidth(bottomNs); - - int bottomRow = r + mergeRS; - if (bottomRow <= rows) - { - renderer.DrawLine( - new Vector2(cellX, bottomY), - new Vector2(rightX, bottomY), - bottomColor, bottomWidth, UVMode.ByDistance); - } + start++; + continue; } + string ns = ResolveBorderNamespace(XTableBorderAxis.Horizontal, boundary, start); + int end = start + 1; + while (end < cols && !IsMergedInteriorBoundary(XTableBorderAxis.Horizontal, boundary, end) + && ResolveBorderNamespace(XTableBorderAxis.Horizontal, boundary, end) == ns) + end++; + + float y = -GetRowTopY(boundary); + renderer.DrawLine(new Vector2(GetColLeftX(start), y), new Vector2(GetColLeftX(end), y), + GetCellBorderColor(ns), GetCellBorderWidth(ns), UVMode.ByDistance); + start = end; } } + + private void SubmitVerticalBoundary(UILineRendererV2 renderer, int boundary, int rows) + { + int start = 0; + while (start < rows) + { + if (IsMergedInteriorBoundary(XTableBorderAxis.Vertical, boundary, start)) + { + start++; + continue; + } + string ns = ResolveBorderNamespace(XTableBorderAxis.Vertical, boundary, start); + int end = start + 1; + while (end < rows && !IsMergedInteriorBoundary(XTableBorderAxis.Vertical, boundary, end) + && ResolveBorderNamespace(XTableBorderAxis.Vertical, boundary, end) == ns) + end++; + + float x = GetColLeftX(boundary); + renderer.DrawLine(new Vector2(x, -GetRowTopY(start)), new Vector2(x, -GetRowTopY(end)), + GetCellBorderColor(ns), GetCellBorderWidth(ns), UVMode.ByDistance); + start = end; + } + } + + private string ResolveBorderNamespace(XTableBorderAxis axis, int fixedIndex, int index) + { + string ns = m_TableData?.GetBorderNamespace(axis, fixedIndex, index); + return string.IsNullOrEmpty(ns) ? m_DefaultStyleNamespace : ns; + } + + private bool IsMergedInteriorBoundary(XTableBorderAxis axis, int fixedIndex, int index) + { + if (m_TableData == null) return false; + if (axis == XTableBorderAxis.Horizontal) + { + if (fixedIndex <= 0 || fixedIndex >= m_RowHeights.Length) return false; + return m_TableData.IsCellMerged(fixedIndex - 1, index) + || m_TableData.IsCellMerged(fixedIndex, index); + } + if (fixedIndex <= 0 || fixedIndex >= m_ColWidths.Length) return false; + return m_TableData.IsCellMerged(index, fixedIndex - 1) + || m_TableData.IsCellMerged(index, fixedIndex); + } } } diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs index 26a0cb8..d4974cb 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs @@ -153,40 +153,81 @@ namespace XericUI.XTable.Rendering.Component #region 四叉树 - /// 强制重建四叉树(外部调用入口,适合编辑器调试或手动刷新) + /// 强制重建四叉树(仅多样式表会实际构建)。 public void ForceRebuildQuadtree() { - if (m_Quadtree == null) - m_Quadtree = new XTableQuadtree(); + m_QuadtreeDirty = true; + EnsureQuadtreeBuilt(); + } + + private string ResolveCellNamespace(XTableCellData data) + { + return data != null ? data.GetEffectiveStyleNamespace(m_DefaultStyleNamespace) : m_DefaultStyleNamespace; + } + + private bool HasMultipleStyleNamespaces() + { + RefreshActiveStyleNamespaces(); + return m_ActiveStyleNamespaces.Count > 1; + } + + private string GetSingleStyleNamespace() + { + RefreshActiveStyleNamespaces(); + foreach (string ns in m_ActiveStyleNamespaces) return ns; + return m_DefaultStyleNamespace; + } + + private void RefreshActiveStyleNamespaces() + { + if (!m_StyleNamespaceDirty) return; + m_ActiveStyleNamespaces.Clear(); + if (m_TableData == null || m_RowHeights == null || m_ColWidths == null) + { + m_ActiveStyleNamespaces.Add(m_DefaultStyleNamespace); + m_StyleNamespaceDirty = false; + return; + } + for (int r = 0; r < m_RowHeights.Length; r++) + for (int c = 0; c < m_ColWidths.Length; c++) + { + if (m_TableData.IsCellMerged(r, c)) continue; + m_ActiveStyleNamespaces.Add(ResolveCellNamespace(m_TableData.GetCell(r, c))); + } + if (m_ActiveStyleNamespaces.Count == 0) + m_ActiveStyleNamespaces.Add(m_DefaultStyleNamespace); + m_StyleNamespaceDirty = false; + } + + /// 只在多样式背景分区需要时,从当前数据构建四叉树。 + private void EnsureQuadtreeBuilt() + { + if (!HasMultipleStyleNamespaces()) + { + m_Quadtree?.Clear(); + m_QuadtreeDirty = false; + return; + } + if (!m_QuadtreeDirty && m_Quadtree != null && m_Quadtree.IsBuilt) return; MaintainQuadtree(); } - /// 从当前 XTableData 收集单元格信息并重建四叉树 private void MaintainQuadtree() { - if (m_TableData == null || m_RowHeights == null || m_ColWidths == null) - return; - if (m_Quadtree == null) - m_Quadtree = new XTableQuadtree(); + if (m_TableData == null || m_RowHeights == null || m_ColWidths == null) return; + if (m_Quadtree == null) m_Quadtree = new XTableQuadtree(); - // 收集所有有数据的单元格(跳过被合并覆盖的子单元格) var cells = new List(); for (int r = 0; r < m_RowHeights.Length; r++) + for (int c = 0; c < m_ColWidths.Length; c++) { - for (int c = 0; c < m_ColWidths.Length; c++) - { - if (m_TableData.IsCellMerged(r, c)) continue; - - var data = m_TableData.GetCell(r, c); - if (data == null) continue; - - string ns = !string.IsNullOrEmpty(data.StyleNamespace) - ? data.StyleNamespace : m_DefaultStyleNamespace; - cells.Add(new XTableQuadtreeCell { Row = r, Col = c, Namespace = ns }); - } + if (m_TableData.IsCellMerged(r, c)) continue; + var data = m_TableData.GetCell(r, c); + cells.Add(new XTableQuadtreeCell { Row = r, Col = c, Namespace = ResolveCellNamespace(data) }); } m_Quadtree.Build(m_TotalWidth, m_TotalHeight, m_RowHeights, m_ColWidths, cells, Config.QuadtreeMaxCellsPerNode); + m_QuadtreeDirty = false; } #endregion diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.ExcelIO.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.ExcelIO.cs index 51e56d2..b7d192b 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.ExcelIO.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.ExcelIO.cs @@ -316,9 +316,7 @@ namespace XericUI.XTable.Rendering.Component if (!string.IsNullOrEmpty(cell.Text)) excelCell.Value = cell.Text; - string ns = !string.IsNullOrEmpty(cell.StyleNamespace) - ? cell.StyleNamespace - : m_DefaultStyleNamespace; + string ns = ResolveCellNamespace(cell); if (!string.IsNullOrEmpty(ns)) ApplyStyleToExcelCell(excelCell, ns); } @@ -332,13 +330,7 @@ namespace XericUI.XTable.Rendering.Component if (m_TableData != null) WriteBorderCacheToExcel(ws, rows, cols); - // 写入表格外边框 - if (m_TableData?.OuterBorderNs != null && m_TableData.OuterBorderNs.Count > 0) - { - var outerRange = ws.Cells[1, 1, rows, cols]; - outerRange.Style.Border.BorderAround(ExcelBorderStyle.Thin, - System.Drawing.Color.FromArgb(180, 180, 180)); - } + // 外边框样式已由范围边框写入,无需额外的统一 BorderAround。 } #endregion @@ -519,7 +511,7 @@ namespace XericUI.XTable.Rendering.Component // 顶部外框(第一行顶部) for (int c = 0; c < cols; c++) { - string topNs = m_TableData.GetBorderBottomNs(-1, c); + string topNs = m_TableData.GetOuterBorderNs(-1, c); if (string.IsNullOrEmpty(topNs)) continue; Color color = GetCellBorderColor(topNs); float width = GetCellBorderWidth(topNs); @@ -533,7 +525,7 @@ namespace XericUI.XTable.Rendering.Component // 左侧外框(第一列左侧) for (int r = 0; r < rows; r++) { - string leftNs = m_TableData.GetBorderRightNs(r, -1); + string leftNs = m_TableData.GetOuterBorderNs(r, -1); if (string.IsNullOrEmpty(leftNs)) continue; Color color = GetCellBorderColor(leftNs); float width = GetCellBorderWidth(leftNs); diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs index f5d9762..5a80526 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs @@ -30,8 +30,6 @@ namespace XericUI.XTable.Rendering.Component public CellAssembler.PooledText CellText; public CellAssembler.PooledImage CellImage; public CellAssembler.PooledImage SelectBg; - public CellAssembler.PooledImage HLine; - public CellAssembler.PooledImage VLine; } #endregion @@ -45,8 +43,6 @@ namespace XericUI.XTable.Rendering.Component m_Assembler?.ReleaseText(objs.CellText); m_Assembler?.ReleaseImage(objs.CellImage); m_Assembler?.ReleaseImage(objs.SelectBg); - m_Assembler?.ReleaseImage(objs.HLine); - m_Assembler?.ReleaseImage(objs.VLine); m_CellTracked.Remove(key); } @@ -58,8 +54,6 @@ namespace XericUI.XTable.Rendering.Component m_Assembler?.ReleaseText(objs.CellText); m_Assembler?.ReleaseImage(objs.CellImage); m_Assembler?.ReleaseImage(objs.SelectBg); - m_Assembler?.ReleaseImage(objs.HLine); - m_Assembler?.ReleaseImage(objs.VLine); } m_CellTracked.Clear(); m_LastRenderedCells.Clear(); @@ -132,13 +126,11 @@ namespace XericUI.XTable.Rendering.Component #region 单元格内容渲染(统一入口) - /// 渲染单个单元格的完整内容(选择高亮 + 网格线 + 文本/图片/预制体)。 - private void RenderCellContent(int row, int col, string defaultNs, - bool withGridLines, bool editorMode) + /// 渲染单个单元格内容(选择高亮 + 文本/图片/预制体)。 + private void RenderCellContent(int row, int col, string defaultNs, bool editorMode) { XTableCellData data = TableData.GetCell(row, col); - string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace)) - ? data.StyleNamespace : defaultNs; + string cellNS = ResolveCellNamespace(data); float cellX = GetCellContentX(col); float cellY = GetCellContentY(row); @@ -160,10 +152,6 @@ namespace XericUI.XTable.Rendering.Component // ── 选择高亮 ── ApplySelectionHighlight(cellId, row, col, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs); - // ── 网格线(仅完整刷新时)── - if (withGridLines) - ApplyGridLines(cellId, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs); - // ── 单元格内容 ── if (data != null) { @@ -223,44 +211,6 @@ namespace XericUI.XTable.Rendering.Component objs.SelectBg = img; } - private void ApplyGridLines(int cellId, float cellX, float cellY, float cellW, float cellH, - string cellNS, bool editorMode, ref CellTrackedObjs objs) - { - Color borderColor; - float borderWidth; - - if (editorMode) - { - borderColor = GetCellBorderColor(cellNS); - borderWidth = GetCellBorderWidth(cellNS); - } - else - { - borderColor = Config.RuntimeBorderColor; - borderWidth = Config.RuntimeBorderWidth; - } - - // 水平网格线(底部) - var hLine = m_Assembler.GetImage(cellId, "cell_hline", cellNS); - if (hLine != null) - { - hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH); - hLine.Rect.sizeDelta = new Vector2(cellW, borderWidth); - hLine.Image.color = borderColor; - objs.HLine = hLine; - } - - // 垂直网格线(右侧) - var vLine = m_Assembler.GetImage(cellId, "cell_vline", cellNS); - if (vLine != null) - { - vLine.Rect.anchoredPosition = new Vector2(cellX + cellW - borderWidth, cellY); - vLine.Rect.sizeDelta = new Vector2(borderWidth, cellH); - vLine.Image.color = borderColor; - objs.VLine = vLine; - } - } - private void RenderCellText(int cellId, XTableCellData data, float cellX, float cellY, float cellW, float cellH, string cellNS, bool editorMode, ref CellTrackedObjs objs) @@ -348,12 +298,7 @@ namespace XericUI.XTable.Rendering.Component ctRt.sizeDelta = new Vector2(m_TotalWidth + pad2, m_TotalHeight + pad2); } - // 更新背景尺寸 - if (m_BackgroundGo != null) - { - var bgRt = m_BackgroundGo.GetComponent(); - bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight); - } + UpdateBackground(); // 归还所有追踪对象 + 预制体 if (m_ActivePrefabMap != null) @@ -380,7 +325,7 @@ namespace XericUI.XTable.Rendering.Component string ns = m_DefaultStyleNamespace; ComputeVisibleCellsInto(m_LastRenderedCells); foreach (var (r, c) in m_LastRenderedCells) - RenderCellContent(r, c, ns, withGridLines: true, editorMode: editorMode); + RenderCellContent(r, c, ns, editorMode); // 边框 + 冻结 RebuildBorders(); @@ -462,8 +407,7 @@ namespace XericUI.XTable.Rendering.Component foreach (var cell in newVisible) { if (selectionChanged || !m_LastRenderedCells.Contains(cell)) - RenderCellContent(cell.Item1, cell.Item2, ns, - withGridLines: false, editorMode: editorMode); + RenderCellContent(cell.Item1, cell.Item2, ns, editorMode); } // 5. 交换:将 newVisible 作为下一帧的比较基准 @@ -488,7 +432,7 @@ namespace XericUI.XTable.Rendering.Component var child = contentT.GetChild(i); // 跳过池管理的对象(标记有 POOL_FLAGS = DontSave | HideInHierarchy) if ((child.hideFlags & HideFlags.DontSave) != 0) continue; - if (m_BackgroundGo != null && child.gameObject == m_BackgroundGo) continue; + if (m_BackgroundRenderer != null && child.gameObject == m_BackgroundRenderer.gameObject) continue; if (borderT != null && child == borderT) continue; child.gameObject.hideFlags = HideFlags.None; @@ -505,24 +449,67 @@ namespace XericUI.XTable.Rendering.Component private void CreateBackground() { - if (m_BackgroundGo != null) return; + if (m_BackgroundRenderer != null) return; - m_BackgroundGo = new GameObject("_TableBackground", - typeof(RectTransform), typeof(CanvasRenderer), typeof(Image)); - m_BackgroundGo.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy; - m_BackgroundGo.transform.SetParent(ContentTransform, false); - m_BackgroundGo.transform.SetAsFirstSibling(); + var backgroundGo = new GameObject("_TableBackground", + typeof(RectTransform), typeof(CanvasRenderer), typeof(XTableBackgroundRenderer)); + backgroundGo.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy; + backgroundGo.transform.SetParent(ContentTransform, false); + backgroundGo.transform.SetAsFirstSibling(); - RectTransform bgRt = m_BackgroundGo.GetComponent(); - bgRt.anchorMin = new Vector2(0, 1); - bgRt.anchorMax = new Vector2(0, 1); - bgRt.pivot = new Vector2(0, 1); - bgRt.anchoredPosition = new Vector2(m_ContentOffset, -m_ContentOffset); - bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight); + RectTransform backgroundRt = backgroundGo.GetComponent(); + backgroundRt.anchorMin = new Vector2(0, 1); + backgroundRt.anchorMax = new Vector2(0, 1); + backgroundRt.pivot = new Vector2(0, 1); + backgroundRt.anchoredPosition = new Vector2(m_ContentOffset, -m_ContentOffset); + backgroundRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight); - var bgImg = m_BackgroundGo.GetComponent(); - bgImg.color = GetCellBgColor(m_DefaultStyleNamespace); - bgImg.raycastTarget = false; + m_BackgroundRenderer = backgroundGo.GetComponent(); + m_BackgroundRenderer.raycastTarget = false; + UpdateBackground(); + } + + private void UpdateBackground() + { + if (m_BackgroundRenderer == null) return; + + m_BackgroundRenderer.rectTransform.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight); + string baseNamespace = GetSingleStyleNamespace(); + var rects = new List + { + new(new Rect(0f, 0f, m_TotalWidth, m_TotalHeight), GetCellBgColor(baseNamespace)) + }; + + if (HasMultipleStyleNamespaces()) + { + EnsureQuadtreeBuilt(); + if (m_Quadtree != null && m_Quadtree.IsBuilt) + { + m_ReusableQuadrants.Clear(); + m_Quadtree.QueryAll(m_ReusableQuadrants); + foreach (var quadrant in m_ReusableQuadrants) + { + if (string.IsNullOrEmpty(quadrant.Namespace) || quadrant.Namespace == baseNamespace) + continue; + rects.Add(new XTableBackgroundRenderer.RectData(quadrant.Region, GetCellBgColor(quadrant.Namespace))); + } + } + } + else + { + m_Quadtree?.Clear(); + } + + m_BackgroundRenderer.ReplaceRects(rects); + } + + private void DestroyBackgroundRenderer() + { + if (m_BackgroundRenderer == null) return; + + if (Application.isPlaying) Destroy(m_BackgroundRenderer.gameObject); + else DestroyImmediate(m_BackgroundRenderer.gameObject); + m_BackgroundRenderer = null; } #endregion diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs index 1e3bdf2..d88fd5d 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs @@ -65,7 +65,7 @@ namespace XericUI.XTable.Rendering.Component [System.NonSerialized] private XTableSelectionHandle m_SelectionHandle; - [System.NonSerialized] private GameObject m_BackgroundGo; + [System.NonSerialized] private XTableBackgroundRenderer m_BackgroundRenderer; /// 预制体对象池——按原型分组管理 [System.NonSerialized] private PrefabPrototypePool m_PrefabPool; @@ -76,8 +76,11 @@ namespace XericUI.XTable.Rendering.Component /// Texture2D → Sprite 缓存,避免每帧 Sprite.Create 分配 [System.NonSerialized] private Dictionary m_SpriteCache; - /// 表格四叉树——按 StyleNamespace 分区,渲染阶段查询可见象限 + /// 表格四叉树——仅多样式背景分区时按需构建 [System.NonSerialized] private XTableQuadtree m_Quadtree; + [System.NonSerialized] private bool m_QuadtreeDirty = true; + [System.NonSerialized] private readonly HashSet m_ActiveStyleNamespaces = new(); + [System.NonSerialized] private bool m_StyleNamespaceDirty = true; /// 边框线段渲染器——使用 UILineRendererV2 的坐标线段绘制 [System.NonSerialized] private UILineRendererV2 m_BorderRenderer; @@ -304,12 +307,7 @@ namespace XericUI.XTable.Rendering.Component m_PrefabPool?.DestroyAll(); m_ActivePrefabMap?.Clear(); DestroyBorderRenderer(); - if (m_BackgroundGo != null) - { - if (Application.isPlaying) Destroy(m_BackgroundGo); - else DestroyImmediate(m_BackgroundGo); - m_BackgroundGo = null; - } + DestroyBackgroundRenderer(); // 销毁 Viewport 冻结渲染器 m_ViewportRenderer?.DestroyAll(); @@ -364,9 +362,11 @@ namespace XericUI.XTable.Rendering.Component { m_DirtyFlag.Mark(type); - // 仅数据或布局变更时重建四叉树;滚动/选区变更无需重建 + // 数据、布局或样式改变只使四叉树失效;仅多样式背景需要时再构建。 if ((type & (TableDirtyType.DataChanged | TableDirtyType.LayoutChanged | TableDirtyType.StyleChanged)) != 0) - MaintainQuadtree(); + m_QuadtreeDirty = true; + if ((type & (TableDirtyType.LayoutChanged | TableDirtyType.StyleChanged)) != 0) + m_StyleNamespaceDirty = true; } /// 运行时样式变更回调:标记脏数据,下一帧重绘 diff --git a/Runtime/XTable/Rendering/Elements/XTableBackgroundRenderer.cs b/Runtime/XTable/Rendering/Elements/XTableBackgroundRenderer.cs new file mode 100644 index 0000000..ff143f4 --- /dev/null +++ b/Runtime/XTable/Rendering/Elements/XTableBackgroundRenderer.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using UnityEngine; +using XericLibrary.Runtime.UIGraph; +using PrimitiveType = XericLibrary.Runtime.UIGraph.PrimitiveType; + +namespace XericUI.XTable.Rendering.Elements +{ + /// 用于批量绘制 XTable 背景区域的图元渲染器。 + public sealed class XTableBackgroundRenderer : PrimitiveCacheRendererBase + { + /// 以左上角为原点的背景区域及其填充颜色。 + public struct RectData + { + public Rect Rect; + public Color Color; + + public RectData(Rect rect, Color color) + { + Rect = rect; + Color = color; + } + } + + /// 以左上角坐标系替换全部背景矩形。 + public void ReplaceRects(IReadOnlyList rects) + { + ClearAll(); + if (rects == null) return; + + for (int i = 0; i < rects.Count; i++) + { + RectData rectData = rects[i]; + Rect rect = rectData.Rect; + AddPrimitive(new PrimitiveCacheEntry + { + type = PrimitiveType.Rectangle, + sizeMode = SizeMode.InscribedEllipse, + center = new Vector2(rect.x + rect.width * 0.5f, -(rect.y + rect.height * 0.5f)), + size = rect.size, + @params = new PrimitiveParams + { + axisScaleX = 1f, + axisScaleY = 1f, + chamferSegments = 1, + bgColor = rectData.Color, + centerColor = rectData.Color, + borderColor = Color.clear, + borderThickness = 0f, + }, + }); + } + } + } +} diff --git a/Runtime/XTable/Rendering/Elements/XTableBackgroundRenderer.cs.meta b/Runtime/XTable/Rendering/Elements/XTableBackgroundRenderer.cs.meta new file mode 100644 index 0000000..b5cd172 --- /dev/null +++ b/Runtime/XTable/Rendering/Elements/XTableBackgroundRenderer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3752be8845ec6624c8b9888048c6c882 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: