From cbcb85b3e707b83f8157cf6c6158a786676f2bde Mon Sep 17 00:00:00 2001 From: lrc <571244399@qq.com> Date: Tue, 7 Jul 2026 10:31:08 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A1=A8=E6=A0=BC=E8=BE=B9?= =?UTF-8?q?=E6=A1=86=E7=BB=98=E5=88=B6=E9=80=BB=E8=BE=91=EF=BC=8C=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=BE=B9=E6=A1=86=E7=BC=93=E5=AD=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../XTable/XericUIActionTableSceneEditor.cs | 15 +- Runtime/XTable/Core/XTableBlock.cs | 43 +++++ Runtime/XTable/Core/XTableData.cs | 165 ++++++++++++++++++ .../Rendering/Component/XericUIActionTable.cs | 114 +++++++++--- 4 files changed, 305 insertions(+), 32 deletions(-) diff --git a/Editor/XTable/XericUIActionTableSceneEditor.cs b/Editor/XTable/XericUIActionTableSceneEditor.cs index 1a5c5ff..b1fbd4c 100644 --- a/Editor/XTable/XericUIActionTableSceneEditor.cs +++ b/Editor/XTable/XericUIActionTableSceneEditor.cs @@ -914,17 +914,24 @@ namespace XericUIEditor.XTable table.SelectedRow, table.SelectedCol) ?? ""; var namespaces = StyleManager.GetAvailableNamespaces(); - int currentIdx = namespaces.IndexOf(currentNS); + // 构建显示列表:如果当前命名空间不在可用列表中则追加到末尾 + var displayNamespaces = new System.Collections.Generic.List(namespaces); + int currentIdx = displayNamespaces.IndexOf(currentNS); + if (currentIdx < 0 && !string.IsNullOrEmpty(currentNS)) + { + displayNamespaces.Add(currentNS); + currentIdx = displayNamespaces.Count - 1; + } if (currentIdx < 0) currentIdx = 0; Rect dropRect = new Rect(barRect.x + 44, row2Y, barW - 44 - pad, rowH); int newIdx = UnityEditor.EditorGUI.Popup(dropRect, currentIdx, - namespaces.ToArray(), UnityEditor.EditorStyles.popup); + displayNamespaces.ToArray(), UnityEditor.EditorStyles.popup); - if (newIdx != currentIdx && newIdx >= 0 && newIdx < namespaces.Count) + if (newIdx != currentIdx && newIdx >= 0 && newIdx < displayNamespaces.Count) { table.SetCellStyleNamespace(table.SelectedRow, table.SelectedCol, - namespaces[newIdx]); + displayNamespaces[newIdx]); } } else diff --git a/Runtime/XTable/Core/XTableBlock.cs b/Runtime/XTable/Core/XTableBlock.cs index 13a3ca1..d9cd94f 100644 --- a/Runtime/XTable/Core/XTableBlock.cs +++ b/Runtime/XTable/Core/XTableBlock.cs @@ -33,6 +33,11 @@ namespace XericUI.XTable.Core /// 合并单元格描述列表 public List MergeDescriptors; + /// 每个单元格右侧边框的样式命名空间缓存(线性数组,同 Cells[] 布局) + public string[] BorderRightNs; + /// 每个单元格底部边框的样式命名空间缓存(线性数组,同 Cells[] 布局) + public string[] BorderBottomNs; + /// /// 创建指定尺寸的块 /// @@ -42,6 +47,8 @@ namespace XericUI.XTable.Core BlockSizeY = blockSizeY; Cells = new XTableCellData[blockSizeX * blockSizeY]; MergeDescriptors = new List(); + BorderRightNs = new string[blockSizeX * blockSizeY]; + BorderBottomNs = new string[blockSizeX * blockSizeY]; } /// @@ -74,6 +81,38 @@ 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; + } + /// /// 检查是否有合并描述重定向此单元格 /// @@ -103,6 +142,10 @@ 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 a4048b2..af6fa12 100644 --- a/Runtime/XTable/Core/XTableData.cs +++ b/Runtime/XTable/Core/XTableData.cs @@ -407,5 +407,170 @@ namespace XericUI.XTable.Core public int BlockCount => BlockMap.Count; #endregion + + #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; + + // 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 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); + } + + /// + /// 获取底部边框样式命名空间。 + /// 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); + } + + /// 获取外边框样式(-1 行 / -1 列) + public string GetOuterBorderNs(int row, int col) + { + if (OuterBorderNs == null) return null; + OuterBorderNs.TryGetValue((row, col), out string ns); + return ns; + } + + /// 设置外边框样式(-1 行 / -1 列) + public void SetOuterBorderNs(int row, int col, string ns) + { + if (OuterBorderNs == null) + OuterBorderNs = new Dictionary<(int, int), string>(); + OuterBorderNs[(row, col)] = ns; + } + + /// 清空所有边框缓存 + public void ClearAllBorderCache() + { + OuterBorderNs?.Clear(); + foreach (var kv in BlockMap) + { + 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); + } + } + + // ── OuterBorderNs 序列化 ── + private void SerializeOuterBorder() + { + m_OuterBorderRows.Clear(); + m_OuterBorderCols.Clear(); + m_OuterBorderValues.Clear(); + if (OuterBorderNs != null) + { + foreach (var kv in OuterBorderNs) + { + m_OuterBorderRows.Add(kv.Key.row); + m_OuterBorderCols.Add(kv.Key.col); + m_OuterBorderValues.Add(kv.Value); + } + } + } + + private void DeserializeOuterBorder() + { + 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++) + { + if (m_OuterBorderValues[i] != null) + OuterBorderNs[(m_OuterBorderRows[i], m_OuterBorderCols[i])] = m_OuterBorderValues[i]; + } + } + + #endregion } } diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs index 3440dca..d11bbaa 100644 --- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs +++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs @@ -474,14 +474,38 @@ namespace XericUI.XTable.Rendering.Component public XTableCellData GetCellData(int row, int col) => TableData.GetOrCreateCell(row, col); - /// 设置单元格的样式命名空间 + /// 设置单元格的样式命名空间,同时写入边框缓存(谁最后设置归谁) public void SetCellStyleNamespace(int row, int col, string styleNamespace) { - var data = TableData.GetOrCreateCell(row, col); - data.StyleNamespace = 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); + } + } + } + else + { + ApplyCellStyleToCell(row, col, styleNamespace); + } + 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); + } + /// 获取单元格的样式命名空间 public string GetCellStyleNamespace(int row, int col) { @@ -1514,8 +1538,9 @@ namespace XericUI.XTable.Rendering.Component /// /// 重建表格的全部边框线段。 - /// 遍历所有非合并覆盖的单元格,为每个单元格的右边界和下边界生成线段, - /// 合并源单元格的线段位置取合并范围的最外沿,内部边界自动跳过。 + /// 第一阶段:从 OuterBorderNs 绘制 -1 行(顶部外框)和 -1 列(左侧外框)。 + /// 第二阶段:遍历非合并覆盖单元格,从边框缓存读取样式命名空间,按 per-cell 样式绘制右侧和底部边框。 + /// 合并源在最外沿绘制,内部边框自动跳过。 /// private void RebuildBorders() { @@ -1525,31 +1550,50 @@ namespace XericUI.XTable.Rendering.Component 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 = RT_BORDER_COLOR; - borderWidth = RT_BORDER_WIDTH; - } -#else - Color borderColor = RT_BORDER_COLOR; - float borderWidth = RT_BORDER_WIDTH; -#endif - int rows = m_RowHeights.Length; int cols = m_ColWidths.Length; + // ════════════════════════════════════════════ + // 阶段 1:-1 行(顶部外框)和 -1 列(左侧外框) + // ════════════════════════════════════════════ + + // 顶部外框:从 col=0 到 col=cols-1,每列一段 + float topY = 0; + for (int c = 0; c < cols; c++) + { + 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++) @@ -1576,21 +1620,35 @@ namespace XericUI.XTable.Rendering.Component 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), - borderColor, borderWidth, UVMode.ByDistance); + 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), - borderColor, borderWidth, UVMode.ByDistance); + bottomColor, bottomWidth, UVMode.ByDistance); + } } } }