重写表格的重建逻辑,优化边框的保存逻辑,优化样式树构建时机。
This commit is contained in:
@@ -33,10 +33,9 @@ namespace XericUI.XTable.Core
|
||||
/// <summary>合并单元格描述列表</summary>
|
||||
public List<XTableMergeDescriptor> MergeDescriptors;
|
||||
|
||||
/// <summary>每个单元格右侧边框的样式命名空间缓存(线性数组,同 Cells[] 布局)</summary>
|
||||
public string[] BorderRightNs;
|
||||
/// <summary>每个单元格底部边框的样式命名空间缓存(线性数组,同 Cells[] 布局)</summary>
|
||||
public string[] BorderBottomNs;
|
||||
// 仅用于读取旧资产;新块不分配、运行时不使用这些逐格边框数组。
|
||||
[HideInInspector] public string[] BorderRightNs;
|
||||
[HideInInspector] public string[] BorderBottomNs;
|
||||
|
||||
/// <summary>
|
||||
/// 创建指定尺寸的块
|
||||
@@ -47,8 +46,6 @@ namespace XericUI.XTable.Core
|
||||
BlockSizeY = blockSizeY;
|
||||
Cells = new XTableCellData[blockSizeX * blockSizeY];
|
||||
MergeDescriptors = new List<XTableMergeDescriptor>();
|
||||
BorderRightNs = new string[blockSizeX * blockSizeY];
|
||||
BorderBottomNs = new string[blockSizeX * blockSizeY];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -81,38 +78,6 @@ namespace XericUI.XTable.Core
|
||||
Cells[index] = data;
|
||||
}
|
||||
|
||||
/// <summary>通过块内局部坐标获取右侧边框缓存</summary>
|
||||
public string GetBorderRight(int localRow, int localCol)
|
||||
{
|
||||
int index = localRow * BlockSizeX + localCol;
|
||||
if (index < 0 || index >= (BorderRightNs?.Length ?? 0)) return null;
|
||||
return BorderRightNs[index];
|
||||
}
|
||||
|
||||
/// <summary>通过块内局部坐标设置右侧边框缓存</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>通过块内局部坐标获取底部边框缓存</summary>
|
||||
public string GetBorderBottom(int localRow, int localCol)
|
||||
{
|
||||
int index = localRow * BlockSizeX + localCol;
|
||||
if (index < 0 || index >= (BorderBottomNs?.Length ?? 0)) return null;
|
||||
return BorderBottomNs[index];
|
||||
}
|
||||
|
||||
/// <summary>通过块内局部坐标设置底部边框缓存</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有合并描述重定向此单元格
|
||||
/// </summary>
|
||||
@@ -142,10 +107,6 @@ namespace XericUI.XTable.Core
|
||||
// 确保数组和列表非空
|
||||
if (Cells == null) Cells = new XTableCellData[BlockSizeX * BlockSizeY];
|
||||
if (MergeDescriptors == null) MergeDescriptors = new List<XTableMergeDescriptor>();
|
||||
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
|
||||
|
||||
+182
-131
@@ -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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表格数据主类——持有块字典,提供单元格级别的读写接口。
|
||||
/// 块按需创建(SetCell 时自动创建不存在的块),通过 Z 曲线索引映射。
|
||||
@@ -38,6 +56,16 @@ namespace XericUI.XTable.Core
|
||||
[SerializeField] private List<ulong> m_BlockKeys = new List<ulong>();
|
||||
[SerializeField] private List<XTableBlock> m_BlockValues = new List<XTableBlock>();
|
||||
|
||||
[SerializeField] private List<string> m_BorderStyles = new List<string>();
|
||||
[SerializeField] private List<XTableBorderRange> m_BorderRanges = new List<XTableBorderRange>();
|
||||
|
||||
// 仅用于读取旧资产的外边框序列化代理;迁移后不再写入。
|
||||
[SerializeField] private List<int> m_OuterBorderRows = new List<int>();
|
||||
[SerializeField] private List<int> m_OuterBorderCols = new List<int>();
|
||||
[SerializeField] private List<string> m_OuterBorderValues = new List<string>();
|
||||
|
||||
[NonSerialized] private Dictionary<(XTableBorderAxis axis, int fixedIndex), List<XTableBorderRange>> m_BorderRangeIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 获取或创建单元格——如果单元格不存在则自动创建空数据。
|
||||
/// 在编辑器和运行时都是安全的,保证不会返回 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<string>();
|
||||
if (m_BorderRanges == null) m_BorderRanges = new List<XTableBorderRange>();
|
||||
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 边框范围
|
||||
|
||||
/// <summary>
|
||||
/// 外边框样式管理器——维护 -1 行 / -1 列的边框样式命名空间。
|
||||
/// key = (row, col),其中 row / col 可为 -1。
|
||||
/// (-1, -1) = 表格最外框,
|
||||
/// (-1, c) = 第 c 列顶部边框,
|
||||
/// (r, -1) = 第 r 行左侧边框。
|
||||
/// </summary>
|
||||
[NonSerialized] public Dictionary<(int row, int col), string> OuterBorderNs;
|
||||
public IReadOnlyList<XTableBorderRange> BorderRanges => m_BorderRanges;
|
||||
|
||||
// OuterBorderNs 序列化代理
|
||||
[SerializeField] private List<int> m_OuterBorderRows = new List<int>();
|
||||
[SerializeField] private List<int> m_OuterBorderCols = new List<int>();
|
||||
[SerializeField] private List<string> m_OuterBorderValues = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// 获取右侧边框样式命名空间。
|
||||
/// col < 0 时退回到 OuterBorderNs。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置右侧边框样式命名空间。
|
||||
/// col < 0 时写入 OuterBorderNs。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取底部边框样式命名空间。
|
||||
/// row < 0 时退回到 OuterBorderNs。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置底部边框样式命名空间。
|
||||
/// row < 0 时写入 OuterBorderNs。
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>获取外边框样式(-1 行 / -1 列)</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>设置外边框样式(-1 行 / -1 列)</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>清空所有边框缓存</summary>
|
||||
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<XTableBorderRange>();
|
||||
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<XTableBorderRange>(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<XTableBorderRange>>();
|
||||
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<XTableBorderRange>();
|
||||
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<XTableBorderRange> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -232,6 +232,25 @@ namespace XericUI.XTable.Core
|
||||
QueryNode(m_Root, visibleRect, results);
|
||||
}
|
||||
|
||||
/// <summary>获取完整表格的所有均匀叶子,用于背景分区提交。</summary>
|
||||
public void QueryAll(List<QuadrantResult> results)
|
||||
{
|
||||
if (results == null || m_Root == null) return;
|
||||
QueryAllNodes(m_Root, results);
|
||||
}
|
||||
|
||||
private void QueryAllNodes(Node node, List<QuadrantResult> 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<QuadrantResult> results)
|
||||
{
|
||||
// 不相交则跳过
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -79,17 +79,13 @@ namespace XericUI.XTable.Rendering.Component
|
||||
/// <summary>设置单元格的样式命名空间(兼容旧版,等同于 StyleNamespace 字段)</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>在单个单元格上设置样式,同时写入其右侧和底部的边框缓存</summary>
|
||||
/// <summary>设置单元格样式并写入其可见右侧、底部边框范围。</summary>
|
||||
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);
|
||||
}
|
||||
|
||||
/// <summary>清除文本内容</summary>
|
||||
@@ -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);
|
||||
|
||||
@@ -65,139 +65,88 @@ namespace XericUI.XTable.Rendering.Component
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重建表格的全部边框线段。
|
||||
/// 第一阶段:从 OuterBorderNs 绘制 -1 行(顶部外框)和 -1 列(左侧外框)。
|
||||
/// 第二阶段:遍历非合并覆盖单元格,从边框缓存读取样式命名空间,按 per-cell 样式绘制右侧和底部边框。
|
||||
/// 合并源在最外沿绘制,内部边框自动跳过。
|
||||
/// </summary>
|
||||
/// <summary>按边界范围提交表格边框;默认样式连续边会合并为单条线段。</summary>
|
||||
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<RectTransform>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,40 +153,81 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
#region 四叉树
|
||||
|
||||
/// <summary>强制重建四叉树(外部调用入口,适合编辑器调试或手动刷新)</summary>
|
||||
/// <summary>强制重建四叉树(仅多样式表会实际构建)。</summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>只在多样式背景分区需要时,从当前数据构建四叉树。</summary>
|
||||
private void EnsureQuadtreeBuilt()
|
||||
{
|
||||
if (!HasMultipleStyleNamespaces())
|
||||
{
|
||||
m_Quadtree?.Clear();
|
||||
m_QuadtreeDirty = false;
|
||||
return;
|
||||
}
|
||||
if (!m_QuadtreeDirty && m_Quadtree != null && m_Quadtree.IsBuilt) return;
|
||||
MaintainQuadtree();
|
||||
}
|
||||
|
||||
/// <summary>从当前 XTableData 收集单元格信息并重建四叉树</summary>
|
||||
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<XTableQuadtreeCell>();
|
||||
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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 单元格内容渲染(统一入口)
|
||||
|
||||
/// <summary>渲染单个单元格的完整内容(选择高亮 + 网格线 + 文本/图片/预制体)。</summary>
|
||||
private void RenderCellContent(int row, int col, string defaultNs,
|
||||
bool withGridLines, bool editorMode)
|
||||
/// <summary>渲染单个单元格内容(选择高亮 + 文本/图片/预制体)。</summary>
|
||||
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<RectTransform>();
|
||||
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<RectTransform>();
|
||||
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<RectTransform>();
|
||||
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<Image>();
|
||||
bgImg.color = GetCellBgColor(m_DefaultStyleNamespace);
|
||||
bgImg.raycastTarget = false;
|
||||
m_BackgroundRenderer = backgroundGo.GetComponent<XTableBackgroundRenderer>();
|
||||
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<XTableBackgroundRenderer.RectData>
|
||||
{
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>预制体对象池——按原型分组管理</summary>
|
||||
[System.NonSerialized] private PrefabPrototypePool m_PrefabPool;
|
||||
@@ -76,8 +76,11 @@ namespace XericUI.XTable.Rendering.Component
|
||||
/// <summary>Texture2D → Sprite 缓存,避免每帧 Sprite.Create 分配</summary>
|
||||
[System.NonSerialized] private Dictionary<Texture2D, Sprite> m_SpriteCache;
|
||||
|
||||
/// <summary>表格四叉树——按 StyleNamespace 分区,渲染阶段查询可见象限</summary>
|
||||
/// <summary>表格四叉树——仅多样式背景分区时按需构建</summary>
|
||||
[System.NonSerialized] private XTableQuadtree m_Quadtree;
|
||||
[System.NonSerialized] private bool m_QuadtreeDirty = true;
|
||||
[System.NonSerialized] private readonly HashSet<string> m_ActiveStyleNamespaces = new();
|
||||
[System.NonSerialized] private bool m_StyleNamespaceDirty = true;
|
||||
|
||||
/// <summary>边框线段渲染器——使用 UILineRendererV2 的坐标线段绘制</summary>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <summary>运行时样式变更回调:标记脏数据,下一帧重绘</summary>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>用于批量绘制 XTable 背景区域的图元渲染器。</summary>
|
||||
public sealed class XTableBackgroundRenderer : PrimitiveCacheRendererBase
|
||||
{
|
||||
/// <summary>以左上角为原点的背景区域及其填充颜色。</summary>
|
||||
public struct RectData
|
||||
{
|
||||
public Rect Rect;
|
||||
public Color Color;
|
||||
|
||||
public RectData(Rect rect, Color color)
|
||||
{
|
||||
Rect = rect;
|
||||
Color = color;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>以左上角坐标系替换全部背景矩形。</summary>
|
||||
public void ReplaceRects(IReadOnlyList<RectData> 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,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3752be8845ec6624c8b9888048c6c882
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user