表格添加四叉树渲染优化逻辑。
This commit is contained in:
@@ -176,18 +176,26 @@ namespace XericUIEditor.XTable
|
||||
[MenuItem("GameObject/Xeric UI/Table/Xeric UI Action Table", false, 60)]
|
||||
private static void CreateTable(MenuCommand menuCommand)
|
||||
{
|
||||
// 1. 创建表格内容节点
|
||||
GameObject contentGo = new GameObject("Content",
|
||||
typeof(RectTransform), typeof(CanvasRenderer), typeof(XericUIActionTable));
|
||||
// 1. 创建根节点:ScrollRect + Image + XericUIActionTable 共存于同一 GameObject
|
||||
GameObject rootGo = new GameObject("Xeric UI Action Table",
|
||||
typeof(RectTransform), typeof(ScrollRect), typeof(Image), typeof(XericUIActionTable));
|
||||
RectTransform rootRt = rootGo.GetComponent<RectTransform>();
|
||||
rootRt.sizeDelta = new Vector2(800, 600);
|
||||
rootGo.GetComponent<Image>().color = new Color(0.1f, 0.1f, 0.1f, 0.3f);
|
||||
|
||||
ScrollRect scrollRect = rootGo.GetComponent<ScrollRect>();
|
||||
XericUIActionTable table = rootGo.GetComponent<XericUIActionTable>();
|
||||
|
||||
// 2. 创建 Content(纯 RectTransform,由表格组件管理动态子对象)
|
||||
GameObject contentGo = new GameObject("Content", typeof(RectTransform));
|
||||
RectTransform contentRt = contentGo.GetComponent<RectTransform>();
|
||||
contentRt.anchorMin = new Vector2(0, 1);
|
||||
contentRt.anchorMax = new Vector2(0, 1);
|
||||
contentRt.pivot = new Vector2(0, 1);
|
||||
contentRt.anchoredPosition = Vector2.zero;
|
||||
contentRt.sizeDelta = new Vector2(800, 600);
|
||||
XericUIActionTable table = contentGo.GetComponent<XericUIActionTable>();
|
||||
|
||||
// 2. 创建 Viewport(Mask 裁剪区域)
|
||||
// 3. 创建 Viewport(Mask 裁剪区域)
|
||||
GameObject viewportGo = new GameObject("Viewport",
|
||||
typeof(RectTransform), typeof(Image), typeof(Mask));
|
||||
viewportGo.GetComponent<Image>().color = new Color(1, 1, 1, 0.01f);
|
||||
@@ -197,7 +205,7 @@ namespace XericUIEditor.XTable
|
||||
vpRt.sizeDelta = Vector2.zero;
|
||||
contentRt.SetParent(vpRt, false);
|
||||
|
||||
// 3. 创建水平滚动条
|
||||
// 4. 创建水平滚动条
|
||||
GameObject hScrollbarGo = new GameObject("Scrollbar Horizontal",
|
||||
typeof(RectTransform), typeof(Scrollbar), typeof(Image));
|
||||
RectTransform hBarRt = hScrollbarGo.GetComponent<RectTransform>();
|
||||
@@ -217,7 +225,7 @@ namespace XericUIEditor.XTable
|
||||
RectTransform hSlidingRt = hSlidingGo.GetComponent<RectTransform>();
|
||||
hSlidingRt.anchorMin = Vector2.zero;
|
||||
hSlidingRt.anchorMax = Vector2.one;
|
||||
hSlidingRt.sizeDelta = new Vector2(-20, -20);
|
||||
hSlidingRt.sizeDelta = new Vector2(-20, 0);
|
||||
|
||||
GameObject hHandleGo = new GameObject("Handle",
|
||||
typeof(RectTransform), typeof(Image));
|
||||
@@ -225,11 +233,11 @@ namespace XericUIEditor.XTable
|
||||
RectTransform hHandleRt = hHandleGo.GetComponent<RectTransform>();
|
||||
hHandleRt.anchorMin = Vector2.zero;
|
||||
hHandleRt.anchorMax = Vector2.one;
|
||||
hHandleRt.sizeDelta = new Vector2(-20, -20);
|
||||
hHandleRt.sizeDelta = new Vector2(-10, -10);
|
||||
hHandleGo.GetComponent<Image>().color = new Color(0.4f, 0.4f, 0.4f, 0.8f);
|
||||
hBar.handleRect = hHandleRt;
|
||||
|
||||
// 4. 创建垂直滚动条
|
||||
// 5. 创建垂直滚动条
|
||||
GameObject vScrollbarGo = new GameObject("Scrollbar Vertical",
|
||||
typeof(RectTransform), typeof(Scrollbar), typeof(Image));
|
||||
RectTransform vBarRt = vScrollbarGo.GetComponent<RectTransform>();
|
||||
@@ -249,7 +257,7 @@ namespace XericUIEditor.XTable
|
||||
RectTransform vSlidingRt = vSlidingGo.GetComponent<RectTransform>();
|
||||
vSlidingRt.anchorMin = Vector2.zero;
|
||||
vSlidingRt.anchorMax = Vector2.one;
|
||||
vSlidingRt.sizeDelta = new Vector2(-20, -20);
|
||||
vSlidingRt.sizeDelta = new Vector2(0, -20);
|
||||
|
||||
GameObject vHandleGo = new GameObject("Handle",
|
||||
typeof(RectTransform), typeof(Image));
|
||||
@@ -257,18 +265,11 @@ namespace XericUIEditor.XTable
|
||||
RectTransform vHandleRt = vHandleGo.GetComponent<RectTransform>();
|
||||
vHandleRt.anchorMin = Vector2.zero;
|
||||
vHandleRt.anchorMax = Vector2.one;
|
||||
vHandleRt.sizeDelta = new Vector2(-20, -20);
|
||||
vHandleRt.sizeDelta = new Vector2(-10, -10);
|
||||
vHandleGo.GetComponent<Image>().color = new Color(0.4f, 0.4f, 0.4f, 0.8f);
|
||||
vBar.handleRect = vHandleRt;
|
||||
|
||||
// 5. 创建 ScrollView 根节点
|
||||
GameObject scrollGo = new GameObject("Xeric UI Action Table",
|
||||
typeof(RectTransform), typeof(ScrollRect), typeof(Image));
|
||||
RectTransform scrollRt = scrollGo.GetComponent<RectTransform>();
|
||||
scrollRt.sizeDelta = new Vector2(800, 600);
|
||||
scrollGo.GetComponent<Image>().color = new Color(0.1f, 0.1f, 0.1f, 0.3f);
|
||||
|
||||
ScrollRect scrollRect = scrollGo.GetComponent<ScrollRect>();
|
||||
// 6. 配置 ScrollRect
|
||||
scrollRect.content = contentRt;
|
||||
scrollRect.viewport = vpRt;
|
||||
scrollRect.horizontalScrollbar = hBar;
|
||||
@@ -278,11 +279,11 @@ namespace XericUIEditor.XTable
|
||||
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||
|
||||
// 组装层级
|
||||
vpRt.SetParent(scrollRt, false);
|
||||
hScrollbarGo.transform.SetParent(scrollRt, false);
|
||||
vScrollbarGo.transform.SetParent(scrollRt, false);
|
||||
vpRt.SetParent(rootRt, false);
|
||||
hScrollbarGo.transform.SetParent(rootRt, false);
|
||||
vScrollbarGo.transform.SetParent(rootRt, false);
|
||||
|
||||
// 绑定 ScrollRect 到表格(通过 SerializedObject 正确回写)
|
||||
// 绑定 ScrollRect 到表格
|
||||
using (SerializedObject so = new SerializedObject(table))
|
||||
{
|
||||
SerializedProperty prop = so.FindProperty("m_ScrollRect");
|
||||
@@ -295,10 +296,10 @@ namespace XericUIEditor.XTable
|
||||
|
||||
// 挂载到 Canvas
|
||||
GameObject parent = GetOrCreateCanvas();
|
||||
scrollRt.SetParent(parent.transform, false);
|
||||
rootRt.SetParent(parent.transform, false);
|
||||
|
||||
Undo.RegisterCreatedObjectUndo(scrollGo, "Create Xeric UI Action Table");
|
||||
Selection.activeGameObject = scrollGo;
|
||||
Undo.RegisterCreatedObjectUndo(rootGo, "Create Xeric UI Action Table");
|
||||
Selection.activeGameObject = rootGo;
|
||||
}
|
||||
|
||||
private static GameObject GetOrCreateCanvas()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using UnityEditor;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using XericLibrary.Runtime.MacroLibrary;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
using XericUI.XTable.Core;
|
||||
using XericUI.XTable.Mapping;
|
||||
@@ -179,7 +179,9 @@ namespace XericUIEditor.XTable
|
||||
Canvas.ForceUpdateCanvases();
|
||||
#endif
|
||||
|
||||
RectTransform tableRt = table.GetComponent<RectTransform>();
|
||||
// 使用 Content 的 RectTransform 进行定位计算(而非表格组件所在的 ScrollRect 根节点)
|
||||
RectTransform tableRt = table.ContentTransform.RectTransform();
|
||||
if (tableRt == null) tableRt = table.GetComponent<RectTransform>();
|
||||
float[] rh = table.RowHeights;
|
||||
float[] cw = table.ColWidths;
|
||||
if (rh == null || cw == null || rh.Length == 0 || cw.Length == 0) return;
|
||||
@@ -361,31 +363,32 @@ namespace XericUIEditor.XTable
|
||||
s_DragStartCol = col;
|
||||
|
||||
if (Event.current.shift && table.SelectedRow >= 0)
|
||||
{
|
||||
if (!table.HasMultiSelection)
|
||||
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
|
||||
table.ExtendMultiSelect(r, col);
|
||||
}
|
||||
{
|
||||
if (!table.HasMultiSelection)
|
||||
table.SelectCell(table.SelectedRow, table.SelectedCol, r, col);
|
||||
else
|
||||
{
|
||||
table.StartMultiSelect(r, col);
|
||||
}
|
||||
|
||||
table.RefreshView();
|
||||
SyncEditText(table);
|
||||
Event.current.Use();
|
||||
SceneView.RepaintAll();
|
||||
table.SelectionHandle.ExtendTo(r, col);
|
||||
}
|
||||
else
|
||||
{
|
||||
table.SelectCell(r, col);
|
||||
}
|
||||
|
||||
y += cellH;
|
||||
table.RefreshView();
|
||||
SyncEditText(table);
|
||||
Event.current.Use();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
y += cellH;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
#region 列标签
|
||||
#region 列标签
|
||||
|
||||
private static void DrawColumnLabels(float[] cw, Rect tableRect,
|
||||
private static void DrawColumnLabels(float[] cw, Rect tableRect,
|
||||
float xScale, float yScale, XericUIActionTable table)
|
||||
{
|
||||
var style = new GUIStyle(GUI.skin.label)
|
||||
@@ -436,25 +439,26 @@ namespace XericUIEditor.XTable
|
||||
s_DragStartCol = c;
|
||||
|
||||
if (Event.current.shift && table.SelectedCol >= 0)
|
||||
{
|
||||
if (!table.HasMultiSelection)
|
||||
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
|
||||
table.ExtendMultiSelect(row, c);
|
||||
}
|
||||
{
|
||||
if (!table.HasMultiSelection)
|
||||
table.SelectCell(table.SelectedRow, table.SelectedCol, row, c);
|
||||
else
|
||||
{
|
||||
table.StartMultiSelect(row, c);
|
||||
}
|
||||
|
||||
table.RefreshView();
|
||||
SyncEditText(table);
|
||||
Event.current.Use();
|
||||
SceneView.RepaintAll();
|
||||
table.SelectionHandle.ExtendTo(row, c);
|
||||
}
|
||||
else
|
||||
{
|
||||
table.SelectCell(row, c);
|
||||
}
|
||||
|
||||
x += cellW;
|
||||
table.RefreshView();
|
||||
SyncEditText(table);
|
||||
Event.current.Use();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
x += cellW;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -972,15 +976,16 @@ namespace XericUIEditor.XTable
|
||||
s_DragStartCol = mdCol;
|
||||
|
||||
if (e.shift && table.SelectedRow >= 0 && table.SelectedCol >= 0)
|
||||
{
|
||||
if (!table.HasMultiSelection)
|
||||
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
|
||||
table.ExtendMultiSelect(mdRow, mdCol);
|
||||
}
|
||||
{
|
||||
if (!table.HasMultiSelection)
|
||||
table.SelectCell(table.SelectedRow, table.SelectedCol, mdRow, mdCol);
|
||||
else
|
||||
{
|
||||
table.StartMultiSelect(mdRow, mdCol);
|
||||
}
|
||||
table.SelectionHandle.ExtendTo(mdRow, mdCol);
|
||||
}
|
||||
else
|
||||
{
|
||||
table.SelectCell(mdRow, mdCol);
|
||||
}
|
||||
|
||||
table.RefreshView();
|
||||
SyncEditText(table);
|
||||
@@ -994,7 +999,7 @@ namespace XericUIEditor.XTable
|
||||
CellFromMouse(tr, xScale, yScale, out int dragRow, out int dragCol);
|
||||
if (dragRow < 0 || dragCol < 0) break;
|
||||
|
||||
table.ExtendMultiSelect(dragRow, dragCol);
|
||||
table.SelectionHandle.ExtendTo(dragRow, dragCol);
|
||||
table.RefreshView();
|
||||
SyncEditText(table);
|
||||
e.Use();
|
||||
@@ -1127,16 +1132,17 @@ namespace XericUIEditor.XTable
|
||||
}
|
||||
|
||||
if (shift)
|
||||
{
|
||||
if (!table.HasMultiSelection && table.SelectedRow >= 0 && table.SelectedCol >= 0)
|
||||
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
|
||||
table.ExtendMultiSelect(row, col);
|
||||
}
|
||||
{
|
||||
if (!table.HasMultiSelection && table.SelectedRow >= 0 && table.SelectedCol >= 0)
|
||||
table.SelectCell(table.SelectedRow, table.SelectedCol, row, col);
|
||||
else
|
||||
{
|
||||
RedirectToMergeSource(ref row, ref col);
|
||||
table.SelectCell(row, col);
|
||||
}
|
||||
table.SelectionHandle.ExtendTo(row, col);
|
||||
}
|
||||
else
|
||||
{
|
||||
RedirectToMergeSource(ref row, ref col);
|
||||
table.SelectCell(row, col);
|
||||
}
|
||||
|
||||
table.RefreshView();
|
||||
SyncEditText(table);
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.XTable.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 表格四叉树构建用的单元格元数据。
|
||||
/// </summary>
|
||||
public struct XTableQuadtreeCell
|
||||
{
|
||||
public int Row;
|
||||
public int Col;
|
||||
public string Namespace;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 四叉树查询结果——一个均匀象限的可见部分。
|
||||
/// </summary>
|
||||
public struct QuadrantResult
|
||||
{
|
||||
/// <summary>该象限在表格像素空间中的区域(左上角坐标系,y 向下为正)</summary>
|
||||
public Rect Region;
|
||||
|
||||
/// <summary>统一命名空间(空字符串 = 空区域)</summary>
|
||||
public string Namespace;
|
||||
|
||||
/// <summary>该象限包含的单元格坐标列表</summary>
|
||||
public List<(int row, int col)> Cells;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表格四叉树——基于单元格 <see cref="XTableQuadtreeCell.Namespace"/> 进行递归区域分割。
|
||||
/// 若一个象限内存在不同命名空间的单元格,则继续分裂,直到所有差异分开。
|
||||
/// 渲染阶段通过 <see cref="QueryVisible"/> 获取可见范围内的所有均匀象限。
|
||||
/// </summary>
|
||||
public class XTableQuadtree
|
||||
{
|
||||
#region 内部节点
|
||||
|
||||
private class Node
|
||||
{
|
||||
/// <summary>当前象限在表格像素空间中的区域</summary>
|
||||
public Rect Region;
|
||||
|
||||
/// <summary>
|
||||
/// null = 非均匀节点(有子节点),非 null = 均匀叶子节点。
|
||||
/// 空字符串 "" 表示该区域无任何单元格。
|
||||
/// </summary>
|
||||
public string UniformNamespace;
|
||||
|
||||
/// <summary>子象限(null 表示叶子节点)</summary>
|
||||
public Node[] Children;
|
||||
|
||||
/// <summary>该均匀象限包含的单元格坐标(仅叶子均匀节点有效)</summary>
|
||||
public List<(int, int)> Cells;
|
||||
|
||||
public bool IsUniform => UniformNamespace != null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 字段
|
||||
|
||||
private Node m_Root;
|
||||
private float[] m_RowHeights;
|
||||
private float[] m_ColWidths;
|
||||
|
||||
/// <summary>四叉树是否已构建完成</summary>
|
||||
public bool IsBuilt { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 构建
|
||||
|
||||
/// <summary>
|
||||
/// 全量构建四叉树。
|
||||
/// </summary>
|
||||
/// <param name="totalWidth">表格总宽度(像素)</param>
|
||||
/// <param name="totalHeight">表格总高度(像素)</param>
|
||||
/// <param name="rowHeights">行高数组(索引 = 行号)</param>
|
||||
/// <param name="colWidths">列宽数组(索引 = 列号)</param>
|
||||
/// <param name="cells">所有有数据的单元格元数据列表</param>
|
||||
public void Build(float totalWidth, float totalHeight,
|
||||
float[] rowHeights, float[] colWidths,
|
||||
List<XTableQuadtreeCell> cells)
|
||||
{
|
||||
if (rowHeights == null || colWidths == null || cells == null)
|
||||
{
|
||||
IsBuilt = false;
|
||||
return;
|
||||
}
|
||||
|
||||
m_RowHeights = rowHeights;
|
||||
m_ColWidths = colWidths;
|
||||
m_Root = new Node { Region = new Rect(0, 0, totalWidth, totalHeight) };
|
||||
|
||||
int maxRow = rowHeights.Length - 1;
|
||||
int maxCol = colWidths.Length - 1;
|
||||
|
||||
if (maxRow >= 0 && maxCol >= 0)
|
||||
BuildNode(m_Root, cells, 0, maxRow, 0, maxCol);
|
||||
else
|
||||
m_Root.UniformNamespace = "";
|
||||
|
||||
IsBuilt = true;
|
||||
}
|
||||
|
||||
/// <summary>强制重建(外部入口,通常由表格组件在数据/布局变更后调用)</summary>
|
||||
public void ForceRebuild(float totalWidth, float totalHeight,
|
||||
float[] rowHeights, float[] colWidths,
|
||||
List<XTableQuadtreeCell> cells)
|
||||
{
|
||||
Clear();
|
||||
Build(totalWidth, totalHeight, rowHeights, colWidths, cells);
|
||||
}
|
||||
|
||||
private void BuildNode(Node node, List<XTableQuadtreeCell> allCells,
|
||||
int minRow, int maxRow, int minCol, int maxCol)
|
||||
{
|
||||
// 收集当前行列范围内的单元格
|
||||
var cellsInRange = new List<XTableQuadtreeCell>();
|
||||
for (int i = 0; i < allCells.Count; i++)
|
||||
{
|
||||
var cell = allCells[i];
|
||||
if (cell.Row >= minRow && cell.Row <= maxRow &&
|
||||
cell.Col >= minCol && cell.Col <= maxCol)
|
||||
cellsInRange.Add(cell);
|
||||
}
|
||||
|
||||
// 空区域
|
||||
if (cellsInRange.Count == 0)
|
||||
{
|
||||
node.UniformNamespace = "";
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否所有单元格命名空间一致
|
||||
string firstNs = NormalizeNs(cellsInRange[0].Namespace);
|
||||
bool allSame = true;
|
||||
for (int i = 1; i < cellsInRange.Count; i++)
|
||||
{
|
||||
if (NormalizeNs(cellsInRange[i].Namespace) != firstNs)
|
||||
{
|
||||
allSame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allSame)
|
||||
{
|
||||
node.UniformNamespace = firstNs;
|
||||
node.Cells = new List<(int, int)>(cellsInRange.Count);
|
||||
for (int i = 0; i < cellsInRange.Count; i++)
|
||||
node.Cells.Add((cellsInRange[i].Row, cellsInRange[i].Col));
|
||||
return;
|
||||
}
|
||||
|
||||
// 无法继续分裂(单行单列),强制标记为均匀
|
||||
if (minRow >= maxRow && minCol >= maxCol)
|
||||
{
|
||||
node.UniformNamespace = firstNs;
|
||||
node.Cells = new List<(int, int)>(cellsInRange.Count);
|
||||
for (int i = 0; i < cellsInRange.Count; i++)
|
||||
node.Cells.Add((cellsInRange[i].Row, cellsInRange[i].Col));
|
||||
return;
|
||||
}
|
||||
|
||||
// 分裂:在行列索引空间取中点
|
||||
int midRow = (minRow + maxRow) / 2;
|
||||
int midCol = (minCol + maxCol) / 2;
|
||||
|
||||
node.Children = new Node[4];
|
||||
|
||||
// 左上 (minRow..midRow, minCol..midCol)
|
||||
node.Children[0] = MakeChild(minRow, midRow, minCol, midCol);
|
||||
BuildNode(node.Children[0], allCells, minRow, midRow, minCol, midCol);
|
||||
|
||||
// 右上 (minRow..midRow, midCol+1..maxCol)
|
||||
node.Children[1] = MakeChild(minRow, midRow, midCol + 1, maxCol);
|
||||
BuildNode(node.Children[1], allCells, minRow, midRow, midCol + 1, maxCol);
|
||||
|
||||
// 左下 (midRow+1..maxRow, minCol..midCol)
|
||||
node.Children[2] = MakeChild(midRow + 1, maxRow, minCol, midCol);
|
||||
BuildNode(node.Children[2], allCells, midRow + 1, maxRow, minCol, midCol);
|
||||
|
||||
// 右下 (midRow+1..maxRow, midCol+1..maxCol)
|
||||
node.Children[3] = MakeChild(midRow + 1, maxRow, midCol + 1, maxCol);
|
||||
BuildNode(node.Children[3], allCells, midRow + 1, maxRow, midCol + 1, maxCol);
|
||||
}
|
||||
|
||||
private Node MakeChild(int minRow, int maxRow, int minCol, int maxCol)
|
||||
{
|
||||
float x = GetColLeftX(minCol);
|
||||
float y = GetRowTopY(minRow);
|
||||
float w = GetColRightX(maxCol) - x;
|
||||
float h = GetRowBottomY(maxRow) - y;
|
||||
return new Node { Region = new Rect(x, y, w, h) };
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 查询
|
||||
|
||||
/// <summary>
|
||||
/// 查询可见矩形内的所有均匀象限。
|
||||
/// </summary>
|
||||
/// <param name="visibleRect">可见矩形(左上角坐标系)</param>
|
||||
/// <param name="results">结果列表(复用外部容器)</param>
|
||||
public void QueryVisible(Rect visibleRect, List<QuadrantResult> results)
|
||||
{
|
||||
if (results == null)
|
||||
results = new List<QuadrantResult>();
|
||||
if (m_Root == null)
|
||||
return;
|
||||
|
||||
QueryNode(m_Root, visibleRect, results);
|
||||
}
|
||||
|
||||
private void QueryNode(Node node, Rect visibleRect, List<QuadrantResult> results)
|
||||
{
|
||||
// 不相交则跳过
|
||||
if (!RectIntersects(node.Region, visibleRect))
|
||||
return;
|
||||
|
||||
if (node.IsUniform)
|
||||
{
|
||||
// 裁剪到可见范围
|
||||
Rect clipped = RectIntersection(node.Region, visibleRect);
|
||||
if (clipped.width > 0 && clipped.height > 0)
|
||||
{
|
||||
results.Add(new QuadrantResult
|
||||
{
|
||||
Region = clipped,
|
||||
Namespace = node.UniformNamespace,
|
||||
Cells = node.Cells
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 非均匀节点:递归查询子节点
|
||||
if (node.Children != null)
|
||||
{
|
||||
for (int i = 0; i < node.Children.Length; i++)
|
||||
{
|
||||
if (node.Children[i] != null)
|
||||
QueryNode(node.Children[i], visibleRect, results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 清理
|
||||
|
||||
/// <summary>清空四叉树</summary>
|
||||
public void Clear()
|
||||
{
|
||||
m_Root = null;
|
||||
m_RowHeights = null;
|
||||
m_ColWidths = null;
|
||||
IsBuilt = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 坐标计算
|
||||
|
||||
private float GetRowTopY(int row)
|
||||
{
|
||||
float y = 0;
|
||||
for (int i = 0; i < row && i < m_RowHeights.Length; i++)
|
||||
y += m_RowHeights[i];
|
||||
return y;
|
||||
}
|
||||
|
||||
private float GetRowBottomY(int row)
|
||||
{
|
||||
float y = 0;
|
||||
for (int i = 0; i <= row && i < m_RowHeights.Length; i++)
|
||||
y += m_RowHeights[i];
|
||||
return y;
|
||||
}
|
||||
|
||||
private float GetColLeftX(int col)
|
||||
{
|
||||
float x = 0;
|
||||
for (int i = 0; i < col && i < m_ColWidths.Length; i++)
|
||||
x += m_ColWidths[i];
|
||||
return x;
|
||||
}
|
||||
|
||||
private float GetColRightX(int col)
|
||||
{
|
||||
float x = 0;
|
||||
for (int i = 0; i <= col && i < m_ColWidths.Length; i++)
|
||||
x += m_ColWidths[i];
|
||||
return x;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 工具
|
||||
|
||||
/// <summary>命名空间规范化:null、空串、默认命名空间统一为 "xeric_table_default"</summary>
|
||||
private static string NormalizeNs(string ns)
|
||||
{
|
||||
if (string.IsNullOrEmpty(ns) || ns == "xeric_table_default")
|
||||
return "xeric_table_default";
|
||||
return ns;
|
||||
}
|
||||
|
||||
private static bool RectIntersects(Rect a, Rect b)
|
||||
{
|
||||
return a.x < b.xMax && a.xMax > b.x &&
|
||||
a.y < b.yMax && a.yMax > b.y;
|
||||
}
|
||||
|
||||
private static Rect RectIntersection(Rect a, Rect b)
|
||||
{
|
||||
float x = Mathf.Max(a.x, b.x);
|
||||
float y = Mathf.Max(a.y, b.y);
|
||||
float xMax = Mathf.Min(a.xMax, b.xMax);
|
||||
float yMax = Mathf.Min(a.yMax, b.yMax);
|
||||
return new Rect(x, y, Mathf.Max(0, xMax - x), Mathf.Max(0, yMax - y));
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90a1712e28eeec248b95350379e42764
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,194 @@
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace XericUI.XTable.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 表格选择句柄——统一管理单选/多选状态。
|
||||
/// 外部可创建此句柄并通过表格的 SetSelectionHandle 或直接操作句柄来控制选区。
|
||||
/// 选区变更时触发 OnSelectionChanged 事件,表格监听此事件进行重绘。
|
||||
/// </summary>
|
||||
public class XTableSelectionHandle
|
||||
{
|
||||
#region 属性
|
||||
|
||||
/// <summary>起始行(多选锚点行,单选当前行)</summary>
|
||||
public int StartRow { get; private set; } = -1;
|
||||
|
||||
/// <summary>起始列(多选锚点列,单选当前列)</summary>
|
||||
public int StartCol { get; private set; } = -1;
|
||||
|
||||
/// <summary>结束行(仅多选有效,等于 StartRow 时为单选)</summary>
|
||||
public int EndRow { get; private set; } = -1;
|
||||
|
||||
/// <summary>结束列(仅多选有效,等于 StartCol 时为单选)</summary>
|
||||
public int EndCol { get; private set; } = -1;
|
||||
|
||||
/// <summary>是否有有效选择</summary>
|
||||
public bool HasSelection => StartRow >= 0 && StartCol >= 0;
|
||||
|
||||
/// <summary>是否为多选(起止不同)</summary>
|
||||
public bool HasMultiSelection =>
|
||||
HasSelection &&
|
||||
(StartRow != EndRow || StartCol != EndCol);
|
||||
|
||||
/// <summary>选中区域最小行</summary>
|
||||
public int MinRow => HasSelection ? Mathf.Min(StartRow, EndRow) : -1;
|
||||
|
||||
/// <summary>选中区域最大行</summary>
|
||||
public int MaxRow => HasSelection ? Mathf.Max(StartRow, EndRow) : -1;
|
||||
|
||||
/// <summary>选中区域最小列</summary>
|
||||
public int MinCol => HasSelection ? Mathf.Min(StartCol, EndCol) : -1;
|
||||
|
||||
/// <summary>选中区域最大列</summary>
|
||||
public int MaxCol => HasSelection ? Mathf.Max(StartCol, EndCol) : -1;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件
|
||||
|
||||
/// <summary>选区变化时触发</summary>
|
||||
public event Action<XTableSelectionHandle> OnSelectionChanged;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公开方法
|
||||
|
||||
/// <summary>
|
||||
/// 单选一个单元格(同时清除多选范围)
|
||||
/// </summary>
|
||||
public void Select(int row, int col)
|
||||
{
|
||||
StartRow = row;
|
||||
StartCol = col;
|
||||
EndRow = row;
|
||||
EndCol = col;
|
||||
NotifyChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 多选一个范围
|
||||
/// </summary>
|
||||
public void Select(int startRow, int startCol, int endRow, int endCol)
|
||||
{
|
||||
StartRow = startRow;
|
||||
StartCol = startCol;
|
||||
EndRow = endRow;
|
||||
EndCol = endCol;
|
||||
NotifyChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 扩展多选范围到目标格(保持原有锚点)
|
||||
/// </summary>
|
||||
public void ExtendTo(int row, int col)
|
||||
{
|
||||
if (!HasSelection)
|
||||
{
|
||||
Select(row, col);
|
||||
return;
|
||||
}
|
||||
EndRow = row;
|
||||
EndCol = col;
|
||||
NotifyChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除选择
|
||||
/// <summary>清除选择</summary>
|
||||
public void Clear()
|
||||
{
|
||||
if (!HasSelection) return;
|
||||
StartRow = -1;
|
||||
StartCol = -1;
|
||||
EndRow = -1;
|
||||
EndCol = -1;
|
||||
NotifyChanged();
|
||||
}
|
||||
|
||||
/// <summary>将选区裁剪到 [0, maxRow] × [0, maxCol] 范围</summary>
|
||||
public void ClampTo(int maxRow, int maxCol)
|
||||
{
|
||||
if (!HasSelection) return;
|
||||
int oldStartRow = StartRow, oldStartCol = StartCol;
|
||||
int oldEndRow = EndRow, oldEndCol = EndCol;
|
||||
|
||||
StartRow = Mathf.Clamp(StartRow, -1, maxRow);
|
||||
StartCol = Mathf.Clamp(StartCol, -1, maxCol);
|
||||
EndRow = Mathf.Clamp(EndRow, -1, maxRow);
|
||||
EndCol = Mathf.Clamp(EndCol, -1, maxCol);
|
||||
|
||||
if (StartRow < 0 || StartCol < 0)
|
||||
Clear();
|
||||
else if (oldStartRow != StartRow || oldStartCol != StartCol ||
|
||||
oldEndRow != EndRow || oldEndCol != EndCol)
|
||||
NotifyChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定坐标是否在选中范围内
|
||||
/// </summary>
|
||||
public bool IsInRange(int row, int col)
|
||||
{
|
||||
if (!HasSelection) return false;
|
||||
int minR = Mathf.Min(StartRow, EndRow);
|
||||
int maxR = Mathf.Max(StartRow, EndRow);
|
||||
int minC = Mathf.Min(StartCol, EndCol);
|
||||
int maxC = Mathf.Max(StartCol, EndCol);
|
||||
return row >= minR && row <= maxR && col >= minC && col <= maxC;
|
||||
}
|
||||
|
||||
/// <summary>左移一格</summary>
|
||||
public int MoveLeft(int maxCol = 0)
|
||||
{
|
||||
if (!HasSelection) return -1;
|
||||
int newCol = Mathf.Max(maxCol, StartCol - 1);
|
||||
if (newCol != StartCol)
|
||||
Select(StartRow, newCol);
|
||||
return newCol;
|
||||
}
|
||||
|
||||
/// <summary>右移一格</summary>
|
||||
public int MoveRight(int maxCol)
|
||||
{
|
||||
if (!HasSelection) return -1;
|
||||
int newCol = Mathf.Min(maxCol, StartCol + 1);
|
||||
if (newCol != StartCol)
|
||||
Select(StartRow, newCol);
|
||||
return newCol;
|
||||
}
|
||||
|
||||
/// <summary>上移一格</summary>
|
||||
public int MoveUp(int maxRow = 0)
|
||||
{
|
||||
if (!HasSelection) return -1;
|
||||
int newRow = Mathf.Max(maxRow, StartRow - 1);
|
||||
if (newRow != StartRow)
|
||||
Select(newRow, StartCol);
|
||||
return newRow;
|
||||
}
|
||||
|
||||
/// <summary>下移一格</summary>
|
||||
public int MoveDown(int maxRow)
|
||||
{
|
||||
if (!HasSelection) return -1;
|
||||
int newRow = Mathf.Min(maxRow, StartRow + 1);
|
||||
if (newRow != StartRow)
|
||||
Select(newRow, StartCol);
|
||||
return newRow;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 内部
|
||||
|
||||
private void NotifyChanged()
|
||||
{
|
||||
OnSelectionChanged?.Invoke(this);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 872c2bc5f6f1a3f47a655bdf059656bd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -9,6 +9,7 @@ using XericUI.XTable.Core;
|
||||
using XericUI.XTable.Mapping;
|
||||
using XericUI.XTable.Rendering.Elements;
|
||||
using XericLibrary.Runtime.SuperStyleSheet;
|
||||
using XericUI.XTable.Core;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
@@ -53,17 +54,16 @@ namespace XericUI.XTable.Rendering.Component
|
||||
[System.NonSerialized] private CellAssembler m_Assembler;
|
||||
[System.NonSerialized] private XTableDirtyFlag m_DirtyFlag;
|
||||
|
||||
private int m_SelectedRow = -1;
|
||||
private int m_SelectedCol = -1;
|
||||
|
||||
// 多选范围
|
||||
private int m_SelectStartRow = -1;
|
||||
private int m_SelectStartCol = -1;
|
||||
private int m_SelectEndRow = -1;
|
||||
private int m_SelectEndCol = -1;
|
||||
[System.NonSerialized] private XTableSelectionHandle m_SelectionHandle;
|
||||
|
||||
[System.NonSerialized] private GameObject m_BackgroundGo;
|
||||
|
||||
/// <summary>预制体实例缓存,key 为 (row, col),用于复用已实例化的预制体</summary>
|
||||
[System.NonSerialized] private Dictionary<(int, int), GameObject> m_PrefabInstances;
|
||||
|
||||
/// <summary>表格四叉树——按 StyleNamespace 分区,渲染阶段查询可见象限</summary>
|
||||
[System.NonSerialized] private XTableQuadtree m_Quadtree;
|
||||
|
||||
private float m_TotalWidth;
|
||||
private float m_TotalHeight;
|
||||
|
||||
@@ -100,14 +100,26 @@ namespace XericUI.XTable.Rendering.Component
|
||||
public float[] ColWidths => m_ColWidths;
|
||||
|
||||
public string DefaultStyleNamespace => m_DefaultStyleNamespace;
|
||||
public int SelectedRow => m_SelectedRow;
|
||||
public int SelectedCol => m_SelectedCol;
|
||||
|
||||
public int SelectStartRow => m_SelectStartRow;
|
||||
public int SelectStartCol => m_SelectStartCol;
|
||||
public int SelectEndRow => m_SelectEndRow;
|
||||
public int SelectEndCol => m_SelectEndCol;
|
||||
public bool HasMultiSelection => m_SelectStartRow >= 0 && m_SelectEndRow >= 0;
|
||||
/// <summary>选中操作句柄——外部可通过此句柄控制选区,或直接操作 StartRow/StartCol 等属性</summary>
|
||||
public XTableSelectionHandle SelectionHandle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_SelectionHandle == null)
|
||||
m_SelectionHandle = new XTableSelectionHandle();
|
||||
return m_SelectionHandle;
|
||||
}
|
||||
}
|
||||
|
||||
// ── 向后兼容属性(供 Editor 脚本使用)──
|
||||
public int SelectedRow => SelectionHandle.StartRow;
|
||||
public int SelectedCol => SelectionHandle.StartCol;
|
||||
public int SelectStartRow => SelectionHandle.StartRow;
|
||||
public int SelectStartCol => SelectionHandle.StartCol;
|
||||
public int SelectEndRow => SelectionHandle.EndRow;
|
||||
public int SelectEndCol => SelectionHandle.EndCol;
|
||||
public bool HasMultiSelection => SelectionHandle.HasMultiSelection;
|
||||
|
||||
/// <summary>动态子对象在 Hierarchy 中是否可见(仅编辑器有效)</summary>
|
||||
public bool ChildrenVisible
|
||||
@@ -119,12 +131,214 @@ namespace XericUI.XTable.Rendering.Component
|
||||
/// <summary>判断单元格是否在多选范围内</summary>
|
||||
public bool IsInSelectionRange(int row, int col)
|
||||
{
|
||||
if (!HasMultiSelection) return false;
|
||||
int minR = Mathf.Min(m_SelectStartRow, m_SelectEndRow);
|
||||
int maxR = Mathf.Max(m_SelectStartRow, m_SelectEndRow);
|
||||
int minC = Mathf.Min(m_SelectStartCol, m_SelectEndCol);
|
||||
int maxC = Mathf.Max(m_SelectStartCol, m_SelectEndCol);
|
||||
return row >= minR && row <= maxR && col >= minC && col <= maxC;
|
||||
return SelectionHandle.IsInRange(row, col);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动态子对象挂载的父节点。
|
||||
/// 若绑定了 ScrollRect 则使用其 Content,否则回退到自身的 rectTransform。
|
||||
/// </summary>
|
||||
public Transform ContentTransform
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ScrollRect != null && m_ScrollRect.content != null)
|
||||
return m_ScrollRect.content;
|
||||
return rectTransform;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于四叉树查询结果渲染可见象限。
|
||||
/// 每个均匀象限渲染整块背景+边框,然后遍历象限内单元格渲染文本/预制体/选择高亮。
|
||||
/// </summary>
|
||||
private void RenderQuadrants(List<QuadrantResult> quadrants)
|
||||
{
|
||||
string ns = m_DefaultStyleNamespace;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Color borderColor = GetCellBorderColor(ns);
|
||||
float borderWidth = GetCellBorderWidth(ns);
|
||||
Color selectionColor = GetCellSelectionBgColor(ns);
|
||||
Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * 0.4f);
|
||||
Color focusColor = new Color(selectionColor.r + 0.04f, selectionColor.g + 0.13f, selectionColor.b + 0.13f, selectionColor.a * 1.4f);
|
||||
|
||||
foreach (var q in quadrants)
|
||||
{
|
||||
if (string.IsNullOrEmpty(q.Namespace)) continue;
|
||||
|
||||
Rect r = q.Region;
|
||||
string qNs = q.Namespace;
|
||||
|
||||
// 象限整块背景
|
||||
var bgImg = m_Assembler.GetImage("quad_bg", qNs);
|
||||
bgImg.Rect.anchoredPosition = new Vector2(r.x, -r.y);
|
||||
bgImg.Rect.sizeDelta = new Vector2(r.width, r.height);
|
||||
bgImg.Image.color = GetCellBgColor(qNs);
|
||||
|
||||
// 象限底部边框
|
||||
var hLine = m_Assembler.GetImage("quad_hline");
|
||||
hLine.Rect.anchoredPosition = new Vector2(r.x, -r.y - r.height);
|
||||
hLine.Rect.sizeDelta = new Vector2(r.width, borderWidth);
|
||||
hLine.Image.color = borderColor;
|
||||
|
||||
// 象限右侧边框
|
||||
var vLine = m_Assembler.GetImage("quad_vline");
|
||||
vLine.Rect.anchoredPosition = new Vector2(r.x + r.width - borderWidth, -r.y);
|
||||
vLine.Rect.sizeDelta = new Vector2(borderWidth, r.height);
|
||||
vLine.Image.color = borderColor;
|
||||
|
||||
// 渲染象限内各单元格的文本/预制体/选择高亮
|
||||
if (q.Cells != null)
|
||||
{
|
||||
foreach (var (row, col) in q.Cells)
|
||||
{
|
||||
// 跳过被合并覆盖的单元格
|
||||
if (TableData.IsCellMerged(row, col)) continue;
|
||||
|
||||
float cellX = GetColLeftX(col);
|
||||
float cellY = -GetRowTopY(row);
|
||||
float cellW = m_ColWidths[col];
|
||||
float cellH = m_RowHeights[row];
|
||||
|
||||
// 合并源:扩展尺寸
|
||||
if (TableData.IsMergeSource(row, col, out int mergeRS, out int mergeCS))
|
||||
{
|
||||
for (int i = 1; i < mergeCS && col + i < m_ColWidths.Length; i++)
|
||||
cellW += m_ColWidths[col + i];
|
||||
for (int i = 1; i < mergeRS && row + i < m_RowHeights.Length; i++)
|
||||
cellH += m_RowHeights[row + i];
|
||||
}
|
||||
|
||||
XTableCellData data = TableData.GetCell(row, col);
|
||||
string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
|
||||
? data.StyleNamespace : ns;
|
||||
|
||||
// 选择高亮
|
||||
if (IsInSelectionRange(row, col))
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_multiselect");
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
|
||||
selImg.Image.color = (row == SelectionHandle.StartRow && col == SelectionHandle.StartCol)
|
||||
? focusColor : multiSelectColor;
|
||||
}
|
||||
else if (row == SelectionHandle.StartRow && col == SelectionHandle.StartCol)
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_select");
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
|
||||
selImg.Image.color = selectionColor;
|
||||
}
|
||||
|
||||
if (data == null) continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(data.Text))
|
||||
{
|
||||
var txt = m_Assembler.GetText("cell_text", cellNS);
|
||||
txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY);
|
||||
txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH);
|
||||
txt.Text.text = data.Text;
|
||||
txt.Text.color = GetCellFgColor(cellNS);
|
||||
txt.Text.fontSize = GetCellFontSize(cellNS);
|
||||
txt.Text.alignment = TextAlignmentOptions.Midline;
|
||||
txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font;
|
||||
}
|
||||
|
||||
if (data.Prefab != null)
|
||||
PositionPrefabInstance(row, col, data.Prefab, cellX, cellY, cellW, cellH);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// 运行时模式
|
||||
Color rtSelectionColor = RT_SELECTION_COLOR;
|
||||
Color rtMultiSelectColor = RT_MULTI_SELECT_COLOR;
|
||||
Color rtFocusColor = RT_FOCUS_COLOR;
|
||||
|
||||
foreach (var q in quadrants)
|
||||
{
|
||||
if (string.IsNullOrEmpty(q.Namespace)) continue;
|
||||
|
||||
Rect r = q.Region;
|
||||
string qNs = q.Namespace;
|
||||
|
||||
var bgImg = m_Assembler.GetImage("quad_bg", qNs);
|
||||
bgImg.Rect.anchoredPosition = new Vector2(r.x, -r.y);
|
||||
bgImg.Rect.sizeDelta = new Vector2(r.width, r.height);
|
||||
bgImg.Image.color = GetCellBgColor(qNs);
|
||||
|
||||
var hLine = m_Assembler.GetImage("quad_hline");
|
||||
hLine.Rect.anchoredPosition = new Vector2(r.x, -r.y - r.height);
|
||||
hLine.Rect.sizeDelta = new Vector2(r.width, RT_BORDER_WIDTH);
|
||||
hLine.Image.color = RT_BORDER_COLOR;
|
||||
|
||||
var vLine = m_Assembler.GetImage("quad_vline");
|
||||
vLine.Rect.anchoredPosition = new Vector2(r.x + r.width - RT_BORDER_WIDTH, -r.y);
|
||||
vLine.Rect.sizeDelta = new Vector2(RT_BORDER_WIDTH, r.height);
|
||||
vLine.Image.color = RT_BORDER_COLOR;
|
||||
|
||||
if (q.Cells != null)
|
||||
{
|
||||
foreach (var (row, col) in q.Cells)
|
||||
{
|
||||
// 跳过被合并覆盖的单元格
|
||||
if (TableData.IsCellMerged(row, col)) continue;
|
||||
|
||||
float cellX = GetColLeftX(col);
|
||||
float cellY = -GetRowTopY(row);
|
||||
float cellW = m_ColWidths[col];
|
||||
float cellH = m_RowHeights[row];
|
||||
|
||||
// 合并源:扩展尺寸
|
||||
if (TableData.IsMergeSource(row, col, out int mergeRS, out int mergeCS))
|
||||
{
|
||||
for (int i = 1; i < mergeCS && col + i < m_ColWidths.Length; i++)
|
||||
cellW += m_ColWidths[col + i];
|
||||
for (int i = 1; i < mergeRS && row + i < m_RowHeights.Length; i++)
|
||||
cellH += m_RowHeights[row + i];
|
||||
}
|
||||
|
||||
XTableCellData data = TableData.GetCell(row, col);
|
||||
string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
|
||||
? data.StyleNamespace : ns;
|
||||
|
||||
if (IsInSelectionRange(row, col))
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_multiselect");
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
|
||||
selImg.Image.color = (row == SelectionHandle.StartRow && col == SelectionHandle.StartCol)
|
||||
? rtFocusColor : rtMultiSelectColor;
|
||||
}
|
||||
else if (row == SelectionHandle.StartRow && col == SelectionHandle.StartCol)
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_select");
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
|
||||
selImg.Image.color = rtSelectionColor;
|
||||
}
|
||||
|
||||
if (data == null) continue;
|
||||
|
||||
if (!string.IsNullOrEmpty(data.Text))
|
||||
{
|
||||
var txt = m_Assembler.GetText("cell_text", cellNS);
|
||||
txt.Rect.anchoredPosition = new Vector2(cellX + 4, cellY);
|
||||
txt.Rect.sizeDelta = new Vector2(cellW - 8, cellH);
|
||||
txt.Text.text = data.Text;
|
||||
}
|
||||
|
||||
if (data.Prefab != null)
|
||||
PositionPrefabInstance(row, col, data.Prefab, cellX, cellY, cellW, cellH);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -141,7 +355,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
if (m_TableData == null)
|
||||
m_TableData = new XTableData();
|
||||
|
||||
m_Assembler = new CellAssembler(rectTransform, m_DefaultStyleNamespace);
|
||||
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
|
||||
RecalculateTotalSize();
|
||||
EnsureDefaultStyleNamespace();
|
||||
CreateDefaultSampleData();
|
||||
@@ -191,6 +405,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
// 销毁所有动态生成的对象
|
||||
if (m_Assembler != null)
|
||||
m_Assembler.DestroyAll();
|
||||
ClearPrefabInstances();
|
||||
if (m_BackgroundGo != null)
|
||||
{
|
||||
if (Application.isPlaying) Destroy(m_BackgroundGo);
|
||||
@@ -422,13 +637,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
newArr[i] = m_RowHeights[i + 1];
|
||||
m_RowHeights = newArr;
|
||||
|
||||
// 更新选中状态
|
||||
if (m_SelectedRow >= m_RowHeights.Length)
|
||||
m_SelectedRow = Mathf.Max(-1, m_RowHeights.Length - 1);
|
||||
if (m_SelectStartRow >= m_RowHeights.Length)
|
||||
m_SelectStartRow = Mathf.Max(-1, m_RowHeights.Length - 1);
|
||||
if (m_SelectEndRow >= m_RowHeights.Length)
|
||||
m_SelectEndRow = Mathf.Max(-1, m_RowHeights.Length - 1);
|
||||
// 裁剪选区到新尺寸
|
||||
ClampSelectionToBounds();
|
||||
|
||||
MarkDirty(TableDirtyType.LayoutChanged | TableDirtyType.DataChanged | TableDirtyType.SelectionChanged);
|
||||
#if UNITY_EDITOR
|
||||
@@ -474,13 +684,8 @@ namespace XericUI.XTable.Rendering.Component
|
||||
newArr[i] = m_ColWidths[i + 1];
|
||||
m_ColWidths = newArr;
|
||||
|
||||
// 更新选中状态
|
||||
if (m_SelectedCol >= m_ColWidths.Length)
|
||||
m_SelectedCol = Mathf.Max(-1, m_ColWidths.Length - 1);
|
||||
if (m_SelectStartCol >= m_ColWidths.Length)
|
||||
m_SelectStartCol = Mathf.Max(-1, m_ColWidths.Length - 1);
|
||||
if (m_SelectEndCol >= m_ColWidths.Length)
|
||||
m_SelectEndCol = Mathf.Max(-1, m_ColWidths.Length - 1);
|
||||
// 裁剪选区到新尺寸
|
||||
ClampSelectionToBounds();
|
||||
|
||||
MarkDirty(TableDirtyType.LayoutChanged | TableDirtyType.DataChanged | TableDirtyType.SelectionChanged);
|
||||
#if UNITY_EDITOR
|
||||
@@ -488,53 +693,38 @@ namespace XericUI.XTable.Rendering.Component
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>设置外部选择句柄(替换内部句柄)</summary>
|
||||
public void SetSelectionHandle(XTableSelectionHandle handle)
|
||||
{
|
||||
if (m_SelectionHandle != null)
|
||||
m_SelectionHandle.OnSelectionChanged -= OnHandleSelectionChanged;
|
||||
m_SelectionHandle = handle;
|
||||
if (m_SelectionHandle != null)
|
||||
m_SelectionHandle.OnSelectionChanged += OnHandleSelectionChanged;
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
}
|
||||
|
||||
/// <summary>单选一个单元格</summary>
|
||||
public void SelectCell(int row, int col)
|
||||
{
|
||||
ClearMultiSelection();
|
||||
m_SelectedRow = row;
|
||||
m_SelectedCol = col;
|
||||
SelectionHandle.Select(row, col);
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
}
|
||||
|
||||
/// <summary>多选一个范围</summary>
|
||||
public void SelectCell(int startRow, int startCol, int endRow, int endCol)
|
||||
{
|
||||
SelectionHandle.Select(startRow, startCol, endRow, endCol);
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
}
|
||||
|
||||
/// <summary>清除选择</summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
m_SelectedRow = -1;
|
||||
m_SelectedCol = -1;
|
||||
ClearMultiSelection();
|
||||
SelectionHandle.Clear();
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
}
|
||||
|
||||
/// <summary>开始多选(以当前格为锚点)</summary>
|
||||
public void StartMultiSelect(int row, int col)
|
||||
{
|
||||
m_SelectStartRow = row;
|
||||
m_SelectStartCol = col;
|
||||
m_SelectEndRow = row;
|
||||
m_SelectEndCol = col;
|
||||
m_SelectedRow = row;
|
||||
m_SelectedCol = col;
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
}
|
||||
|
||||
/// <summary>扩展多选范围到目标格</summary>
|
||||
public void ExtendMultiSelect(int row, int col)
|
||||
{
|
||||
m_SelectEndRow = row;
|
||||
m_SelectEndCol = col;
|
||||
m_SelectedRow = row;
|
||||
m_SelectedCol = col;
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
}
|
||||
|
||||
/// <summary>清除多选范围</summary>
|
||||
public void ClearMultiSelection()
|
||||
{
|
||||
m_SelectStartRow = -1;
|
||||
m_SelectStartCol = -1;
|
||||
m_SelectEndRow = -1;
|
||||
m_SelectEndCol = -1;
|
||||
}
|
||||
|
||||
/// <summary>通过文本选择一个单元格(如 "A0")</summary>
|
||||
public bool SelectCellByText(string text)
|
||||
{
|
||||
@@ -551,30 +741,36 @@ namespace XericUI.XTable.Rendering.Component
|
||||
out int sr, out int sc, out int er, out int ec))
|
||||
return false;
|
||||
|
||||
ClearSelection();
|
||||
m_SelectStartRow = sr;
|
||||
m_SelectStartCol = sc;
|
||||
m_SelectEndRow = er;
|
||||
m_SelectEndCol = ec;
|
||||
m_SelectedRow = sr;
|
||||
m_SelectedCol = sc;
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
SelectCell(sr, sc, er, ec);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>获取当前选区的文本描述(单格 "A0",多格 "A0:B1")</summary>
|
||||
public string GetSelectionText()
|
||||
{
|
||||
if (m_SelectedRow < 0 || m_SelectedCol < 0) return string.Empty;
|
||||
if (HasMultiSelection &&
|
||||
(m_SelectStartRow != m_SelectEndRow || m_SelectStartCol != m_SelectEndCol))
|
||||
if (!SelectionHandle.HasSelection) return string.Empty;
|
||||
if (SelectionHandle.HasMultiSelection)
|
||||
{
|
||||
return XTableCoordinateFormat.RangeToText(
|
||||
m_SelectStartRow, m_SelectStartCol,
|
||||
m_SelectEndRow, m_SelectEndCol);
|
||||
SelectionHandle.MinRow, SelectionHandle.MinCol,
|
||||
SelectionHandle.MaxRow, SelectionHandle.MaxCol);
|
||||
}
|
||||
|
||||
return XTableCoordinateFormat.CellToText(m_SelectedRow, m_SelectedCol);
|
||||
return XTableCoordinateFormat.CellToText(
|
||||
SelectionHandle.StartRow, SelectionHandle.StartCol);
|
||||
}
|
||||
|
||||
private void OnHandleSelectionChanged(XTableSelectionHandle handle)
|
||||
{
|
||||
MarkDirty(TableDirtyType.SelectionChanged);
|
||||
}
|
||||
|
||||
/// <summary>裁剪选区到当前行列尺寸范围内</summary>
|
||||
private void ClampSelectionToBounds()
|
||||
{
|
||||
int maxR = (m_RowHeights?.Length ?? 0) - 1;
|
||||
int maxC = (m_ColWidths?.Length ?? 0) - 1;
|
||||
SelectionHandle.ClampTo(maxR, maxC);
|
||||
}
|
||||
|
||||
public void RefreshView()
|
||||
@@ -583,7 +779,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
if (m_Assembler == null)
|
||||
{
|
||||
if (rectTransform != null)
|
||||
m_Assembler = new CellAssembler(rectTransform, m_DefaultStyleNamespace);
|
||||
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
|
||||
else return;
|
||||
}
|
||||
|
||||
@@ -607,14 +803,18 @@ namespace XericUI.XTable.Rendering.Component
|
||||
bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
|
||||
}
|
||||
|
||||
// 清理上一次渲染的预制体实例
|
||||
ClearPrefabInstances();
|
||||
|
||||
// 回收池
|
||||
m_Assembler.ReturnAll();
|
||||
|
||||
// 额外清理孤立的直接子对象(防止域重载或异常流程产生的残留)
|
||||
Transform poolRoot = m_Assembler.PoolRoot;
|
||||
for (int i = rectTransform.childCount - 1; i >= 0; i--)
|
||||
Transform contentT = ContentTransform;
|
||||
for (int i = contentT.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = rectTransform.GetChild(i);
|
||||
var child = contentT.GetChild(i);
|
||||
if ((poolRoot != null && child == poolRoot)) continue;
|
||||
if (m_BackgroundGo != null && child.gameObject == m_BackgroundGo) continue;
|
||||
|
||||
@@ -625,14 +825,21 @@ namespace XericUI.XTable.Rendering.Component
|
||||
DestroyImmediate(child.gameObject);
|
||||
}
|
||||
|
||||
// 计算可见范围
|
||||
int sr, er, sc, ec;
|
||||
CalcVisibleRange(out sr, out er, out sc, out ec);
|
||||
|
||||
if (er <= sr || ec <= sc) return;
|
||||
|
||||
// 渲染每个可见单元格
|
||||
RenderCells(sr, er, sc, ec);
|
||||
// 渲染可见单元格:优先使用四叉树查询
|
||||
if (m_Quadtree != null && m_Quadtree.IsBuilt)
|
||||
{
|
||||
var quadrants = new List<QuadrantResult>();
|
||||
m_Quadtree.QueryVisible(m_VisibleRect, quadrants);
|
||||
RenderQuadrants(quadrants);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 回退:传统行列遍历
|
||||
int sr, er, sc, ec;
|
||||
CalcVisibleRange(out sr, out er, out sc, out ec);
|
||||
if (er > sr && ec > sc)
|
||||
RenderCells(sr, er, sc, ec);
|
||||
}
|
||||
|
||||
m_DirtyFlag.Clear();
|
||||
OnTableRefreshed?.Invoke();
|
||||
@@ -667,13 +874,14 @@ namespace XericUI.XTable.Rendering.Component
|
||||
/// <summary>销毁 transform 下所有子对象(用于域重载后清理残留)</summary>
|
||||
private void ClearAllChildren()
|
||||
{
|
||||
if (rectTransform == null) return;
|
||||
Transform t = ContentTransform;
|
||||
if (t == null) return;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
// 编辑器模式下先重置 HideFlags 再销毁,防止 DontSave 对象拒绝销毁
|
||||
while (rectTransform.childCount > 0)
|
||||
while (t.childCount > 0)
|
||||
{
|
||||
var child = rectTransform.GetChild(0);
|
||||
var child = t.GetChild(0);
|
||||
child.gameObject.hideFlags = HideFlags.None;
|
||||
if (Application.isPlaying)
|
||||
Destroy(child.gameObject);
|
||||
@@ -681,9 +889,9 @@ namespace XericUI.XTable.Rendering.Component
|
||||
DestroyImmediate(child.gameObject);
|
||||
}
|
||||
#else
|
||||
for (int i = rectTransform.childCount - 1; i >= 0; i--)
|
||||
for (int i = t.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = rectTransform.GetChild(i);
|
||||
var child = t.GetChild(i);
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
#endif
|
||||
@@ -694,10 +902,11 @@ namespace XericUI.XTable.Rendering.Component
|
||||
{
|
||||
m_ChildrenVisible = visible;
|
||||
#if UNITY_EDITOR
|
||||
if (rectTransform == null) return;
|
||||
for (int i = rectTransform.childCount - 1; i >= 0; i--)
|
||||
Transform t = ContentTransform;
|
||||
if (t == null) return;
|
||||
for (int i = t.childCount - 1; i >= 0; i--)
|
||||
{
|
||||
var child = rectTransform.GetChild(i);
|
||||
var child = t.GetChild(i);
|
||||
child.gameObject.hideFlags = visible
|
||||
? HideFlags.DontSave
|
||||
: HideFlags.DontSave | HideFlags.HideInHierarchy;
|
||||
@@ -715,7 +924,7 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
m_TableData = new XTableData();
|
||||
|
||||
m_Assembler = new CellAssembler(rectTransform, m_DefaultStyleNamespace);
|
||||
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
|
||||
RecalculateTotalSize();
|
||||
CreateDefaultSampleData();
|
||||
CreateBackground();
|
||||
@@ -758,6 +967,48 @@ namespace XericUI.XTable.Rendering.Component
|
||||
SetChildrenVisible(m_ChildrenVisible);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有数据(单元格文本、合并信息),但保留行列排布和样式。
|
||||
/// 等价于"清空内容但不改变表格结构"。
|
||||
/// </summary>
|
||||
public void ClearData()
|
||||
{
|
||||
if (m_TableData != null)
|
||||
{
|
||||
m_TableData.ClearAllMerges();
|
||||
m_TableData.BlockMap.Clear();
|
||||
}
|
||||
ClearPrefabInstances();
|
||||
m_Quadtree?.Clear();
|
||||
ClearSelection();
|
||||
MarkDirty(TableDirtyType.DataChanged);
|
||||
RefreshView();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清空表格所有内容——数据、行列、样式,等同 <see cref="FullRebuild"/> 但不创建默认示例数据。
|
||||
/// </summary>
|
||||
public void ClearTable()
|
||||
{
|
||||
m_Assembler?.DestroyAll();
|
||||
ClearPrefabInstances();
|
||||
m_Quadtree?.Clear();
|
||||
ClearAllChildren();
|
||||
m_BackgroundGo = null;
|
||||
|
||||
m_TableData = new XTableData();
|
||||
m_RowHeights = null;
|
||||
m_ColWidths = null;
|
||||
ClearSelection();
|
||||
|
||||
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
|
||||
MarkDirty(TableDirtyType.All);
|
||||
RecalculateTotalSize();
|
||||
CreateBackground();
|
||||
RefreshView();
|
||||
SetChildrenVisible(m_ChildrenVisible);
|
||||
}
|
||||
|
||||
/// <summary>仅强制刷新样式,不重建数据结构</summary>
|
||||
public void ForceStyleRefresh()
|
||||
{
|
||||
@@ -848,10 +1099,10 @@ namespace XericUI.XTable.Rendering.Component
|
||||
var selImg = m_Assembler.GetImage("cell_multiselect");
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
|
||||
selImg.Image.color = (r == m_SelectedRow && c == m_SelectedCol)
|
||||
selImg.Image.color = (r == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
|
||||
? focusColor : multiSelectColor;
|
||||
}
|
||||
else if (r == m_SelectedRow && c == m_SelectedCol)
|
||||
else if (r == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_select");
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
@@ -884,6 +1135,10 @@ namespace XericUI.XTable.Rendering.Component
|
||||
txt.Text.alignment = TextAlignmentOptions.Midline;
|
||||
txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font;
|
||||
}
|
||||
|
||||
// 预制体渲染
|
||||
if (data.Prefab != null)
|
||||
PositionPrefabInstance(r, c, data.Prefab, cellX, cellY, cellW, cellH);
|
||||
}
|
||||
}
|
||||
return;
|
||||
@@ -925,10 +1180,10 @@ namespace XericUI.XTable.Rendering.Component
|
||||
var selImg = m_Assembler.GetImage("cell_multiselect");
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
selImg.Rect.sizeDelta = new Vector2(cellW, cellH);
|
||||
selImg.Image.color = (r == m_SelectedRow && c == m_SelectedCol)
|
||||
selImg.Image.color = (r == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
|
||||
? rtFocusColor : rtMultiSelectColor;
|
||||
}
|
||||
else if (r == m_SelectedRow && c == m_SelectedCol)
|
||||
else if (r == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
|
||||
{
|
||||
var selImg = m_Assembler.GetImage("cell_select");
|
||||
selImg.Rect.anchoredPosition = new Vector2(cellX, cellY);
|
||||
@@ -956,12 +1211,71 @@ namespace XericUI.XTable.Rendering.Component
|
||||
txt.Text.text = data.Text;
|
||||
// 运行时样式由 StyleBoundElement 被动更新,此处仅设置文本内容
|
||||
}
|
||||
|
||||
// 预制体渲染
|
||||
if (data.Prefab != null)
|
||||
PositionPrefabInstance(r, c, data.Prefab, cellX, cellY, cellW, cellH);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 预制体管理
|
||||
|
||||
/// <summary>获取或创建指定单元格的预制体实例</summary>
|
||||
private GameObject GetOrCreatePrefabInstance(int row, int col, GameObject prefab)
|
||||
{
|
||||
if (m_PrefabInstances == null)
|
||||
m_PrefabInstances = new Dictionary<(int, int), GameObject>();
|
||||
|
||||
var key = (row, col);
|
||||
if (m_PrefabInstances.TryGetValue(key, out GameObject existing) && existing != null)
|
||||
return existing;
|
||||
|
||||
GameObject instance = Instantiate(prefab, ContentTransform);
|
||||
instance.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
|
||||
instance.name = $"_prefab_{row}_{col}";
|
||||
m_PrefabInstances[key] = instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/// <summary>定位预制体实例到指定单元格区域(锚点左上角,枢轴左上角)</summary>
|
||||
private void PositionPrefabInstance(int row, int col, GameObject prefab, float cellX, float cellY, float cellW, float cellH)
|
||||
{
|
||||
GameObject instance = GetOrCreatePrefabInstance(row, col, prefab);
|
||||
if (instance == null) return;
|
||||
|
||||
RectTransform prt = instance.GetComponent<RectTransform>();
|
||||
if (prt == null) prt = instance.AddComponent<RectTransform>();
|
||||
|
||||
prt.anchorMin = new Vector2(0, 1);
|
||||
prt.anchorMax = new Vector2(0, 1);
|
||||
prt.pivot = new Vector2(0, 1);
|
||||
prt.anchoredPosition = new Vector2(cellX, cellY);
|
||||
prt.sizeDelta = new Vector2(cellW, cellH);
|
||||
}
|
||||
|
||||
/// <summary>销毁所有已缓存的预制体实例</summary>
|
||||
private void ClearPrefabInstances()
|
||||
{
|
||||
if (m_PrefabInstances == null) return;
|
||||
|
||||
foreach (var kv in m_PrefabInstances)
|
||||
{
|
||||
if (kv.Value != null)
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
Destroy(kv.Value);
|
||||
else
|
||||
DestroyImmediate(kv.Value);
|
||||
}
|
||||
}
|
||||
m_PrefabInstances.Clear();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 坐标计算
|
||||
|
||||
private float GetRowTopY(int row)
|
||||
@@ -1116,7 +1430,14 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
#region 其他
|
||||
|
||||
private void MarkDirty(TableDirtyType type) => m_DirtyFlag.Mark(type);
|
||||
private void MarkDirty(TableDirtyType type)
|
||||
{
|
||||
m_DirtyFlag.Mark(type);
|
||||
|
||||
// 数据或布局变更时重建四叉树
|
||||
if ((type & (TableDirtyType.DataChanged | TableDirtyType.LayoutChanged)) != 0)
|
||||
MaintainQuadtree();
|
||||
}
|
||||
|
||||
/// <summary>运行时样式变更回调:标记脏数据,下一帧重绘</summary>
|
||||
private void OnPoolStyleChanged() => MarkDirty(TableDirtyType.StyleChanged);
|
||||
@@ -1125,6 +1446,46 @@ namespace XericUI.XTable.Rendering.Component
|
||||
|
||||
#endregion
|
||||
|
||||
#region 四叉树
|
||||
|
||||
/// <summary>强制重建四叉树(外部调用入口,适合编辑器调试或手动刷新)</summary>
|
||||
public void ForceRebuildQuadtree()
|
||||
{
|
||||
if (m_Quadtree == null)
|
||||
m_Quadtree = new XTableQuadtree();
|
||||
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();
|
||||
|
||||
// 收集所有有数据的单元格(跳过被合并覆盖的子单元格)
|
||||
var cells = new List<XTableQuadtreeCell>();
|
||||
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;
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
|
||||
m_Quadtree.Build(m_TotalWidth, m_TotalHeight, m_RowHeights, m_ColWidths, cells);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPointerClickHandler
|
||||
|
||||
public void OnPointerClick(PointerEventData eventData)
|
||||
|
||||
Reference in New Issue
Block a user