diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs
new file mode 100644
index 0000000..e1366a3
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs
@@ -0,0 +1,566 @@
+using System;
+using System.Collections.Generic;
+using TMPro;
+using UnityEngine;
+using UnityEngine.EventSystems;
+using UnityEngine.UI;
+using XericUI.Core.Base;
+using XericUI.XTable.Core;
+using XericUI.XTable.Mapping;
+using XericUI.XTable.Rendering.Elements;
+using XericLibrary.Runtime.SuperStyleSheet;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace XericUI.XTable.Rendering.Component
+{
+ public partial class XericUIActionTable
+ {
+ /// 设置冻结行列数并重建视图
+ public void SetFreezeCount(int rows, int cols)
+ {
+ m_FreezeRowCount = Mathf.Max(0, rows);
+ m_FreezeColCount = Mathf.Max(0, cols);
+ CreateViewportRenderer();
+ MarkDirty(TableDirtyType.LayoutChanged);
+ RefreshView();
+ }
+
+ public void SetCellData(int row, int col, XTableCellData data)
+ {
+ TableData.SetCell(row, col, data);
+ MarkDirty(TableDirtyType.DataChanged);
+ }
+
+ /// 设置文本内容(ContentType 自动设为 Text)
+ public XTableCellData SetCellText(int row, int col, string text)
+ {
+ var data = TableData.GetOrCreateCell(row, col);
+ data.Text = text;
+ data.ContentType = CellContentType.Text;
+ MarkDirty(TableDirtyType.DataChanged);
+ return data;
+ }
+
+ /// 设置图片纹理(ContentType 自动设为 Image)
+ public XTableCellData SetCellImage(int row, int col, Texture2D image)
+ {
+ var data = TableData.GetOrCreateCell(row, col);
+ data.Image = image;
+ data.ContentType = CellContentType.Image;
+ MarkDirty(TableDirtyType.DataChanged);
+ return data;
+ }
+
+ /// 设置预制体(ContentType 自动设为 Prefab)
+ /// 注意是设置预制体而不是实例,设置后订阅单元格预制体生成事件 OnPrefabAcquire 处理获取事件
+ public XTableCellData SetCellPrefab(int row, int col, GameObject prefab)
+ {
+ var data = TableData.GetOrCreateCell(row, col);
+ data.Prefab = prefab;
+ data.ContentType = CellContentType.Prefab;
+ MarkDirty(TableDirtyType.DataChanged);
+ return data;
+ }
+
+ /// 设置单元格的样式命名空间,同时写入边框缓存(谁最后设置归谁)
+ public XTableCellData SetCellStyle(int row, int col, string styleNamespace, string stylePath)
+ {
+ var data = TableData.GetOrCreateCell(row, col);
+ if (data.CellStyle == null)
+ data.CellStyle = new XericLibrary.Runtime.SuperStyleSheet.StyleMember();
+ data.CellStyle.CurrentNamespace = styleNamespace ?? "";
+ data.CellStyle.StylePath = stylePath ?? "";
+ MarkDirty(TableDirtyType.StyleChanged);
+ return data;
+ }
+
+ /// 设置单元格的样式命名空间(兼容旧版,等同于 StyleNamespace 字段)
+ public void SetCellStyleNamespace(int row, int col, string styleNamespace)
+ {
+ // 合并源:遍历所有被合并单元格,全部写入该样式
+ if (TableData.IsMergeSource(row, col, out int rowSpan, out int colSpan))
+ {
+ for (int r = row; r < row + rowSpan; r++)
+ {
+ for (int c = col; c < col + colSpan; c++)
+ {
+ if (r < m_RowHeights.Length && c < m_ColWidths.Length)
+ ApplyCellStyleToCell(r, c, styleNamespace);
+ }
+ }
+ }
+ else
+ {
+ ApplyCellStyleToCell(row, col, styleNamespace);
+ }
+
+ MarkDirty(TableDirtyType.StyleChanged);
+ }
+
+ /// 在单个单元格上设置样式,同时写入其右侧和底部的边框缓存
+ private void ApplyCellStyleToCell(int row, int col, string ns)
+ {
+ var data = TableData.GetOrCreateCell(row, col);
+ data.StyleNamespace = ns;
+ TableData.SetBorderRightNs(row, col, ns);
+ TableData.SetBorderBottomNs(row, col, ns);
+ }
+
+ /// 清除文本内容
+ public void ClearCellText(int row, int col)
+ {
+ var data = TableData.GetCell(row, col);
+ if (data == null) return;
+ data.Text = null;
+ if (data.ContentType == CellContentType.Text)
+ data.ContentType = CellContentType.Text; // 保持类型不变,仅清空内容
+ MarkDirty(TableDirtyType.DataChanged);
+ }
+
+ /// 清除图片纹理
+ public void ClearCellImage(int row, int col)
+ {
+ var data = TableData.GetCell(row, col);
+ if (data == null) return;
+ data.Image = null;
+ MarkDirty(TableDirtyType.DataChanged);
+ }
+
+ /// 清除预制体及委托
+ public void ClearCellPrefab(int row, int col)
+ {
+ var data = TableData.GetCell(row, col);
+ if (data == null) return;
+ data.Prefab = null;
+ data.OnPrefabAcquire = null;
+ data.OnPrefabRelease = null;
+ MarkDirty(TableDirtyType.DataChanged);
+ }
+
+ /// 清除单元格样式覆盖
+ public void ClearCellStyle(int row, int col)
+ {
+ var data = TableData.GetCell(row, col);
+ if (data == null) return;
+ data.CellStyle = null;
+ MarkDirty(TableDirtyType.StyleChanged);
+ }
+
+ public XTableCellData GetCellData(int row, int col)
+ => TableData.GetOrCreateCell(row, col);
+
+ /// 获取单元格的样式命名空间(含回退逻辑)
+ public string GetCellStyleNamespace(int row, int col)
+ {
+ var data = TableData.GetCell(row, col);
+ return data != null ? data.GetEffectiveStyleNamespace(m_DefaultStyleNamespace) : m_DefaultStyleNamespace;
+ }
+
+ public void MergeCells(int startRow, int startCol, int rowSpan, int colSpan)
+ {
+ TableData.MergeCells(startRow, startCol, rowSpan, colSpan);
+ MarkDirty(TableDirtyType.DataChanged);
+ }
+
+ public void UnmergeCells(int startRow, int startCol)
+ {
+ TableData.UnmergeCells(startRow, startCol);
+ MarkDirty(TableDirtyType.DataChanged);
+ }
+
+ /// 判断指定单元格是否被合并覆盖(非源,会被重定向)
+ public bool IsCellMerged(int row, int col) => TableData.IsCellMerged(row, col);
+
+ /// 判断指定单元格是否为合并源(锚点),并返回合并跨度
+ public bool IsMergeSource(int row, int col, out int rowSpan, out int colSpan)
+ => TableData.IsMergeSource(row, col, out rowSpan, out colSpan);
+
+ /// 在末尾追加一行,默认行高来自 Config
+ public void AddRow(float height = -1f)
+ {
+ if (height <= 0) height = Config.DefaultRowHeight;
+#if UNITY_EDITOR
+ UnityEditor.Undo.RecordObject(this, "Add Table Row");
+#endif
+ int newLen = m_RowHeights.Length + 1;
+ var newArr = new float[newLen];
+ System.Array.Copy(m_RowHeights, newArr, m_RowHeights.Length);
+ newArr[newLen - 1] = height;
+ m_RowHeights = newArr;
+ MarkDirty(TableDirtyType.LayoutChanged);
+#if UNITY_EDITOR
+ UnityEditor.EditorUtility.SetDirty(this);
+#endif
+ }
+
+ /// 在末尾追加一列,默认列宽来自 Config
+ public void AddColumn(float width = -1f)
+ {
+ if (width <= 0) width = Config.DefaultColumnWidth;
+#if UNITY_EDITOR
+ UnityEditor.Undo.RecordObject(this, "Add Table Column");
+#endif
+ int newLen = m_ColWidths.Length + 1;
+ var newArr = new float[newLen];
+ System.Array.Copy(m_ColWidths, newArr, m_ColWidths.Length);
+ newArr[newLen - 1] = width;
+ m_ColWidths = newArr;
+ MarkDirty(TableDirtyType.LayoutChanged);
+#if UNITY_EDITOR
+ UnityEditor.EditorUtility.SetDirty(this);
+#endif
+ }
+
+ /// 设置指定行的高度
+ public void SetRowHeight(int row, float height)
+ {
+ if (row < 0 || row >= m_RowHeights.Length) return;
+ if (height <= 0) return;
+#if UNITY_EDITOR
+ UnityEditor.Undo.RecordObject(this, "Set Row Height");
+#endif
+ m_RowHeights[row] = height;
+ MarkDirty(TableDirtyType.LayoutChanged);
+#if UNITY_EDITOR
+ if (!Application.isPlaying) UnityEditor.EditorUtility.SetDirty(this);
+#endif
+ }
+
+ /// 设置指定列的宽度
+ public void SetColWidth(int col, float width)
+ {
+ if (col < 0 || col >= m_ColWidths.Length) return;
+ if (width <= 0) return;
+#if UNITY_EDITOR
+ UnityEditor.Undo.RecordObject(this, "Set Column Width");
+#endif
+ m_ColWidths[col] = width;
+ MarkDirty(TableDirtyType.LayoutChanged);
+#if UNITY_EDITOR
+ if (!Application.isPlaying) UnityEditor.EditorUtility.SetDirty(this);
+#endif
+ }
+
+ /// 删除指定行,单元格数据上移
+ public void RemoveRow(int row)
+ {
+ if (row < 0 || row >= m_RowHeights.Length) return;
+#if UNITY_EDITOR
+ UnityEditor.Undo.RecordObject(this, "Remove Table Row");
+#endif
+ int colCount = m_ColWidths.Length;
+
+ // 先解除涉及该行的合并
+ for (int c = 0; c < colCount; c++)
+ TableData.UnmergeCells(row, c);
+
+ // 将下方行数据上移
+ for (int r = row + 1; r < m_RowHeights.Length; r++)
+ for (int c = 0; c < colCount; c++)
+ {
+ var data = TableData.GetCell(r, c);
+ if (data != null)
+ TableData.SetCell(r - 1, c, data);
+ else
+ TableData.SetCell(r - 1, c, null); // 显式清空原位置
+ }
+
+ // 清空最后一行数据
+ int lastRow = m_RowHeights.Length - 1;
+ for (int c = 0; c < colCount; c++)
+ TableData.SetCell(lastRow, c, null);
+
+ // 缩小行高数组
+ int newLen = m_RowHeights.Length - 1;
+ var newArr = new float[newLen];
+ for (int i = 0; i < row; i++)
+ newArr[i] = m_RowHeights[i];
+ for (int i = row; i < newLen; i++)
+ newArr[i] = m_RowHeights[i + 1];
+ m_RowHeights = newArr;
+
+ // 裁剪选区到新尺寸
+ ClampSelectionToBounds();
+
+ MarkDirty(TableDirtyType.LayoutChanged | TableDirtyType.DataChanged | TableDirtyType.SelectionChanged);
+#if UNITY_EDITOR
+ UnityEditor.EditorUtility.SetDirty(this);
+#endif
+ }
+
+ /// 删除指定列,单元格数据左移
+ public void RemoveColumn(int col)
+ {
+ if (col < 0 || col >= m_ColWidths.Length) return;
+#if UNITY_EDITOR
+ UnityEditor.Undo.RecordObject(this, "Remove Table Column");
+#endif
+ int rowCount = m_RowHeights.Length;
+
+ // 先解除涉及该列的合并
+ for (int r = 0; r < rowCount; r++)
+ TableData.UnmergeCells(r, col);
+
+ // 将右侧列数据左移
+ for (int r = 0; r < rowCount; r++)
+ for (int c = col + 1; c < m_ColWidths.Length; c++)
+ {
+ var data = TableData.GetCell(r, c);
+ if (data != null)
+ TableData.SetCell(r, c - 1, data);
+ else
+ TableData.SetCell(r, c - 1, null);
+ }
+
+ // 清空最后一列数据
+ int lastCol = m_ColWidths.Length - 1;
+ for (int r = 0; r < rowCount; r++)
+ TableData.SetCell(r, lastCol, null);
+
+ // 缩小列宽数组
+ int newLen = m_ColWidths.Length - 1;
+ var newArr = new float[newLen];
+ for (int i = 0; i < col; i++)
+ newArr[i] = m_ColWidths[i];
+ for (int i = col; i < newLen; i++)
+ newArr[i] = m_ColWidths[i + 1];
+ m_ColWidths = newArr;
+
+ // 裁剪选区到新尺寸
+ ClampSelectionToBounds();
+
+ MarkDirty(TableDirtyType.LayoutChanged | TableDirtyType.DataChanged | TableDirtyType.SelectionChanged);
+#if UNITY_EDITOR
+ UnityEditor.EditorUtility.SetDirty(this);
+#endif
+ }
+
+ /// 设置外部选择句柄(替换内部句柄)
+ 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);
+ }
+
+ /// 单选一个单元格
+ public void SelectCell(int row, int col)
+ {
+ SelectionHandle.Select(row, col);
+ MarkDirty(TableDirtyType.SelectionChanged);
+ }
+
+ /// 多选一个范围
+ public void SelectCell(int startRow, int startCol, int endRow, int endCol)
+ {
+ SelectionHandle.Select(startRow, startCol, endRow, endCol);
+ MarkDirty(TableDirtyType.SelectionChanged);
+ }
+
+ /// 清除选择
+ public void ClearSelection()
+ {
+ SelectionHandle.Clear();
+ MarkDirty(TableDirtyType.SelectionChanged);
+ }
+
+ /// 通过文本选择一个单元格(如 "A0")
+ public bool SelectCellByText(string text)
+ {
+ if (!XTableCoordinateFormat.TryParseCell(text, out int row, out int col))
+ return false;
+ SelectCell(row, col);
+ return true;
+ }
+
+ /// 通过文本选择一个范围(如 "A0:B1", "A:A", "0:0")
+ public bool SelectRangeByText(string text)
+ {
+ if (!XTableCoordinateFormat.TryParseRange(text,
+ out int sr, out int sc, out int er, out int ec))
+ return false;
+
+ SelectCell(sr, sc, er, ec);
+ return true;
+ }
+
+ /// 获取当前选区的文本描述(单格 "A0",多格 "A0:B1")
+ public string GetSelectionText()
+ {
+ if (!SelectionHandle.HasSelection) return string.Empty;
+ if (SelectionHandle.HasMultiSelection)
+ {
+ return XTableCoordinateFormat.RangeToText(
+ SelectionHandle.MinRow, SelectionHandle.MinCol,
+ SelectionHandle.MaxRow, SelectionHandle.MaxCol);
+ }
+
+ return XTableCoordinateFormat.CellToText(
+ SelectionHandle.StartRow, SelectionHandle.StartCol);
+ }
+
+ private void OnHandleSelectionChanged(XTableSelectionHandle handle)
+ {
+ MarkDirty(TableDirtyType.SelectionChanged);
+ }
+
+ /// 裁剪选区到当前行列尺寸范围内
+ private void ClampSelectionToBounds()
+ {
+ int maxR = (m_RowHeights?.Length ?? 0) - 1;
+ int maxC = (m_ColWidths?.Length ?? 0) - 1;
+ SelectionHandle.ClampTo(maxR, maxC);
+ }
+
+ /// 完全重建表格——销毁所有动态生成的对象并重新初始化
+ [ContextMenu("Xeric UI/完全重建表格")]
+ public void FullRebuild()
+ {
+ m_Assembler?.DestroyAll();
+ m_PrefabPool?.DestroyAll();
+ ClearAllChildren();
+ m_BackgroundGo = null;
+ m_BorderRenderer = null;
+
+ m_TableData = new XTableData();
+
+ m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
+ m_PrefabPool = new PrefabPrototypePool(ContentTransform);
+ m_ActivePrefabMap = new Dictionary<(int, int), GameObject>();
+ RecalculateTotalSize();
+ CreateDefaultSampleData();
+ CreateBackground();
+ CreateViewportRenderer();
+ MarkDirty(TableDirtyType.All);
+ RefreshView();
+ SetChildrenVisible(m_ChildrenVisible);
+ }
+
+ /// 重新生成表格但保留已有文本数据——先保存,重建后再回填
+ public void FullRebuildPreservingData()
+ {
+ // 保存当前单元格文本
+ var savedTexts = new Dictionary<(int, int), string>();
+ if (m_TableData != null)
+ {
+ int rows = m_RowHeights?.Length ?? 0;
+ int cols = m_ColWidths?.Length ?? 0;
+ for (int r = 0; r < rows; r++)
+ {
+ for (int c = 0; c < cols; c++)
+ {
+ var cell = m_TableData.GetCell(r, c);
+ if (cell != null && !string.IsNullOrEmpty(cell.Text))
+ savedTexts[(r, c)] = cell.Text;
+ }
+ }
+ }
+
+ FullRebuild();
+
+ // 恢复文本数据
+ foreach (var kv in savedTexts)
+ {
+ int r = kv.Key.Item1, c = kv.Key.Item2;
+ if (r < (m_RowHeights?.Length ?? 0) && c < (m_ColWidths?.Length ?? 0))
+ m_TableData.GetOrCreateCell(r, c).Text = kv.Value;
+ }
+
+ RefreshView();
+ SetChildrenVisible(m_ChildrenVisible);
+ }
+
+ ///
+ /// 清空所有数据(单元格文本、合并信息),但保留行列排布和样式。
+ /// 等价于"清空内容但不改变表格结构"。
+ ///
+ public void ClearData()
+ {
+ if (m_TableData != null)
+ {
+ m_TableData.ClearAllMerges();
+ m_TableData.BlockMap.Clear();
+ }
+ m_ActivePrefabMap?.Clear();
+ m_PrefabPool?.ReturnAll();
+ m_Quadtree?.Clear();
+ ClearSelection();
+ MarkDirty(TableDirtyType.DataChanged);
+ RefreshView();
+ }
+
+ ///
+ /// 清空表格所有内容——数据、行列、样式,等同 但不创建默认示例数据。
+ ///
+ public void ClearTable()
+ {
+ m_Assembler?.DestroyAll();
+ m_PrefabPool?.DestroyAll();
+ m_ActivePrefabMap?.Clear();
+ m_Quadtree?.Clear();
+ ClearAllChildren();
+ m_BackgroundGo = null;
+ m_BorderRenderer = null;
+
+ m_TableData = new XTableData();
+ m_RowHeights = null;
+ m_ColWidths = null;
+ ClearSelection();
+
+ m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
+ m_PrefabPool = new PrefabPrototypePool(ContentTransform);
+ m_ActivePrefabMap = new Dictionary<(int, int), GameObject>();
+ MarkDirty(TableDirtyType.All);
+ RecalculateTotalSize();
+ CreateBackground();
+ CreateViewportRenderer();
+ RefreshView();
+ SetChildrenVisible(m_ChildrenVisible);
+ }
+
+ /// 仅强制刷新样式,不重建数据结构
+ public void ForceStyleRefresh()
+ {
+ StyleManager.ForceRefresh();
+ MarkDirty(TableDirtyType.StyleChanged);
+ RefreshView();
+ }
+
+ /// 构建剪贴板数据对象(尺寸 + 单元格文本 + 合并信息)
+ public XTableClipboardData BuildClipboardData()
+ {
+ return XTableClipboardData.FromTable(this);
+ }
+
+ /// 将剪贴板数据应用到表格——覆盖尺寸、命名空间、单元格文本、合并信息并刷新
+ public void ApplyClipboardData(XTableClipboardData data)
+ {
+ if (data == null) return;
+
+ // 确保数据层存在
+ if (m_TableData == null)
+ m_TableData = new XTableData();
+
+ // 应用尺寸与默认命名空间
+ if (data.RowHeights != null && data.RowHeights.Length > 0)
+ m_RowHeights = data.RowHeights;
+ if (data.ColWidths != null && data.ColWidths.Length > 0)
+ m_ColWidths = data.ColWidths;
+ if (!string.IsNullOrEmpty(data.DefaultStyleNamespace))
+ m_DefaultStyleNamespace = data.DefaultStyleNamespace;
+
+ // 写回单元格文本与合并信息
+ data.PopulateToTable(this);
+
+ MarkDirty(TableDirtyType.DataChanged);
+ RecalculateTotalSize();
+ RefreshView();
+ SetChildrenVisible(m_ChildrenVisible);
+ }
+ }
+}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs.meta b/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs.meta
new file mode 100644
index 0000000..702bfa2
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Api.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8c49b16a58833a040ab95beac397acb2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs
new file mode 100644
index 0000000..b312ed2
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs
@@ -0,0 +1,203 @@
+using System;
+using System.Collections.Generic;
+using TMPro;
+using UnityEngine;
+using UnityEngine.EventSystems;
+using UnityEngine.UI;
+using XericUI.Core.Base;
+using XericUI.XTable.Core;
+using XericUI.XTable.Mapping;
+using XericUI.XTable.Rendering.Elements;
+using XericLibrary.Runtime.SuperStyleSheet;
+using XericLibrary.Runtime.UIGraph;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace XericUI.XTable.Rendering.Component
+{
+ public partial class XericUIActionTable
+ {
+ ///
+ /// 获取或创建边框渲染器 GameObject 和 组件。
+ ///
+ private UILineRendererV2 GetOrCreateBorderRenderer()
+ {
+ if (m_BorderRenderer != null)
+ return m_BorderRenderer;
+
+ Transform contentT = ContentTransform;
+ // 查找已有对象
+ var existing = contentT.Find("_TableBorders");
+ if (existing != null)
+ {
+ m_BorderRenderer = existing.GetComponent();
+ if (m_BorderRenderer != null) return m_BorderRenderer;
+ }
+
+ var go = new GameObject("_TableBorders",
+ typeof(RectTransform), typeof(CanvasRenderer), typeof(UILineRendererV2));
+ go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
+ go.transform.SetParent(contentT, false);
+
+ RectTransform rt = go.GetComponent();
+ rt.anchorMin = new Vector2(0, 1);
+ rt.anchorMax = new Vector2(0, 1);
+ rt.pivot = new Vector2(0, 1);
+ float pad = m_ContentOffset * 2f;
+ rt.anchoredPosition = new Vector2(m_ContentOffset, -m_ContentOffset);
+ rt.sizeDelta = new Vector2(m_TotalWidth + pad, m_TotalHeight + pad);
+
+ m_BorderRenderer = go.GetComponent();
+ return m_BorderRenderer;
+ }
+
+ /// 销毁边框渲染器
+ private void DestroyBorderRenderer()
+ {
+ if (m_BorderRenderer != null)
+ {
+ if (Application.isPlaying)
+ Destroy(m_BorderRenderer.gameObject);
+ else
+ DestroyImmediate(m_BorderRenderer.gameObject);
+ m_BorderRenderer = null;
+ }
+ }
+
+ ///
+ /// 重建表格的全部边框线段。
+ /// 第一阶段:从 OuterBorderNs 绘制 -1 行(顶部外框)和 -1 列(左侧外框)。
+ /// 第二阶段:遍历非合并覆盖单元格,从边框缓存读取样式命名空间,按 per-cell 样式绘制右侧和底部边框。
+ /// 合并源在最外沿绘制,内部边框自动跳过。
+ ///
+ private void RebuildBorders()
+ {
+ if (m_RowHeights == null || m_ColWidths == null) return;
+ if (m_RowHeights.Length == 0 || m_ColWidths.Length == 0) return;
+
+ var renderer = GetOrCreateBorderRenderer();
+ renderer.ClearAll();
+
+ var borderRt = renderer.GetComponent();
+ borderRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
+
+#if UNITY_EDITOR
+ Color borderColor;
+ float borderWidth;
+ if (!Application.isPlaying)
+ {
+ borderColor = GetCellBorderColor(m_DefaultStyleNamespace);
+ borderWidth = GetCellBorderWidth(m_DefaultStyleNamespace);
+ }
+ else
+ {
+ borderColor = Config.RuntimeBorderColor;
+ borderWidth = Config.RuntimeBorderWidth;
+ }
+#else
+ Color borderColor = Config.RuntimeBorderColor;
+ float borderWidth = Config.RuntimeBorderWidth;
+#endif
+
+ int rows = m_RowHeights.Length;
+ int cols = m_ColWidths.Length;
+
+ // ════════════════════════════════════════════
+ // 阶段 1:-1 行(顶部外框)和 -1 列(左侧外框)
+ // ════════════════════════════════════════════
+
+ // 顶部外框:从 col=0 到 col=cols-1,每列一段
+ float topY = 0;
+ for (int c = 0; c < cols; c++)
+ {
+ string ns = m_TableData?.GetOuterBorderNs(-1, c);
+ if (string.IsNullOrEmpty(ns)) ns = m_DefaultStyleNamespace;
+ Color color = GetCellBorderColor(ns);
+ float width = GetCellBorderWidth(ns);
+ float leftX = GetColLeftX(c);
+ renderer.DrawLine(
+ new Vector2(leftX, topY),
+ new Vector2(leftX + m_ColWidths[c], topY),
+ color, width, UVMode.ByDistance);
+ }
+
+ // 左侧外框:从 row=0 到 row=rows-1,每行一段
+ float xEdge = 0;
+ for (int r = 0; r < rows; r++)
+ {
+ string ns = m_TableData?.GetOuterBorderNs(r, -1);
+ if (string.IsNullOrEmpty(ns)) ns = m_DefaultStyleNamespace;
+ Color color = GetCellBorderColor(ns);
+ float width = GetCellBorderWidth(ns);
+ float rowTopY = -GetRowTopY(r);
+ renderer.DrawLine(
+ new Vector2(xEdge, rowTopY),
+ new Vector2(xEdge, rowTopY - m_RowHeights[r]),
+ color, width, UVMode.ByDistance);
+ }
+
+ // ════════════════════════════════════════════
+ // 阶段 2:每个单元格的右侧和底部边框
+ // ════════════════════════════════════════════
+
+ for (int r = 0; r < rows; r++)
+ {
+ for (int c = 0; c < cols; c++)
+ {
+ // 跳过被合并覆盖的单元格
+ 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);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs.meta b/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs.meta
new file mode 100644
index 0000000..baf8292
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Border.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8357070a070775e42954b1783029541e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs
new file mode 100644
index 0000000..26a0cb8
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs
@@ -0,0 +1,194 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using XericUI.Core.Base;
+using XericUI.XTable.Core;
+
+namespace XericUI.XTable.Rendering.Component
+{
+ ///
+ /// 表格数据与坐标计算——可见性判定、坐标公式、尺寸重算、四叉树维护。
+ ///
+ public partial class XericUIActionTable
+ {
+ #region 可见性判定
+
+ /// 判断指定单元格是否与当前可见矩形有重叠(用于裁剪剔除)
+ private bool IsCellVisible(int row, int col)
+ {
+ // 计入 m_ContentOffset 以匹配单元格在 Content 中的实际渲染位置
+ float cellLeft = GetColLeftX(col) + m_ContentOffset;
+ float cellRight = cellLeft + m_ColWidths[col];
+ float cellTop = GetRowTopY(row) + m_ContentOffset;
+ float cellBottom = cellTop + m_RowHeights[row];
+
+ return cellRight > m_VisibleRect.x && cellLeft < m_VisibleRect.xMax &&
+ cellBottom > m_VisibleRect.y && cellTop < m_VisibleRect.yMax;
+ }
+
+ #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 GetColLeftX(int col)
+ {
+ float x = 0;
+ for (int i = 0; i < col && i < m_ColWidths.Length; i++)
+ x += m_ColWidths[i];
+ return x;
+ }
+
+ #endregion
+
+ #region Sprite 缓存
+
+ /// 将 Texture2D 转为 Sprite(带缓存),用于 Image 类型单元格渲染
+ private Sprite GetOrCreateSprite(Texture2D texture)
+ {
+ if (texture == null) return null;
+
+ if (m_SpriteCache == null)
+ m_SpriteCache = new Dictionary();
+
+ if (!m_SpriteCache.TryGetValue(texture, out Sprite sprite))
+ {
+ sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
+ m_SpriteCache[texture] = sprite;
+ }
+ return sprite;
+ }
+
+ #endregion
+
+ #region 尺寸计算与可见矩形
+
+ private void RecalculateTotalSize()
+ {
+ m_TotalWidth = 0;
+ if (m_ColWidths != null)
+ foreach (float w in m_ColWidths)
+ m_TotalWidth += w;
+
+ m_TotalHeight = 0;
+ if (m_RowHeights != null)
+ foreach (float h in m_RowHeights)
+ m_TotalHeight += h;
+
+ m_ContentOffset = GetCellBorderWidth(m_DefaultStyleNamespace) * 0.5f;
+ float pad = m_ContentOffset * 2f;
+
+ if (m_ScrollRect != null && m_ScrollRect.content != null)
+ m_ScrollRect.content.sizeDelta = new Vector2(
+ m_TotalWidth + pad, m_TotalHeight + pad);
+ }
+
+ private void UpdateVisibleRect()
+ {
+ m_VisibleRect = new Rect(0, 0, m_TotalWidth, m_TotalHeight);
+
+ if (m_ScrollRect == null || m_ScrollRect.viewport == null || m_ScrollRect.content == null)
+ return;
+
+ RectTransform vt = m_ScrollRect.viewport;
+ RectTransform ct = m_ScrollRect.content as RectTransform;
+ if (vt == null || ct == null) return;
+
+ // Content 的 anchor/pivot 在 Awake 中强制设为 (0,1),anchoredPosition
+ // 直接表示 Content 左上角相对于 Viewport 左上角的偏移。
+ Vector2 ap = ct.anchoredPosition;
+ float visibleLeft = -ap.x;
+ float visibleTop = -ap.y;
+
+ m_VisibleRect = new Rect(visibleLeft, visibleTop, vt.rect.width, vt.rect.height);
+ }
+
+ private void CalcVisibleRange(out int sr, out int er, out int sc, out int ec)
+ {
+ sr = 0;
+ er = 0;
+ sc = 0;
+ ec = 0;
+
+ float accY = 0;
+ for (int r = 0; r < m_RowHeights.Length; r++)
+ {
+ float rowBottom = accY + m_RowHeights[r];
+ if (rowBottom > m_VisibleRect.y && accY < m_VisibleRect.yMax)
+ {
+ if (er == 0 && sr == 0) sr = r;
+ er = r + 1;
+ }
+
+ accY = rowBottom;
+ if (accY > m_VisibleRect.yMax) break;
+ }
+
+ float accX = 0;
+ for (int c = 0; c < m_ColWidths.Length; c++)
+ {
+ float colRight = accX + m_ColWidths[c];
+ if (colRight > m_VisibleRect.x && accX < m_VisibleRect.xMax)
+ {
+ if (ec == 0 && sc == 0) sc = c;
+ ec = c + 1;
+ }
+
+ accX = colRight;
+ if (accX > m_VisibleRect.xMax) break;
+ }
+
+ if (sr < 0) sr = 0;
+ if (sc < 0) sc = 0;
+ }
+
+ #endregion
+
+ #region 四叉树
+
+ /// 强制重建四叉树(外部调用入口,适合编辑器调试或手动刷新)
+ public void ForceRebuildQuadtree()
+ {
+ if (m_Quadtree == null)
+ m_Quadtree = new XTableQuadtree();
+ MaintainQuadtree();
+ }
+
+ /// 从当前 XTableData 收集单元格信息并重建四叉树
+ 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();
+ 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, Config.QuadtreeMaxCellsPerNode);
+ }
+
+ #endregion
+ }
+}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs.meta b/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs.meta
new file mode 100644
index 0000000..5b1d71a
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Data.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: af4d46cbfa5975f4da50bee5277e679e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Editor.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Editor.cs
new file mode 100644
index 0000000..98b94a7
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Editor.cs
@@ -0,0 +1,97 @@
+using UnityEngine;
+using XericUI.Core.Base;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace XericUI.XTable.Rendering.Component
+{
+ public partial class XericUIActionTable
+ {
+ #region 编辑器模式支持
+
+ [System.NonSerialized] private bool m_EditorUpdateSubscribed;
+
+ private void SubscribeEditorUpdate()
+ {
+ if (m_EditorUpdateSubscribed) return;
+#if UNITY_EDITOR
+ UnityEditor.EditorApplication.update += EditorUpdate;
+ m_EditorUpdateSubscribed = true;
+#endif
+ }
+
+ private void UnsubscribeEditorUpdate()
+ {
+ if (!m_EditorUpdateSubscribed) return;
+#if UNITY_EDITOR
+ UnityEditor.EditorApplication.update -= EditorUpdate;
+ m_EditorUpdateSubscribed = false;
+#endif
+ }
+
+#if UNITY_EDITOR
+ private void EditorUpdate()
+ {
+ if (this == null || !isActiveAndEnabled)
+ {
+ UnsubscribeEditorUpdate();
+ return;
+ }
+
+ if (!m_DirtyFlag.IsDirty) return;
+
+ var flags = m_DirtyFlag.Flags;
+ if ((flags & ~(TableDirtyType.ScrollChanged | TableDirtyType.SelectionChanged)) == 0)
+ RefreshViewLight();
+ else
+ RefreshView();
+ }
+#endif
+
+ #endregion
+
+ /// 销毁 transform 下所有子对象(用于域重载后清理残留)
+ private void ClearAllChildren()
+ {
+ Transform t = ContentTransform;
+ if (t == null) return;
+
+#if UNITY_EDITOR
+ // 编辑器模式下先重置 HideFlags 再销毁,防止 DontSave 对象拒绝销毁
+ while (t.childCount > 0)
+ {
+ var child = t.GetChild(0);
+ child.gameObject.hideFlags = HideFlags.None;
+ if (Application.isPlaying)
+ Destroy(child.gameObject);
+ else
+ DestroyImmediate(child.gameObject);
+ }
+#else
+ for (int i = t.childCount - 1; i >= 0; i--)
+ {
+ var child = t.GetChild(i);
+ Destroy(child.gameObject);
+ }
+#endif
+ }
+
+ /// 设置所有动态子对象的可见性(仅编辑器)
+ public void SetChildrenVisible(bool visible)
+ {
+ m_ChildrenVisible = visible;
+#if UNITY_EDITOR
+ Transform t = ContentTransform;
+ if (t == null) return;
+ for (int i = t.childCount - 1; i >= 0; i--)
+ {
+ var child = t.GetChild(i);
+ child.gameObject.hideFlags = visible
+ ? HideFlags.DontSave
+ : HideFlags.DontSave | HideFlags.HideInHierarchy;
+ }
+#endif
+ }
+ }
+}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Editor.cs.meta b/Runtime/XTable/Rendering/Component/XericUIActionTable.Editor.cs.meta
new file mode 100644
index 0000000..c30cbc7
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Editor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b8508d58614894145a342cc75c9bc1d1
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs
new file mode 100644
index 0000000..dbfe628
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs
@@ -0,0 +1,117 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+using XericUI.XTable.Core;
+using XericUI.XTable.Rendering.Elements;
+using XericLibrary.Runtime.SuperStyleSheet;
+
+namespace XericUI.XTable.Rendering.Component
+{
+ public partial class XericUIActionTable
+ {
+ #region Viewport 冻结渲染
+
+ /// 创建或重新创建 Viewport 层冻结渲染器
+ private void CreateViewportRenderer()
+ {
+ if (m_ScrollRect == null || m_ScrollRect.viewport == null) return;
+ if (m_FreezeRowCount <= 0 && m_FreezeColCount <= 0) return;
+
+ // 清理旧渲染器
+ m_ViewportRenderer?.DestroyAll();
+
+ Transform viewportT = m_ScrollRect.viewport;
+ m_ViewportRenderer = new TableRenderer(viewportT, m_DefaultStyleNamespace, OnPoolStyleChanged);
+
+ // 配置委托
+ m_ViewportRenderer.GetStyleColor = (ns, path, fb) => GetStyleColor(ns, path, fb);
+ m_ViewportRenderer.GetStyleFloat = (ns, path, fb) => GetStyleFloat(ns, path, fb);
+ m_ViewportRenderer.GetStyleInt = (ns, path, fb) => GetStyleInt(ns, path, fb);
+ m_ViewportRenderer.GetStyleFontAsset = (ns, path) => GetStyleFontAsset(ns, path);
+ m_ViewportRenderer.GetRowHeights = () => m_RowHeights;
+ m_ViewportRenderer.GetColWidths = () => m_ColWidths;
+ m_ViewportRenderer.GetTableData = () => m_TableData;
+ m_ViewportRenderer.GetDefaultStyleNamespace = () => m_DefaultStyleNamespace;
+ m_ViewportRenderer.IsInSelectionRange = (r, c) => IsInSelectionRange(r, c);
+ m_ViewportRenderer.GetSelectedRow = () => SelectionHandle.StartRow;
+ m_ViewportRenderer.GetSelectedCol = () => SelectionHandle.StartCol;
+
+ // 仅渲染冻结范围
+ m_ViewportRenderer.StartRow = 0;
+ m_ViewportRenderer.EndRow = m_FreezeRowCount;
+ m_ViewportRenderer.StartCol = 0;
+ m_ViewportRenderer.EndCol = m_FreezeColCount;
+
+ // 共享预制体对象池
+ m_ViewportRenderer.PrefabPool = m_PrefabPool;
+ m_ViewportRenderer.ActivePrefabMap = m_ActivePrefabMap;
+ m_ViewportRenderer.CellPadding = Config.CellHorizontalPadding;
+ m_ViewportRenderer.Config = Config;
+ }
+
+ /// 渲染 Viewport 层冻结单元格,随滚动偏移调整位置
+ private void RenderFrozenCells()
+ {
+ if (m_FreezeRowCount <= 0 && m_FreezeColCount <= 0)
+ {
+ // 无冻结需求时清理渲染器
+ if (m_ViewportRenderer != null)
+ {
+ m_ViewportRenderer.DestroyAll();
+ m_ViewportRenderer = null;
+ }
+ return;
+ }
+
+ // 检测冻结范围变更,重建渲染器
+ if (m_ViewportRenderer == null ||
+ m_ViewportRenderer.EndRow != m_FreezeRowCount ||
+ m_ViewportRenderer.EndCol != m_FreezeColCount)
+ CreateViewportRenderer();
+
+ // 计算冻结区域尺寸
+ float freezeW = 0, freezeH = 0;
+ for (int c = 0; c < m_FreezeColCount && c < m_ColWidths.Length; c++)
+ freezeW += m_ColWidths[c];
+ for (int r = 0; r < m_FreezeRowCount && r < m_RowHeights.Length; r++)
+ freezeH += m_RowHeights[r];
+
+ // 创建/更新背景
+ m_ViewportRenderer.CreateBackground(freezeW, freezeH, m_DefaultStyleNamespace);
+
+ // 获取滚动偏移
+ Vector2 scrollOffset = Vector2.zero;
+ if (m_ScrollRect != null && m_ScrollRect.content != null)
+ {
+ scrollOffset.x = Mathf.Max(0, -m_ScrollRect.content.anchoredPosition.x);
+ scrollOffset.y = Mathf.Max(0, -m_ScrollRect.content.anchoredPosition.y);
+ }
+
+ // 调整背景位置(随滚动偏移移动,保持在视口可见范围内)
+ if (m_ViewportRenderer.BackgroundGo != null)
+ {
+ var bgRt = m_ViewportRenderer.BackgroundGo.GetComponent();
+ bgRt.anchoredPosition = new Vector2(scrollOffset.x, -scrollOffset.y);
+ }
+
+ // 回收并重新渲染冻结单元格
+ m_ViewportRenderer.ReturnAll();
+ m_ViewportRenderer.RenderCells(
+ 0, m_FreezeRowCount,
+ 0, m_FreezeColCount,
+ !Application.isPlaying);
+
+ // 冻结单元格需要跟随滚动偏移(相对于 Viewport 定位)
+ if (m_ViewportRenderer.Assembler != null)
+ {
+ // 调整所有活跃的池对象的 anchoredPosition 以抵消滚动
+ // 这通过重新定位背景实现,单元格相对于背景 (0,0) 定位,所以随背景一起移动
+ }
+
+ // 背景始终可见
+ m_ViewportRenderer.BackgroundGo?.SetActive(true);
+ }
+
+ #endregion
+ }
+}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs.meta b/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs.meta
new file mode 100644
index 0000000..e0a859c
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.Freeze.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8dc33902374469e4aac60318ee862346
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs
new file mode 100644
index 0000000..de20af0
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs
@@ -0,0 +1,523 @@
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+using XericUI.XTable.Core;
+using XericUI.XTable.Rendering.Elements;
+#if UNITY_EDITOR
+using UnityEditor;
+#endif
+
+namespace XericUI.XTable.Rendering.Component
+{
+ public partial class XericUIActionTable
+ {
+ #region 增量渲染追踪
+
+ [NonSerialized] private HashSet<(int, int)> m_LastRenderedCells = new();
+ [NonSerialized] private Dictionary<(int, int), CellTrackedObjs> m_CellTracked = new();
+
+ /// 可复用列表,避免每帧分配 GC
+ [NonSerialized] private List<(int, int)> m_ReusableCellList = new(capacity: 128);
+ [NonSerialized] private List m_ReusableQuadrants = new(capacity: 32);
+
+ /// 控制滚动时是否使用四叉树(大表格推荐)
+ private bool UseQuadtreeForScroll => m_Quadtree != null && m_Quadtree.IsBuilt
+ && m_RowHeights.Length * m_ColWidths.Length > 2000;
+
+ private struct CellTrackedObjs
+ {
+ public CellAssembler.PooledText CellText;
+ public CellAssembler.PooledImage CellImage;
+ public CellAssembler.PooledImage SelectBg;
+ public CellAssembler.PooledImage HLine;
+ public CellAssembler.PooledImage VLine;
+ }
+
+ #endregion
+
+ #region 追踪对象生命周期
+
+ private void ReleaseTrackedCell(int row, int col)
+ {
+ var key = (row, col);
+ if (!m_CellTracked.TryGetValue(key, out var objs)) return;
+ 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);
+ }
+
+ private void ReleaseAllTrackedCells()
+ {
+ foreach (var kv in m_CellTracked)
+ {
+ var objs = kv.Value;
+ 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();
+ }
+
+ #endregion
+
+ #region 可见单元格计算
+
+ /// 计算当前可见矩形内所有未被合并覆盖的单元格
+ private void ComputeVisibleCellsInto(HashSet<(int, int)> result)
+ {
+ result.Clear();
+
+ if (m_Quadtree != null && m_Quadtree.IsBuilt)
+ {
+ m_ReusableQuadrants.Clear();
+ m_Quadtree.QueryVisible(m_VisibleRect, m_ReusableQuadrants);
+ foreach (var q in m_ReusableQuadrants)
+ {
+ if (string.IsNullOrEmpty(q.Namespace) || q.Cells == null) continue;
+ foreach (var cell in q.Cells)
+ {
+ if (!TableData.IsCellMerged(cell.Item1, cell.Item2)
+ && IsCellVisible(cell.Item1, cell.Item2))
+ result.Add(cell);
+ }
+ }
+ }
+ else
+ {
+ CalcVisibleRange(out int sr, out int er, out int sc, out int ec);
+ for (int r = sr; r < er; r++)
+ for (int c = sc; c < ec; c++)
+ {
+ if (!TableData.IsCellMerged(r, c) && IsCellVisible(r, c))
+ result.Add((r, c));
+ }
+ }
+ }
+
+ /// [Scroll] 仅用简单范围遍历计算可见单元格(避免四叉树遍历及 QuadrantResult 分配)
+ private void ComputeVisibleCellsScrollInto(HashSet<(int, int)> result)
+ {
+ result.Clear();
+ CalcVisibleRange(out int sr, out int er, out int sc, out int ec);
+ for (int r = sr; r < er; r++)
+ for (int c = sc; c < ec; c++)
+ {
+ if (!TableData.IsCellMerged(r, c) && IsCellVisible(r, c))
+ result.Add((r, c));
+ }
+ }
+
+ #endregion
+
+ #region 单元格坐标
+
+ private float GetCellContentX(int col)
+ {
+ return GetColLeftX(col) + m_ContentOffset;
+ }
+
+ private float GetCellContentY(int row)
+ {
+ return -GetRowTopY(row) - m_ContentOffset;
+ }
+
+ #endregion
+
+ #region 单元格内容渲染(统一入口)
+
+ /// 渲染单个单元格的完整内容(选择高亮 + 网格线 + 文本/图片/预制体)。
+ private void RenderCellContent(int row, int col, string defaultNs,
+ bool withGridLines, bool editorMode)
+ {
+ XTableCellData data = TableData.GetCell(row, col);
+ string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
+ ? data.StyleNamespace : defaultNs;
+
+ float cellX = GetCellContentX(col);
+ float cellY = GetCellContentY(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];
+ }
+
+ var objs = new CellTrackedObjs();
+
+ // ── 选择高亮 ──
+ ApplySelectionHighlight(row, col, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
+
+ // ── 网格线(仅完整刷新时)──
+ if (withGridLines)
+ ApplyGridLines(cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
+
+ // ── 单元格内容 ──
+ if (data != null)
+ {
+ switch (data.ContentType)
+ {
+ case CellContentType.Text:
+ RenderCellText(data, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
+ break;
+ case CellContentType.Image:
+ RenderCellImage(data, cellX, cellY, cellW, cellH, cellNS, ref objs);
+ break;
+ case CellContentType.Prefab:
+ RenderCellPrefab(data, row, col, cellX, cellY, cellW, cellH);
+ break;
+ }
+ }
+
+ m_CellTracked[(row, col)] = objs;
+ m_LastRenderedCells.Add((row, col));
+ }
+
+ private void ApplySelectionHighlight(int row, int col,
+ float cellX, float cellY, float cellW, float cellH,
+ string cellNS, bool editorMode, ref CellTrackedObjs objs)
+ {
+ bool inRange = IsInSelectionRange(row, col);
+ bool isFocused = row == SelectionHandle.StartRow && col == SelectionHandle.StartCol;
+
+ if (!inRange && !isFocused) return;
+
+ string key = inRange ? "cell_multiselect" : "cell_select";
+ var img = m_Assembler.GetImage(key, cellNS);
+ img.Rect.anchoredPosition = new Vector2(cellX, cellY);
+ img.Rect.sizeDelta = new Vector2(cellW, cellH);
+
+ if (editorMode)
+ {
+ Color selColor = GetCellSelectionBgColor(cellNS);
+ if (inRange)
+ img.Image.color = isFocused
+ ? new Color(selColor.r + Config.FocusRAdd, selColor.g + Config.FocusGAdd,
+ selColor.b + Config.FocusBAdd, selColor.a * Config.FocusAlphaMul)
+ : new Color(selColor.r, selColor.g, selColor.b,
+ selColor.a * Config.MultiSelectAlphaFactor);
+ else
+ img.Image.color = selColor;
+ }
+ else
+ {
+ if (inRange)
+ img.Image.color = isFocused ? Config.RuntimeFocusColor : Config.RuntimeMultiSelectColor;
+ else
+ img.Image.color = Config.RuntimeSelectionColor;
+ }
+
+ objs.SelectBg = img;
+ }
+
+ private void ApplyGridLines(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("cell_hline", cellNS);
+ 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("cell_vline", cellNS);
+ 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(XTableCellData data,
+ float cellX, float cellY, float cellW, float cellH,
+ string cellNS, bool editorMode, ref CellTrackedObjs objs)
+ {
+ if (string.IsNullOrEmpty(data.Text)) return;
+
+ var txt = m_Assembler.GetText("cell_text", cellNS);
+ txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
+ txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
+ txt.Text.text = data.Text;
+
+ if (editorMode)
+ {
+ txt.Text.color = GetCellFgColor(cellNS);
+ txt.Text.fontSize = GetCellFontSize(cellNS);
+ txt.Text.alignment = TMPro.TextAlignmentOptions.Midline;
+ txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font;
+ }
+
+ objs.CellText = txt;
+ }
+
+ private void RenderCellImage(XTableCellData data,
+ float cellX, float cellY, float cellW, float cellH,
+ string cellNS, ref CellTrackedObjs objs)
+ {
+ if (data.Image == null) return;
+
+ Sprite sprite = GetOrCreateSprite(data.Image);
+ var img = m_Assembler.GetImage("cell_image", cellNS);
+ img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
+ img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
+ img.Image.sprite = sprite;
+
+ objs.CellImage = img;
+ }
+
+ private void RenderCellPrefab(XTableCellData data, int row, int col,
+ float cellX, float cellY, float cellW, float cellH)
+ {
+ if (data.Prefab == null) return;
+
+ GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col);
+ RectTransform prt = instance.GetComponent();
+ prt.anchoredPosition = new Vector2(cellX, cellY);
+ prt.sizeDelta = new Vector2(cellW, cellH);
+ m_ActivePrefabMap[(row, col)] = instance;
+ data.OnPrefabAcquire?.Invoke(instance);
+ }
+
+ #endregion
+
+ #region 完整刷新
+
+ public void RefreshView()
+ {
+ if (m_Assembler == null)
+ {
+ if (rectTransform != null)
+ m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
+ else return;
+ }
+
+ if (m_RowHeights == null || m_ColWidths == null) return;
+ if (m_RowHeights.Length == 0 || m_ColWidths.Length == 0) return;
+
+ CreateBackground();
+
+#if UNITY_EDITOR
+ if (!Application.isPlaying)
+ Canvas.ForceUpdateCanvases();
+#endif
+
+ RecalculateTotalSize();
+ UpdateVisibleRect();
+
+ // 确保 ScrollRect Content 尺寸正确
+ RectTransform ctRt = ContentTransform as RectTransform;
+ if (ctRt != null)
+ {
+ float pad2 = m_ContentOffset * 2f;
+ ctRt.sizeDelta = new Vector2(m_TotalWidth + pad2, m_TotalHeight + pad2);
+ }
+
+ // 更新背景尺寸
+ if (m_BackgroundGo != null)
+ {
+ var bgRt = m_BackgroundGo.GetComponent();
+ bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
+ }
+
+ // 归还所有追踪对象 + 预制体
+ if (m_ActivePrefabMap != null)
+ {
+ foreach (var kv in m_ActivePrefabMap)
+ {
+ var cellData = TableData.GetCell(kv.Key.Item1, kv.Key.Item2);
+ cellData?.OnPrefabRelease?.Invoke(kv.Value);
+ }
+ m_ActivePrefabMap.Clear();
+ }
+ m_PrefabPool?.ReturnAll();
+ ReleaseAllTrackedCells();
+
+ // 清理孤立子对象
+ CleanOrphanChildren();
+
+ // 渲染所有可见单元格
+#if UNITY_EDITOR
+ bool editorMode = !Application.isPlaying;
+#else
+ bool editorMode = false;
+#endif
+ string ns = m_DefaultStyleNamespace;
+ ComputeVisibleCellsInto(m_LastRenderedCells);
+ foreach (var (r, c) in m_LastRenderedCells)
+ RenderCellContent(r, c, ns, withGridLines: true, editorMode: editorMode);
+
+ // 边框 + 冻结
+ RebuildBorders();
+ RenderFrozenCells();
+
+ m_DirtyFlag.Clear();
+ OnTableRefreshed?.Invoke();
+ SetChildrenVisible(m_ChildrenVisible);
+ }
+
+ #endregion
+
+ #region 轻刷新(增量——仅处理可见性变更)
+
+ private void RefreshViewLight()
+ {
+ if (m_Assembler == null)
+ {
+ if (rectTransform != null)
+ m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
+ else return;
+ }
+
+ if (m_RowHeights == null || m_ColWidths == null) return;
+ if (m_RowHeights.Length == 0 || m_ColWidths.Length == 0) return;
+
+ UpdateVisibleRect();
+
+#if UNITY_EDITOR
+ if (!Application.isPlaying)
+ Canvas.ForceUpdateCanvases();
+#endif
+
+#if UNITY_EDITOR
+ bool editorMode = !Application.isPlaying;
+#else
+ bool editorMode = false;
+#endif
+ string ns = m_DefaultStyleNamespace;
+
+ // 选区变更时:所有单元格的选择高亮需要重绘,但预制体不受影响
+ bool selectionChanged = m_DirtyFlag.Has(TableDirtyType.SelectionChanged);
+ if (selectionChanged)
+ {
+ ReleaseAllTrackedCells();
+ }
+
+ // 1. 计算新一帧的可见单元格(滚动路径用简单范围遍历,避免四叉树/分配开销)
+ var newVisible = new HashSet<(int, int)>();
+ ComputeVisibleCellsScrollInto(newVisible);
+
+ // 2. 回收不再可见的追踪对象
+ if (!selectionChanged)
+ {
+ m_ReusableCellList.Clear();
+ foreach (var cell in m_LastRenderedCells)
+ if (!newVisible.Contains(cell))
+ m_ReusableCellList.Add(cell);
+ foreach (var cell in m_ReusableCellList)
+ ReleaseTrackedCell(cell.Item1, cell.Item2);
+ }
+
+ // 3. 回收不再可见的预制体
+ if (m_ActivePrefabMap != null && m_ActivePrefabMap.Count > 0)
+ {
+ m_ReusableCellList.Clear();
+ foreach (var kv in m_ActivePrefabMap)
+ if (!newVisible.Contains(kv.Key))
+ m_ReusableCellList.Add(kv.Key);
+
+ foreach (var key in m_ReusableCellList)
+ {
+ var cellData = TableData.GetCell(key.Item1, key.Item2);
+ cellData?.OnPrefabRelease?.Invoke(m_ActivePrefabMap[key]);
+ m_PrefabPool.Release(m_ActivePrefabMap[key]);
+ m_ActivePrefabMap.Remove(key);
+ }
+ }
+
+ // 4. 渲染单元格:选区变更时全量重渲所有可见单元格,否则仅新进入的
+ foreach (var cell in newVisible)
+ {
+ if (selectionChanged || !m_LastRenderedCells.Contains(cell))
+ RenderCellContent(cell.Item1, cell.Item2, ns,
+ withGridLines: false, editorMode: editorMode);
+ }
+
+ // 5. 交换:将 newVisible 作为下一帧的比较基准
+ (m_LastRenderedCells, newVisible) = (newVisible, m_LastRenderedCells);
+
+ m_DirtyFlag.ClearFlags(TableDirtyType.ScrollChanged | TableDirtyType.SelectionChanged);
+ OnTableRefreshed?.Invoke();
+ SetChildrenVisible(m_ChildrenVisible);
+ }
+
+ #endregion
+
+ #region 孤立子对象清理
+
+ private void CleanOrphanChildren()
+ {
+ Transform poolRoot = m_Assembler?.PoolRoot;
+ Transform prefabPoolRoot = m_PrefabPool?.PoolRoot;
+ Transform contentT = ContentTransform;
+ Transform borderT = contentT?.Find("_TableBorders");
+
+ for (int i = contentT.childCount - 1; i >= 0; i--)
+ {
+ var child = contentT.GetChild(i);
+ if (poolRoot != null && child == poolRoot) continue;
+ if (prefabPoolRoot != null && child == prefabPoolRoot) continue;
+ if (m_BackgroundGo != null && child.gameObject == m_BackgroundGo) continue;
+ if (borderT != null && child == borderT) continue;
+
+ child.gameObject.hideFlags = HideFlags.None;
+ if (Application.isPlaying)
+ Destroy(child.gameObject);
+ else
+ DestroyImmediate(child.gameObject);
+ }
+ }
+
+ #endregion
+
+ #region 背景创建
+
+ private void CreateBackground()
+ {
+ if (m_BackgroundGo != 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();
+
+ RectTransform bgRt = m_BackgroundGo.GetComponent();
+ bgRt.anchorMin = new Vector2(0, 1);
+ bgRt.anchorMax = new Vector2(0, 1);
+ bgRt.pivot = new Vector2(0, 1);
+ bgRt.anchoredPosition = new Vector2(m_ContentOffset, -m_ContentOffset);
+ bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
+
+ var bgImg = m_BackgroundGo.GetComponent();
+ bgImg.color = GetCellBgColor(m_DefaultStyleNamespace);
+ bgImg.raycastTarget = false;
+ }
+
+ #endregion
+ }
+}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs.meta b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs.meta
new file mode 100644
index 0000000..6adfc82
--- /dev/null
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.RendererFilter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7336cd39fbd4ade469db004e3b950446
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.StyleParams.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.StyleParams.cs
index 19ed9b6..3485e08 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.StyleParams.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.StyleParams.cs
@@ -5,8 +5,7 @@ using XericLibrary.Runtime.SuperStyleSheet;
namespace XericUI.XTable.Rendering.Component
{
///
- /// 表格样式参数——统一管理所有样式路径与回退值。
- /// 修改此处的常量即可全局调整表格外观,无需在各方法中查找硬编码字符串。
+ /// 表格样式参数——统一管理所有样式路径、回退值、样式取值方法与默认命名空间初始化。
///
public partial class XericUIActionTable
{
@@ -95,5 +94,69 @@ namespace XericUI.XTable.Rendering.Component
GetStyleFontAsset(ns, STYLE_CELL_FONT_ASSET);
#endregion
+
+ #region 底层样式查询方法
+
+ private Color GetStyleColor(string ns, string path, Color fallback)
+ {
+ StyleValue val = StyleManager.GetValue(ns, path);
+ return val != null ? (Color)val : fallback;
+ }
+
+ private int GetStyleInt(string ns, string path, int fallback)
+ {
+ StyleValue val = StyleManager.GetValue(ns, path);
+ return val != null ? (int)val : fallback;
+ }
+
+ private float GetStyleFloat(string ns, string path, float fallback)
+ {
+ StyleValue val = StyleManager.GetValue(ns, path);
+ return val != null ? (float)val : fallback;
+ }
+
+ private TMP_FontAsset GetStyleFontAsset(string ns, string path, TMP_FontAsset fallback = null)
+ {
+ StyleValue val = StyleManager.GetValue(ns, path);
+ if (val == null) return fallback;
+ try { return val.GetValueAs(); }
+ catch { return fallback; }
+ }
+
+ #endregion
+
+ #region 默认样式命名空间初始化
+
+ private void EnsureDefaultStyleNamespace()
+ {
+ // 检查命名空间是否已存在,避免 HasStyle → GetValue 对不存在的命名空间报错
+ bool nsExists = StyleManager.GetAvailableNamespaces()?.Contains(m_DefaultStyleNamespace) == true;
+
+ // 命名空间已存在且样式已加载(来自 .xsss),无需添加回退值
+ if (nsExists && StyleManager.HasStyle(m_DefaultStyleNamespace, STYLE_CELL_BG_COLOR))
+ return;
+
+ // 回退默认值匹配 DefaultTableStyles.xsss
+ // AddDynamicStyle 自动创建命名空间(如果尚不存在)
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BG_COLOR, DEFAULT_BG_COLOR);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_FG_COLOR, DEFAULT_FG_COLOR);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_TOP_COLOR, DEFAULT_BORDER_COLOR);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_TOP_WIDTH, DEFAULT_BORDER_WIDTH);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_BOTTOM_COLOR, DEFAULT_BORDER_COLOR);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_BOTTOM_WIDTH, DEFAULT_BORDER_WIDTH);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_LEFT_COLOR, DEFAULT_BORDER_COLOR);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_LEFT_WIDTH, DEFAULT_BORDER_WIDTH);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_RIGHT_COLOR, DEFAULT_BORDER_COLOR);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_RIGHT_WIDTH, DEFAULT_BORDER_WIDTH);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_SELECTION_BG_COLOR, DEFAULT_SELECTION_COLOR);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_FONT_SIZE, DEFAULT_FONT_SIZE);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_FONT_ALIGNMENT, DEFAULT_FONT_ALIGNMENT);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_FONT_RICH_TEXT, DEFAULT_FONT_RICH_TEXT);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_DEFAULT_HEIGHT, DEFAULT_CELL_HEIGHT);
+ StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_DEFAULT_WIDTH, DEFAULT_CELL_WIDTH);
+ StyleManager.ForceRefresh();
+ }
+
+ #endregion
}
}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
index 05ced7f..fc7d73f 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
@@ -127,16 +127,6 @@ namespace XericUI.XTable.Rendering.Component
/// 冻结列数
public int FreezeColCount => m_FreezeColCount;
- /// 设置冻结行列数并重建视图
- public void SetFreezeCount(int rows, int cols)
- {
- m_FreezeRowCount = Mathf.Max(0, rows);
- m_FreezeColCount = Mathf.Max(0, cols);
- CreateViewportRenderer();
- MarkDirty(TableDirtyType.LayoutChanged);
- RefreshView();
- }
-
public string DefaultStyleNamespace => m_DefaultStyleNamespace;
/// 选中操作句柄——外部可通过此句柄控制选区,或直接操作 StartRow/StartCol 等属性
@@ -166,16 +156,8 @@ namespace XericUI.XTable.Rendering.Component
set => SetChildrenVisible(value);
}
- /// 判断单元格是否在多选范围内
- public bool IsInSelectionRange(int row, int col)
- {
- return SelectionHandle.IsInRange(row, col);
- }
-
- ///
- /// 动态子对象挂载的父节点。
- /// 若绑定了 ScrollRect 则使用其 Content,否则回退到自身的 rectTransform。
- ///
+ /// 动态子对象挂载的父节点。
+ /// 若绑定了 ScrollRect 则使用其 Content,否则回退到自身的 rectTransform。
public Transform ContentTransform
{
get
@@ -203,212 +185,10 @@ namespace XericUI.XTable.Rendering.Component
/// 单元格内容宽度扣除量
private float CellPadding2 => Config.CellHorizontalPadding * 2f;
- ///
- /// 基于四叉树查询结果渲染可见象限内的单元格内容(文本/预制体/选择高亮)。
- /// 边框由 统一绘制。
- ///
- private void RenderQuadrants(List quadrants)
+ /// 判断单元格是否在多选范围内
+ public bool IsInSelectionRange(int row, int col)
{
- string ns = m_DefaultStyleNamespace;
-
-#if UNITY_EDITOR
- if (!Application.isPlaying)
- {
- Color selectionColor = GetCellSelectionBgColor(ns);
- Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * Config.MultiSelectAlphaFactor);
- Color focusColor = new Color(selectionColor.r + Config.FocusRAdd, selectionColor.g + Config.FocusGAdd, selectionColor.b + Config.FocusBAdd, selectionColor.a * Config.FocusAlphaMul);
-
- foreach (var q in quadrants)
- {
- if (string.IsNullOrEmpty(q.Namespace)) continue;
-
- Rect r = q.Region;
- string qNs = q.Namespace;
-
- // 渲染象限内各单元格的文本/预制体/选择高亮
- if (q.Cells != null)
- {
- foreach (var (row, col) in q.Cells)
- {
- // 跳过被合并覆盖的单元格
- if (TableData.IsCellMerged(row, col)) continue;
- // 跳过不可见的单元格
- if (!IsCellVisible(row, col)) continue;
-
- float cellX = GetColLeftX(col) + m_ContentOffset;
- float cellY = -GetRowTopY(row) - m_ContentOffset;
- 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;
-
- switch (data.ContentType)
- {
- case CellContentType.Text:
- if (!string.IsNullOrEmpty(data.Text))
- {
- var txt = m_Assembler.GetText("cell_text", cellNS);
- txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- txt.Text.text = data.Text;
- txt.Text.color = GetCellFgColor(cellNS);
- txt.Text.fontSize = GetCellFontSize(cellNS);
- txt.Text.alignment = TextAlignmentOptions.Midline;
- txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font;
- }
- break;
- case CellContentType.Image:
- if (data.Image != null)
- {
- Sprite sprite = GetOrCreateSprite(data.Image);
- var img = m_Assembler.GetImage("cell_image", cellNS);
- img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- img.Image.sprite = sprite;
- }
- break;
- case CellContentType.Prefab:
- if (data.Prefab != null)
- {
- GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col);
- RectTransform prt = instance.GetComponent();
- prt.anchoredPosition = new Vector2(cellX, cellY);
- prt.sizeDelta = new Vector2(cellW, cellH);
- m_ActivePrefabMap[(row, col)] = instance;
- data.OnPrefabAcquire?.Invoke(instance);
- }
- break;
- }
- }
- }
- }
- return;
- }
-#endif
-
- // 运行时模式
- Color rtSelectionColor = Config.RuntimeSelectionColor;
- Color rtMultiSelectColor = Config.RuntimeMultiSelectColor;
- Color rtFocusColor = Config.RuntimeFocusColor;
-
- foreach (var q in quadrants)
- {
- if (string.IsNullOrEmpty(q.Namespace)) continue;
-
- Rect r = q.Region;
- string qNs = q.Namespace;
-
- if (q.Cells != null)
- {
- foreach (var (row, col) in q.Cells)
- {
- // 跳过被合并覆盖的单元格
- if (TableData.IsCellMerged(row, col)) continue;
- // 跳过不可见的单元格
- if (!IsCellVisible(row, col)) continue;
-
- float cellX = GetColLeftX(col) + m_ContentOffset;
- float cellY = -GetRowTopY(row) - m_ContentOffset;
- 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;
-
- switch (data.ContentType)
- {
- case CellContentType.Text:
- if (!string.IsNullOrEmpty(data.Text))
- {
- var txt = m_Assembler.GetText("cell_text", cellNS);
- txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- txt.Text.text = data.Text;
- }
- break;
- case CellContentType.Image:
- if (data.Image != null)
- {
- Sprite sprite = GetOrCreateSprite(data.Image);
- var img = m_Assembler.GetImage("cell_image", cellNS);
- img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- img.Image.sprite = sprite;
- }
- break;
- case CellContentType.Prefab:
- if (data.Prefab != null)
- {
- GameObject instance = m_PrefabPool.Acquire(data.Prefab, row, col);
- RectTransform prt = instance.GetComponent();
- prt.anchoredPosition = new Vector2(cellX, cellY);
- prt.sizeDelta = new Vector2(cellW, cellH);
- m_ActivePrefabMap[(row, col)] = instance;
- data.OnPrefabAcquire?.Invoke(instance);
- }
- break;
- }
- }
- }
- }
+ return SelectionHandle.IsInRange(row, col);
}
#endregion
@@ -453,6 +233,19 @@ namespace XericUI.XTable.Rendering.Component
if (m_TableData == null)
m_TableData = new XTableData();
+ // 强制 Content 的 anchor/pivot 为 (0,1),确保 anchoredPosition 语义一致。
+ // 若不固定,ScrollRect 可能改变 anchor 导致坐标计算错误。
+ if (m_ScrollRect != null && m_ScrollRect.content != null)
+ {
+ RectTransform ct = m_ScrollRect.content as RectTransform;
+ if (ct != null)
+ {
+ ct.anchorMin = new Vector2(0, 1);
+ ct.anchorMax = new Vector2(0, 1);
+ ct.pivot = new Vector2(0, 1);
+ }
+ }
+
m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
m_PrefabPool = new PrefabPrototypePool(ContentTransform);
m_ActivePrefabMap = new Dictionary<(int, int), GameObject>();
@@ -562,1437 +355,7 @@ namespace XericUI.XTable.Rendering.Component
#endregion
- #region 编辑器模式支持
-
- [System.NonSerialized] private bool m_EditorUpdateSubscribed;
-
- private void SubscribeEditorUpdate()
- {
- if (m_EditorUpdateSubscribed) return;
-#if UNITY_EDITOR
- UnityEditor.EditorApplication.update += EditorUpdate;
- m_EditorUpdateSubscribed = true;
-#endif
- }
-
- private void UnsubscribeEditorUpdate()
- {
- if (!m_EditorUpdateSubscribed) return;
-#if UNITY_EDITOR
- UnityEditor.EditorApplication.update -= EditorUpdate;
- m_EditorUpdateSubscribed = false;
-#endif
- }
-
-#if UNITY_EDITOR
- private void EditorUpdate()
- {
- if (this == null || !isActiveAndEnabled)
- {
- UnsubscribeEditorUpdate();
- return;
- }
-
- if (!m_DirtyFlag.IsDirty) return;
-
- var flags = m_DirtyFlag.Flags;
- if ((flags & ~(TableDirtyType.ScrollChanged | TableDirtyType.SelectionChanged)) == 0)
- RefreshViewLight();
- else
- RefreshView();
- }
-#endif
-
- #endregion
-
- #region 公开方法
-
- public void SetCellData(int row, int col, XTableCellData data)
- {
- TableData.SetCell(row, col, data);
- MarkDirty(TableDirtyType.DataChanged);
- }
-
- /// 设置文本内容(ContentType 自动设为 Text)
- public XTableCellData SetCellText(int row, int col, string text)
- {
- var data = TableData.GetOrCreateCell(row, col);
- data.Text = text;
- data.ContentType = CellContentType.Text;
- MarkDirty(TableDirtyType.DataChanged);
- return data;
- }
-
- /// 设置图片纹理(ContentType 自动设为 Image)
- public XTableCellData SetCellImage(int row, int col, Texture2D image)
- {
- var data = TableData.GetOrCreateCell(row, col);
- data.Image = image;
- data.ContentType = CellContentType.Image;
- MarkDirty(TableDirtyType.DataChanged);
- return data;
- }
-
- /// 设置预制体(ContentType 自动设为 Prefab)
- /// 注意是设置预制体而不是实例,设置后订阅单元格预制体生成事件 OnPrefabAcquire 处理获取事件
- public XTableCellData SetCellPrefab(int row, int col, GameObject prefab)
- {
- var data = TableData.GetOrCreateCell(row, col);
- data.Prefab = prefab;
- data.ContentType = CellContentType.Prefab;
- MarkDirty(TableDirtyType.DataChanged);
- return data;
- }
-
- /// 设置单元格的样式命名空间,同时写入边框缓存(谁最后设置归谁)
- public XTableCellData SetCellStyle(int row, int col, string styleNamespace, string stylePath)
- {
- var data = TableData.GetOrCreateCell(row, col);
- if (data.CellStyle == null)
- data.CellStyle = new XericLibrary.Runtime.SuperStyleSheet.StyleMember();
- data.CellStyle.CurrentNamespace = styleNamespace ?? "";
- data.CellStyle.StylePath = stylePath ?? "";
- MarkDirty(TableDirtyType.StyleChanged);
- return data;
- }
-
- /// 设置单元格的样式命名空间(兼容旧版,等同于 StyleNamespace 字段)
- public void SetCellStyleNamespace(int row, int col, string styleNamespace)
- {
- // 合并源:遍历所有被合并单元格,全部写入该样式
- if (TableData.IsMergeSource(row, col, out int rowSpan, out int colSpan))
- {
- for (int r = row; r < row + rowSpan; r++)
- {
- for (int c = col; c < col + colSpan; c++)
- {
- if (r < m_RowHeights.Length && c < m_ColWidths.Length)
- ApplyCellStyleToCell(r, c, styleNamespace);
- }
- }
- }
- else
- {
- ApplyCellStyleToCell(row, col, styleNamespace);
- }
-
- MarkDirty(TableDirtyType.StyleChanged);
- }
-
- /// 在单个单元格上设置样式,同时写入其右侧和底部的边框缓存
- private void ApplyCellStyleToCell(int row, int col, string ns)
- {
- var data = TableData.GetOrCreateCell(row, col);
- data.StyleNamespace = ns;
- TableData.SetBorderRightNs(row, col, ns);
- TableData.SetBorderBottomNs(row, col, ns);
- }
-
- /// 清除文本内容
- public void ClearCellText(int row, int col)
- {
- var data = TableData.GetCell(row, col);
- if (data == null) return;
- data.Text = null;
- if (data.ContentType == CellContentType.Text)
- data.ContentType = CellContentType.Text; // 保持类型不变,仅清空内容
- MarkDirty(TableDirtyType.DataChanged);
- }
-
- /// 清除图片纹理
- public void ClearCellImage(int row, int col)
- {
- var data = TableData.GetCell(row, col);
- if (data == null) return;
- data.Image = null;
- MarkDirty(TableDirtyType.DataChanged);
- }
-
- /// 清除预制体及委托
- public void ClearCellPrefab(int row, int col)
- {
- var data = TableData.GetCell(row, col);
- if (data == null) return;
- data.Prefab = null;
- data.OnPrefabAcquire = null;
- data.OnPrefabRelease = null;
- MarkDirty(TableDirtyType.DataChanged);
- }
-
- /// 清除单元格样式覆盖
- public void ClearCellStyle(int row, int col)
- {
- var data = TableData.GetCell(row, col);
- if (data == null) return;
- data.CellStyle = null;
- MarkDirty(TableDirtyType.StyleChanged);
- }
-
- public XTableCellData GetCellData(int row, int col)
- => TableData.GetOrCreateCell(row, col);
-
- /// 获取单元格的样式命名空间(含回退逻辑)
- public string GetCellStyleNamespace(int row, int col)
- {
- var data = TableData.GetCell(row, col);
- return data != null ? data.GetEffectiveStyleNamespace(m_DefaultStyleNamespace) : m_DefaultStyleNamespace;
- }
-
- public void MergeCells(int startRow, int startCol, int rowSpan, int colSpan)
- {
- TableData.MergeCells(startRow, startCol, rowSpan, colSpan);
- MarkDirty(TableDirtyType.DataChanged);
- }
-
- public void UnmergeCells(int startRow, int startCol)
- {
- TableData.UnmergeCells(startRow, startCol);
- MarkDirty(TableDirtyType.DataChanged);
- }
-
- /// 判断指定单元格是否被合并覆盖(非源,会被重定向)
- public bool IsCellMerged(int row, int col) => TableData.IsCellMerged(row, col);
-
- /// 判断指定单元格是否为合并源(锚点),并返回合并跨度
- public bool IsMergeSource(int row, int col, out int rowSpan, out int colSpan)
- => TableData.IsMergeSource(row, col, out rowSpan, out colSpan);
-
- /// 在末尾追加一行,默认行高来自 Config
- public void AddRow(float height = -1f)
- {
- if (height <= 0) height = Config.DefaultRowHeight;
-#if UNITY_EDITOR
- UnityEditor.Undo.RecordObject(this, "Add Table Row");
-#endif
- int newLen = m_RowHeights.Length + 1;
- var newArr = new float[newLen];
- System.Array.Copy(m_RowHeights, newArr, m_RowHeights.Length);
- newArr[newLen - 1] = height;
- m_RowHeights = newArr;
- MarkDirty(TableDirtyType.LayoutChanged);
-#if UNITY_EDITOR
- UnityEditor.EditorUtility.SetDirty(this);
-#endif
- }
-
- /// 在末尾追加一列,默认列宽来自 Config
- public void AddColumn(float width = -1f)
- {
- if (width <= 0) width = Config.DefaultColumnWidth;
-#if UNITY_EDITOR
- UnityEditor.Undo.RecordObject(this, "Add Table Column");
-#endif
- int newLen = m_ColWidths.Length + 1;
- var newArr = new float[newLen];
- System.Array.Copy(m_ColWidths, newArr, m_ColWidths.Length);
- newArr[newLen - 1] = width;
- m_ColWidths = newArr;
- MarkDirty(TableDirtyType.LayoutChanged);
-#if UNITY_EDITOR
- UnityEditor.EditorUtility.SetDirty(this);
-#endif
- }
-
- /// 设置指定行的高度
- public void SetRowHeight(int row, float height)
- {
- if (row < 0 || row >= m_RowHeights.Length) return;
- if (height <= 0) return;
-#if UNITY_EDITOR
- UnityEditor.Undo.RecordObject(this, "Set Row Height");
-#endif
- m_RowHeights[row] = height;
- MarkDirty(TableDirtyType.LayoutChanged);
-#if UNITY_EDITOR
- if (!Application.isPlaying) UnityEditor.EditorUtility.SetDirty(this);
-#endif
- }
-
- /// 设置指定列的宽度
- public void SetColWidth(int col, float width)
- {
- if (col < 0 || col >= m_ColWidths.Length) return;
- if (width <= 0) return;
-#if UNITY_EDITOR
- UnityEditor.Undo.RecordObject(this, "Set Column Width");
-#endif
- m_ColWidths[col] = width;
- MarkDirty(TableDirtyType.LayoutChanged);
-#if UNITY_EDITOR
- if (!Application.isPlaying) UnityEditor.EditorUtility.SetDirty(this);
-#endif
- }
-
- /// 删除指定行,单元格数据上移
- public void RemoveRow(int row)
- {
- if (row < 0 || row >= m_RowHeights.Length) return;
-#if UNITY_EDITOR
- UnityEditor.Undo.RecordObject(this, "Remove Table Row");
-#endif
- int colCount = m_ColWidths.Length;
-
- // 先解除涉及该行的合并
- for (int c = 0; c < colCount; c++)
- TableData.UnmergeCells(row, c);
-
- // 将下方行数据上移
- for (int r = row + 1; r < m_RowHeights.Length; r++)
- for (int c = 0; c < colCount; c++)
- {
- var data = TableData.GetCell(r, c);
- if (data != null)
- TableData.SetCell(r - 1, c, data);
- else
- TableData.SetCell(r - 1, c, null); // 显式清空原位置
- }
-
- // 清空最后一行数据
- int lastRow = m_RowHeights.Length - 1;
- for (int c = 0; c < colCount; c++)
- TableData.SetCell(lastRow, c, null);
-
- // 缩小行高数组
- int newLen = m_RowHeights.Length - 1;
- var newArr = new float[newLen];
- for (int i = 0; i < row; i++)
- newArr[i] = m_RowHeights[i];
- for (int i = row; i < newLen; i++)
- newArr[i] = m_RowHeights[i + 1];
- m_RowHeights = newArr;
-
- // 裁剪选区到新尺寸
- ClampSelectionToBounds();
-
- MarkDirty(TableDirtyType.LayoutChanged | TableDirtyType.DataChanged | TableDirtyType.SelectionChanged);
-#if UNITY_EDITOR
- UnityEditor.EditorUtility.SetDirty(this);
-#endif
- }
-
- /// 删除指定列,单元格数据左移
- public void RemoveColumn(int col)
- {
- if (col < 0 || col >= m_ColWidths.Length) return;
-#if UNITY_EDITOR
- UnityEditor.Undo.RecordObject(this, "Remove Table Column");
-#endif
- int rowCount = m_RowHeights.Length;
-
- // 先解除涉及该列的合并
- for (int r = 0; r < rowCount; r++)
- TableData.UnmergeCells(r, col);
-
- // 将右侧列数据左移
- for (int r = 0; r < rowCount; r++)
- for (int c = col + 1; c < m_ColWidths.Length; c++)
- {
- var data = TableData.GetCell(r, c);
- if (data != null)
- TableData.SetCell(r, c - 1, data);
- else
- TableData.SetCell(r, c - 1, null);
- }
-
- // 清空最后一列数据
- int lastCol = m_ColWidths.Length - 1;
- for (int r = 0; r < rowCount; r++)
- TableData.SetCell(r, lastCol, null);
-
- // 缩小列宽数组
- int newLen = m_ColWidths.Length - 1;
- var newArr = new float[newLen];
- for (int i = 0; i < col; i++)
- newArr[i] = m_ColWidths[i];
- for (int i = col; i < newLen; i++)
- newArr[i] = m_ColWidths[i + 1];
- m_ColWidths = newArr;
-
- // 裁剪选区到新尺寸
- ClampSelectionToBounds();
-
- MarkDirty(TableDirtyType.LayoutChanged | TableDirtyType.DataChanged | TableDirtyType.SelectionChanged);
-#if UNITY_EDITOR
- UnityEditor.EditorUtility.SetDirty(this);
-#endif
- }
-
- /// 设置外部选择句柄(替换内部句柄)
- 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);
- }
-
- /// 单选一个单元格
- public void SelectCell(int row, int col)
- {
- SelectionHandle.Select(row, col);
- MarkDirty(TableDirtyType.SelectionChanged);
- }
-
- /// 多选一个范围
- public void SelectCell(int startRow, int startCol, int endRow, int endCol)
- {
- SelectionHandle.Select(startRow, startCol, endRow, endCol);
- MarkDirty(TableDirtyType.SelectionChanged);
- }
-
- /// 清除选择
- public void ClearSelection()
- {
- SelectionHandle.Clear();
- MarkDirty(TableDirtyType.SelectionChanged);
- }
-
- /// 通过文本选择一个单元格(如 "A0")
- public bool SelectCellByText(string text)
- {
- if (!XTableCoordinateFormat.TryParseCell(text, out int row, out int col))
- return false;
- SelectCell(row, col);
- return true;
- }
-
- /// 通过文本选择一个范围(如 "A0:B1", "A:A", "0:0")
- public bool SelectRangeByText(string text)
- {
- if (!XTableCoordinateFormat.TryParseRange(text,
- out int sr, out int sc, out int er, out int ec))
- return false;
-
- SelectCell(sr, sc, er, ec);
- return true;
- }
-
- /// 获取当前选区的文本描述(单格 "A0",多格 "A0:B1")
- public string GetSelectionText()
- {
- if (!SelectionHandle.HasSelection) return string.Empty;
- if (SelectionHandle.HasMultiSelection)
- {
- return XTableCoordinateFormat.RangeToText(
- SelectionHandle.MinRow, SelectionHandle.MinCol,
- SelectionHandle.MaxRow, SelectionHandle.MaxCol);
- }
-
- return XTableCoordinateFormat.CellToText(
- SelectionHandle.StartRow, SelectionHandle.StartCol);
- }
-
- private void OnHandleSelectionChanged(XTableSelectionHandle handle)
- {
- MarkDirty(TableDirtyType.SelectionChanged);
- }
-
- /// 裁剪选区到当前行列尺寸范围内
- private void ClampSelectionToBounds()
- {
- int maxR = (m_RowHeights?.Length ?? 0) - 1;
- int maxC = (m_ColWidths?.Length ?? 0) - 1;
- SelectionHandle.ClampTo(maxR, maxC);
- }
-
- public void RefreshView()
- {
- // 编辑模式下 Awake 可能尚未执行,按需回退初始化
- if (m_Assembler == null)
- {
- if (rectTransform != null)
- m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
- else return;
- }
-
- if (m_RowHeights == null || m_ColWidths == null) return;
- if (m_RowHeights.Length == 0 || m_ColWidths.Length == 0) return;
-
- CreateBackground();
-
-#if UNITY_EDITOR
- if (!Application.isPlaying)
- Canvas.ForceUpdateCanvases();
-#endif
-
- RecalculateTotalSize();
- UpdateVisibleRect();
-
- // 确保 ScrollRect Content 尺寸正确(含 padding 偏移量,防止 viewport 裁切边缘边框)
- RectTransform ctRt = ContentTransform as RectTransform;
- if (ctRt != null)
- {
- float pad2 = m_ContentOffset * 2f;
- ctRt.sizeDelta = new Vector2(m_TotalWidth + pad2, m_TotalHeight + pad2);
- }
-
- // 更新背景尺寸(仅覆盖网格区域,无需 padding)
- if (m_BackgroundGo != null)
- {
- var bgRt = m_BackgroundGo.GetComponent();
- bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
- }
-
- // 归还预制体前,触发所有活跃实例的释放回调
- if (m_ActivePrefabMap != null)
- {
- foreach (var kv in m_ActivePrefabMap)
- {
- var data = TableData.GetCell(kv.Key.Item1, kv.Key.Item2);
- data?.OnPrefabRelease?.Invoke(kv.Value);
- }
- m_ActivePrefabMap.Clear();
- }
-
- // 回收预制体池
- m_PrefabPool?.ReturnAll();
-
- // 回收 CellAssembler 池
- m_Assembler.ReturnAll();
-
- // 额外清理孤立的直接子对象(防止域重载或异常流程产生的残留)
- Transform poolRoot = m_Assembler.PoolRoot;
- Transform prefabPoolRoot = m_PrefabPool?.PoolRoot;
- Transform contentT = ContentTransform;
- Transform borderT = contentT.Find("_TableBorders");
- for (int i = contentT.childCount - 1; i >= 0; i--)
- {
- var child = contentT.GetChild(i);
- if (poolRoot != null && child == poolRoot) continue;
- if (prefabPoolRoot != null && child == prefabPoolRoot) continue;
- if (m_BackgroundGo != null && child.gameObject == m_BackgroundGo) continue;
- if (borderT != null && child == borderT) continue;
-
- child.gameObject.hideFlags = HideFlags.None;
- if (Application.isPlaying)
- Destroy(child.gameObject);
- else
- DestroyImmediate(child.gameObject);
- }
-
- // 渲染可见单元格:优先使用四叉树查询
- if (m_Quadtree != null && m_Quadtree.IsBuilt)
- {
- var quadrants = new List();
- 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);
- }
-
- // 重建边框线段
- RebuildBorders();
-
- // 渲染 Viewport 层冻结单元格
- RenderFrozenCells();
-
- m_DirtyFlag.Clear();
- OnTableRefreshed?.Invoke();
- SetChildrenVisible(m_ChildrenVisible);
- }
-
- ///
- /// 轻刷新——仅更新可见矩形和可见单元格内容。
- /// 不重建背景、边框、不重置 content.sizeDelta、不做孤儿清理。
- /// 用于滚动和选区变更场景。
- ///
- private void RefreshViewLight()
- {
- if (m_Assembler == null)
- {
- if (rectTransform != null)
- m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
- else return;
- }
-
- if (m_RowHeights == null || m_ColWidths == null) return;
- if (m_RowHeights.Length == 0 || m_ColWidths.Length == 0) return;
-
-#if UNITY_EDITOR
- if (!Application.isPlaying)
- Canvas.ForceUpdateCanvases();
-#endif
-
- // 1. 回收 CellAssembler 对象(轻量操作:SetActive(false) + 压栈)
- m_Assembler.ReturnAll();
-
- // 2. 计算新可见单元格集合并回收不再可见的预制体
- HashSet<(int, int)> newVisibleCells;
-
- if (m_Quadtree != null && m_Quadtree.IsBuilt)
- {
- var quadrants = new List();
- m_Quadtree.QueryVisible(m_VisibleRect, quadrants);
-
- newVisibleCells = new HashSet<(int, int)>();
- foreach (var q in quadrants)
- {
- if (q.Cells != null)
- {
- foreach (var cell in q.Cells)
- newVisibleCells.Add(cell);
- }
- }
- }
- else
- {
- // 回退:计算可见行列范围
- CalcVisibleRange(out int vrSr, out int vrEr, out int vrSc, out int vrEc);
- newVisibleCells = new HashSet<(int, int)>();
- for (int vr = vrSr; vr < vrEr; vr++)
- for (int vc = vrSc; vc < vrEc; vc++)
- newVisibleCells.Add((vr, vc));
- }
-
- // 回收不再可见的预制体
- if (m_ActivePrefabMap != null && m_ActivePrefabMap.Count > 0)
- {
- var toRelease = new List<(int, int)>();
- foreach (var kv in m_ActivePrefabMap)
- {
- if (!newVisibleCells.Contains(kv.Key))
- toRelease.Add(kv.Key);
- }
-
- foreach (var key in toRelease)
- {
- var data = TableData.GetCell(key.Item1, key.Item2);
- data?.OnPrefabRelease?.Invoke(m_ActivePrefabMap[key]);
- m_PrefabPool.Release(m_ActivePrefabMap[key]);
- m_ActivePrefabMap.Remove(key);
- }
- }
-
- // 3. 渲染可见单元格(仅内容 + 选择高亮,无边框/背景)
- if (m_Quadtree != null && m_Quadtree.IsBuilt)
- {
- var quadrants = new List();
- m_Quadtree.QueryVisible(m_VisibleRect, quadrants);
- RenderQuadrants(quadrants);
- }
- else
- {
- CalcVisibleRange(out int sr, out int er, out int sc, out int ec);
- if (er > sr && ec > sc)
- RenderCellsLight(sr, er, sc, ec);
- }
-
- // 仅清除滚动和选区脏标记,保留其他标记(如 DataChanged 下次触发重刷新)
- m_DirtyFlag.ClearFlags(TableDirtyType.ScrollChanged | TableDirtyType.SelectionChanged);
- OnTableRefreshed?.Invoke();
- SetChildrenVisible(m_ChildrenVisible);
- }
-
- #endregion
-
- #region 背景
-
- private void CreateBackground()
- {
- if (m_BackgroundGo != 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();
-
- RectTransform bgRt = m_BackgroundGo.GetComponent();
- bgRt.anchorMin = new Vector2(0, 1);
- bgRt.anchorMax = new Vector2(0, 1);
- bgRt.pivot = new Vector2(0, 1);
- bgRt.anchoredPosition = new Vector2(m_ContentOffset, -m_ContentOffset);
- bgRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
-
- var bgImg = m_BackgroundGo.GetComponent();
- bgImg.color = GetCellBgColor(m_DefaultStyleNamespace);
- bgImg.raycastTarget = false;
- }
-
- /// 销毁 transform 下所有子对象(用于域重载后清理残留)
- private void ClearAllChildren()
- {
- Transform t = ContentTransform;
- if (t == null) return;
-
-#if UNITY_EDITOR
- // 编辑器模式下先重置 HideFlags 再销毁,防止 DontSave 对象拒绝销毁
- while (t.childCount > 0)
- {
- var child = t.GetChild(0);
- child.gameObject.hideFlags = HideFlags.None;
- if (Application.isPlaying)
- Destroy(child.gameObject);
- else
- DestroyImmediate(child.gameObject);
- }
-#else
- for (int i = t.childCount - 1; i >= 0; i--)
- {
- var child = t.GetChild(i);
- Destroy(child.gameObject);
- }
-#endif
- }
-
- /// 设置所有动态子对象的可见性(仅编辑器)
- public void SetChildrenVisible(bool visible)
- {
- m_ChildrenVisible = visible;
-#if UNITY_EDITOR
- Transform t = ContentTransform;
- if (t == null) return;
- for (int i = t.childCount - 1; i >= 0; i--)
- {
- var child = t.GetChild(i);
- child.gameObject.hideFlags = visible
- ? HideFlags.DontSave
- : HideFlags.DontSave | HideFlags.HideInHierarchy;
- }
-#endif
- }
-
- /// 完全重建表格——销毁所有动态生成的对象并重新初始化
- [ContextMenu("Xeric UI/完全重建表格")]
- public void FullRebuild()
- {
- m_Assembler?.DestroyAll();
- m_PrefabPool?.DestroyAll();
- ClearAllChildren();
- m_BackgroundGo = null;
- m_BorderRenderer = null;
-
- m_TableData = new XTableData();
-
- m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
- m_PrefabPool = new PrefabPrototypePool(ContentTransform);
- m_ActivePrefabMap = new Dictionary<(int, int), GameObject>();
- RecalculateTotalSize();
- CreateDefaultSampleData();
- CreateBackground();
- CreateViewportRenderer();
- MarkDirty(TableDirtyType.All);
- RefreshView();
- SetChildrenVisible(m_ChildrenVisible);
- }
-
- /// 重新生成表格但保留已有文本数据——先保存,重建后再回填
- public void FullRebuildPreservingData()
- {
- // 保存当前单元格文本
- var savedTexts = new Dictionary<(int, int), string>();
- if (m_TableData != null)
- {
- int rows = m_RowHeights?.Length ?? 0;
- int cols = m_ColWidths?.Length ?? 0;
- for (int r = 0; r < rows; r++)
- {
- for (int c = 0; c < cols; c++)
- {
- var cell = m_TableData.GetCell(r, c);
- if (cell != null && !string.IsNullOrEmpty(cell.Text))
- savedTexts[(r, c)] = cell.Text;
- }
- }
- }
-
- FullRebuild();
-
- // 恢复文本数据
- foreach (var kv in savedTexts)
- {
- int r = kv.Key.Item1, c = kv.Key.Item2;
- if (r < (m_RowHeights?.Length ?? 0) && c < (m_ColWidths?.Length ?? 0))
- m_TableData.GetOrCreateCell(r, c).Text = kv.Value;
- }
-
- RefreshView();
- SetChildrenVisible(m_ChildrenVisible);
- }
-
- ///
- /// 清空所有数据(单元格文本、合并信息),但保留行列排布和样式。
- /// 等价于"清空内容但不改变表格结构"。
- ///
- public void ClearData()
- {
- if (m_TableData != null)
- {
- m_TableData.ClearAllMerges();
- m_TableData.BlockMap.Clear();
- }
- m_ActivePrefabMap?.Clear();
- m_PrefabPool?.ReturnAll();
- m_Quadtree?.Clear();
- ClearSelection();
- MarkDirty(TableDirtyType.DataChanged);
- RefreshView();
- }
-
- ///
- /// 清空表格所有内容——数据、行列、样式,等同 但不创建默认示例数据。
- ///
- public void ClearTable()
- {
- m_Assembler?.DestroyAll();
- m_PrefabPool?.DestroyAll();
- m_ActivePrefabMap?.Clear();
- m_Quadtree?.Clear();
- ClearAllChildren();
- m_BackgroundGo = null;
- m_BorderRenderer = null;
-
- m_TableData = new XTableData();
- m_RowHeights = null;
- m_ColWidths = null;
- ClearSelection();
-
- m_Assembler = new CellAssembler(ContentTransform, m_DefaultStyleNamespace);
- m_PrefabPool = new PrefabPrototypePool(ContentTransform);
- m_ActivePrefabMap = new Dictionary<(int, int), GameObject>();
- MarkDirty(TableDirtyType.All);
- RecalculateTotalSize();
- CreateBackground();
- CreateViewportRenderer();
- RefreshView();
- SetChildrenVisible(m_ChildrenVisible);
- }
-
- /// 仅强制刷新样式,不重建数据结构
- public void ForceStyleRefresh()
- {
- StyleManager.ForceRefresh();
- MarkDirty(TableDirtyType.StyleChanged);
- RefreshView();
- }
-
- /// 构建剪贴板数据对象(尺寸 + 单元格文本 + 合并信息)
- public XTableClipboardData BuildClipboardData()
- {
- return XTableClipboardData.FromTable(this);
- }
-
- /// 将剪贴板数据应用到表格——覆盖尺寸、命名空间、单元格文本、合并信息并刷新
- public void ApplyClipboardData(XTableClipboardData data)
- {
- if (data == null) return;
-
- // 确保数据层存在
- if (m_TableData == null)
- m_TableData = new XTableData();
-
- // 应用尺寸与默认命名空间
- if (data.RowHeights != null && data.RowHeights.Length > 0)
- m_RowHeights = data.RowHeights;
- if (data.ColWidths != null && data.ColWidths.Length > 0)
- m_ColWidths = data.ColWidths;
- if (!string.IsNullOrEmpty(data.DefaultStyleNamespace))
- m_DefaultStyleNamespace = data.DefaultStyleNamespace;
-
- // 写回单元格文本与合并信息
- data.PopulateToTable(this);
-
- MarkDirty(TableDirtyType.DataChanged);
- RecalculateTotalSize();
- RefreshView();
- SetChildrenVisible(m_ChildrenVisible);
- }
-
- #endregion
-
- #region 单元格渲染
-
- private void RenderCells(int sr, int er, int sc, int ec)
- {
- string ns = m_DefaultStyleNamespace;
-
- // 编辑器模式:从样式表 pull 所有颜色值
-#if UNITY_EDITOR
- if (!Application.isPlaying)
- {
- Color selectionColor = GetCellSelectionBgColor(ns);
- Color borderColor = GetCellBorderColor(ns);
- float borderWidth = GetCellBorderWidth(ns);
-
- Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b, selectionColor.a * Config.MultiSelectAlphaFactor);
- Color focusColor = new Color(selectionColor.r + Config.FocusRAdd, selectionColor.g + Config.FocusGAdd, selectionColor.b + Config.FocusBAdd, selectionColor.a * Config.FocusAlphaMul);
-
- for (int r = sr; r < er && r < m_RowHeights.Length; r++)
- {
- for (int c = sc; c < ec && c < m_ColWidths.Length; c++)
- {
- // 跳过被合并覆盖的单元格
- if (TableData.IsCellMerged(r, c)) continue;
-
- float cellX = GetColLeftX(c) + m_ContentOffset;
- float cellY = -GetRowTopY(r) - m_ContentOffset;
- float cellW = m_ColWidths[c];
- float cellH = m_RowHeights[r];
-
- // 合并源:扩展尺寸
- if (TableData.IsMergeSource(r, c, out int mergeRS, out int mergeCS))
- {
- for (int i = 1; i < mergeCS && c + i < m_ColWidths.Length; i++)
- cellW += m_ColWidths[c + i];
- for (int i = 1; i < mergeRS && r + i < m_RowHeights.Length; i++)
- cellH += m_RowHeights[r + i];
- }
-
- XTableCellData data = TableData.GetCell(r, c);
- string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
- ? data.StyleNamespace : ns;
-
- // 多选 / 单选高亮
- if (IsInSelectionRange(r, c))
- {
- 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 == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
- ? focusColor : multiSelectColor;
- }
- else if (r == SelectionHandle.StartRow && c == 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;
- }
-
- // 水平网格线(合并区域底部)
- var hLine = m_Assembler.GetImage("cell_hline");
- hLine.Rect.anchoredPosition = new Vector2(cellX, cellY - cellH);
- hLine.Rect.sizeDelta = new Vector2(cellW, borderWidth);
- hLine.Image.color = borderColor;
-
- // 垂直网格线(合并区域右侧)
- var vLine = m_Assembler.GetImage("cell_vline");
- 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)
- {
- case CellContentType.Text:
- if (!string.IsNullOrEmpty(data.Text))
- {
- var txt = m_Assembler.GetText("cell_text", cellNS);
- txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- txt.Text.text = data.Text;
- txt.Text.color = GetCellFgColor(cellNS);
- txt.Text.fontSize = GetCellFontSize(cellNS);
- txt.Text.alignment = TextAlignmentOptions.Midline;
- txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font;
- }
- break;
- case CellContentType.Image:
- if (data.Image != null)
- {
- Sprite sprite = GetOrCreateSprite(data.Image);
- var img = m_Assembler.GetImage("cell_image", cellNS);
- img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- img.Image.sprite = sprite;
- }
- break;
- case CellContentType.Prefab:
- if (data.Prefab != null)
- {
- GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c);
- RectTransform prt = instance.GetComponent();
- prt.anchoredPosition = new Vector2(cellX, cellY);
- prt.sizeDelta = new Vector2(cellW, cellH);
- m_ActivePrefabMap[(r, c)] = instance;
- data.OnPrefabAcquire?.Invoke(instance);
- }
- break;
- }
- }
- }
- return;
- }
-#endif
-
- // 运行时模式
- Color rtSelectionColor = Config.RuntimeSelectionColor;
- Color rtMultiSelectColor = Config.RuntimeMultiSelectColor;
- Color rtFocusColor = Config.RuntimeFocusColor;
-
- for (int r = sr; r < er && r < m_RowHeights.Length; r++)
- {
- for (int c = sc; c < ec && c < m_ColWidths.Length; c++)
- {
- // 跳过被合并覆盖的单元格
- if (TableData.IsCellMerged(r, c)) continue;
-
- float cellX = GetColLeftX(c) + m_ContentOffset;
- float cellY = -GetRowTopY(r) - m_ContentOffset;
- float cellW = m_ColWidths[c];
- float cellH = m_RowHeights[r];
-
- // 合并源:扩展尺寸
- if (TableData.IsMergeSource(r, c, out int mergeRS, out int mergeCS))
- {
- for (int i = 1; i < mergeCS && c + i < m_ColWidths.Length; i++)
- cellW += m_ColWidths[c + i];
- for (int i = 1; i < mergeRS && r + i < m_RowHeights.Length; i++)
- cellH += m_RowHeights[r + i];
- }
-
- XTableCellData data = TableData.GetCell(r, c);
- string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
- ? data.StyleNamespace : ns;
-
- if (IsInSelectionRange(r, c))
- {
- 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 == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
- ? rtFocusColor : rtMultiSelectColor;
- }
- else if (r == SelectionHandle.StartRow && c == 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;
- }
-
- var hLine = m_Assembler.GetImage("cell_hline");
- 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("cell_vline");
- 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)
- {
- case CellContentType.Text:
- if (!string.IsNullOrEmpty(data.Text))
- {
- var txt = m_Assembler.GetText("cell_text", cellNS);
- txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- txt.Text.text = data.Text;
- // 运行时样式由 StyleBoundElement 被动更新,此处仅设置文本内容
- }
- break;
- case CellContentType.Image:
- if (data.Image != null)
- {
- Sprite sprite = GetOrCreateSprite(data.Image);
- var img = m_Assembler.GetImage("cell_image", cellNS);
- img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- img.Image.sprite = sprite;
- }
- break;
- case CellContentType.Prefab:
- if (data.Prefab != null)
- {
- GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c);
- RectTransform prt = instance.GetComponent();
- prt.anchoredPosition = new Vector2(cellX, cellY);
- prt.sizeDelta = new Vector2(cellW, cellH);
- m_ActivePrefabMap[(r, c)] = instance;
- data.OnPrefabAcquire?.Invoke(instance);
- }
- break;
- }
- }
- }
- }
-
- ///
- /// 轻渲染——与 RenderCells 相同但跳过网格线 Image 绘制。
- /// 用于滚动场景,网格线由 RebuildBorders 的 UILineRendererV2 统一处理。
- ///
- private void RenderCellsLight(int sr, int er, int sc, int ec)
- {
- string ns = m_DefaultStyleNamespace;
-
-#if UNITY_EDITOR
- if (!Application.isPlaying)
- {
- Color selectionColor = GetCellSelectionBgColor(ns);
- Color multiSelectColor = new Color(selectionColor.r, selectionColor.g, selectionColor.b,
- selectionColor.a * Config.MultiSelectAlphaFactor);
- Color focusColor = new Color(selectionColor.r + Config.FocusRAdd,
- selectionColor.g + Config.FocusGAdd, selectionColor.b + Config.FocusBAdd,
- selectionColor.a * Config.FocusAlphaMul);
-
- for (int r = sr; r < er && r < m_RowHeights.Length; r++)
- {
- for (int c = sc; c < ec && c < m_ColWidths.Length; c++)
- {
- if (TableData.IsCellMerged(r, c)) continue;
-
- float cellX = GetColLeftX(c) + m_ContentOffset;
- float cellY = -GetRowTopY(r) - m_ContentOffset;
- float cellW = m_ColWidths[c];
- float cellH = m_RowHeights[r];
-
- if (TableData.IsMergeSource(r, c, out int mergeRS, out int mergeCS))
- {
- for (int i = 1; i < mergeCS && c + i < m_ColWidths.Length; i++)
- cellW += m_ColWidths[c + i];
- for (int i = 1; i < mergeRS && r + i < m_RowHeights.Length; i++)
- cellH += m_RowHeights[r + i];
- }
-
- XTableCellData data = TableData.GetCell(r, c);
- string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
- ? data.StyleNamespace : ns;
-
- // 选择高亮(无网格线)
- if (IsInSelectionRange(r, c))
- {
- 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 == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
- ? focusColor : multiSelectColor;
- }
- else if (r == SelectionHandle.StartRow && c == 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;
-
- switch (data.ContentType)
- {
- case CellContentType.Text:
- if (!string.IsNullOrEmpty(data.Text))
- {
- var txt = m_Assembler.GetText("cell_text", cellNS);
- txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- txt.Text.text = data.Text;
- txt.Text.color = GetCellFgColor(cellNS);
- txt.Text.fontSize = GetCellFontSize(cellNS);
- txt.Text.alignment = TextAlignmentOptions.Midline;
- txt.Text.font = GetCellFontAsset(cellNS) ?? txt.Text.font;
- }
- break;
- case CellContentType.Image:
- if (data.Image != null)
- {
- Sprite sprite = GetOrCreateSprite(data.Image);
- var img = m_Assembler.GetImage("cell_image", cellNS);
- img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- img.Image.sprite = sprite;
- }
- break;
- case CellContentType.Prefab:
- if (data.Prefab != null)
- {
- GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c);
- RectTransform prt = instance.GetComponent();
- prt.anchoredPosition = new Vector2(cellX, cellY);
- prt.sizeDelta = new Vector2(cellW, cellH);
- m_ActivePrefabMap[(r, c)] = instance;
- data.OnPrefabAcquire?.Invoke(instance);
- }
- break;
- }
- }
- }
- return;
- }
-#endif
-
- // 运行时模式(无网格线)
- Color rtSelectionColor = Config.RuntimeSelectionColor;
- Color rtMultiSelectColor = Config.RuntimeMultiSelectColor;
- Color rtFocusColor = Config.RuntimeFocusColor;
-
- for (int r = sr; r < er && r < m_RowHeights.Length; r++)
- {
- for (int c = sc; c < ec && c < m_ColWidths.Length; c++)
- {
- if (TableData.IsCellMerged(r, c)) continue;
-
- float cellX = GetColLeftX(c) + m_ContentOffset;
- float cellY = -GetRowTopY(r) - m_ContentOffset;
- float cellW = m_ColWidths[c];
- float cellH = m_RowHeights[r];
-
- if (TableData.IsMergeSource(r, c, out int mergeRS, out int mergeCS))
- {
- for (int i = 1; i < mergeCS && c + i < m_ColWidths.Length; i++)
- cellW += m_ColWidths[c + i];
- for (int i = 1; i < mergeRS && r + i < m_RowHeights.Length; i++)
- cellH += m_RowHeights[r + i];
- }
-
- XTableCellData data = TableData.GetCell(r, c);
- string cellNS = (data != null && !string.IsNullOrEmpty(data.StyleNamespace))
- ? data.StyleNamespace : ns;
-
- if (IsInSelectionRange(r, c))
- {
- 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 == SelectionHandle.StartRow && c == SelectionHandle.StartCol)
- ? rtFocusColor : rtMultiSelectColor;
- }
- else if (r == SelectionHandle.StartRow && c == 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;
-
- switch (data.ContentType)
- {
- case CellContentType.Text:
- if (!string.IsNullOrEmpty(data.Text))
- {
- var txt = m_Assembler.GetText("cell_text", cellNS);
- txt.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- txt.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- txt.Text.text = data.Text;
- }
- break;
- case CellContentType.Image:
- if (data.Image != null)
- {
- Sprite sprite = GetOrCreateSprite(data.Image);
- var img = m_Assembler.GetImage("cell_image", cellNS);
- img.Rect.anchoredPosition = new Vector2(cellX + CellPadding, cellY);
- img.Rect.sizeDelta = new Vector2(cellW - CellPadding2, cellH);
- img.Image.sprite = sprite;
- }
- break;
- case CellContentType.Prefab:
- if (data.Prefab != null)
- {
- GameObject instance = m_PrefabPool.Acquire(data.Prefab, r, c);
- RectTransform prt = instance.GetComponent();
- prt.anchoredPosition = new Vector2(cellX, cellY);
- prt.sizeDelta = new Vector2(cellW, cellH);
- m_ActivePrefabMap[(r, c)] = instance;
- data.OnPrefabAcquire?.Invoke(instance);
- }
- break;
- }
- }
- }
- }
-
- #endregion
-
- #region 坐标计算
-
- /// 判断指定单元格是否与当前可见矩形有重叠(用于 RenderQuadrants 裁剪)
- private bool IsCellVisible(int row, int col)
- {
- float cellTop = GetRowTopY(row);
- float cellBottom = cellTop + m_RowHeights[row];
- float cellLeft = GetColLeftX(col);
- float cellRight = cellLeft + m_ColWidths[col];
-
- return cellBottom > m_VisibleRect.y && cellTop < m_VisibleRect.yMax &&
- cellRight > m_VisibleRect.x && cellLeft < m_VisibleRect.xMax;
- }
-
- private float GetRowTopY(int row)
- {
- float y = 0;
- 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;
- }
-
- /// 将 Texture2D 转为 Sprite(带缓存),用于 Image 类型单元格渲染
- private Sprite GetOrCreateSprite(Texture2D texture)
- {
- if (texture == null) return null;
-
- if (m_SpriteCache == null)
- m_SpriteCache = new Dictionary();
-
- if (!m_SpriteCache.TryGetValue(texture, out Sprite sprite))
- {
- sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f));
- m_SpriteCache[texture] = sprite;
- }
- return sprite;
- }
-
- private void RecalculateTotalSize()
- {
- m_TotalWidth = 0;
- if (m_ColWidths != null)
- foreach (float w in m_ColWidths)
- m_TotalWidth += w;
-
- m_TotalHeight = 0;
- if (m_RowHeights != null)
- foreach (float h in m_RowHeights)
- m_TotalHeight += h;
-
- m_ContentOffset = GetCellBorderWidth(m_DefaultStyleNamespace) * 0.5f;
- float pad = m_ContentOffset * 2f;
-
- if (m_ScrollRect != null && m_ScrollRect.content != null)
- m_ScrollRect.content.sizeDelta = new Vector2(
- m_TotalWidth + pad, m_TotalHeight + pad);
- }
-
- private void UpdateVisibleRect()
- {
- m_VisibleRect = new Rect(0, 0, m_TotalWidth, m_TotalHeight);
-
- if (m_ScrollRect != null && m_ScrollRect.viewport != null)
- {
- RectTransform vt = m_ScrollRect.viewport;
- RectTransform ct = m_ScrollRect.content;
- if (vt != null && ct != null)
- {
- float sx = Mathf.Max(0, -ct.anchoredPosition.x);
- float sy = Mathf.Max(0, -ct.anchoredPosition.y);
- m_VisibleRect = new Rect(sx, sy, vt.rect.width, vt.rect.height);
- }
- }
- }
-
- private void CalcVisibleRange(out int sr, out int er, out int sc, out int ec)
- {
- sr = 0;
- er = 0;
- sc = 0;
- ec = 0;
-
- float accY = 0;
- for (int r = 0; r < m_RowHeights.Length; r++)
- {
- float rowBottom = accY + m_RowHeights[r];
- if (rowBottom > m_VisibleRect.y && accY < m_VisibleRect.yMax)
- {
- if (er == 0 && sr == 0) sr = r;
- er = r + 1;
- }
-
- accY = rowBottom;
- if (accY > m_VisibleRect.yMax) break;
- }
-
- float accX = 0;
- for (int c = 0; c < m_ColWidths.Length; c++)
- {
- float colRight = accX + m_ColWidths[c];
- if (colRight > m_VisibleRect.x && accX < m_VisibleRect.xMax)
- {
- if (ec == 0 && sc == 0) sc = c;
- ec = c + 1;
- }
-
- accX = colRight;
- if (accX > m_VisibleRect.xMax) break;
- }
-
- if (sr < 0) sr = 0;
- if (sc < 0) sc = 0;
- }
-
- #endregion
-
- #region 样式
-
- private Color GetStyleColor(string ns, string path, Color fallback)
- {
- StyleValue val = StyleManager.GetValue(ns, path);
- return val != null ? (Color)val : fallback;
- }
-
- private int GetStyleInt(string ns, string path, int fallback)
- {
- StyleValue val = StyleManager.GetValue(ns, path);
- return val != null ? (int)val : fallback;
- }
-
- private float GetStyleFloat(string ns, string path, float fallback)
- {
- StyleValue val = StyleManager.GetValue(ns, path);
- return val != null ? (float)val : fallback;
- }
-
- private TMP_FontAsset GetStyleFontAsset(string ns, string path, TMP_FontAsset fallback = null)
- {
- StyleValue val = StyleManager.GetValue(ns, path);
- if (val == null) return fallback;
- try { return val.GetValueAs(); }
- catch { return fallback; }
- }
-
- private void EnsureDefaultStyleNamespace()
- {
- // 检查命名空间是否已存在,避免 HasStyle → GetValue 对不存在的命名空间报错
- bool nsExists = StyleManager.GetAvailableNamespaces()?.Contains(m_DefaultStyleNamespace) == true;
-
- // 命名空间已存在且样式已加载(来自 .xsss),无需添加回退值
- if (nsExists && StyleManager.HasStyle(m_DefaultStyleNamespace, STYLE_CELL_BG_COLOR))
- return;
-
- // 回退默认值匹配 DefaultTableStyles.xsss
- // AddDynamicStyle 自动创建命名空间(如果尚不存在)
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BG_COLOR, DEFAULT_BG_COLOR);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_FG_COLOR, DEFAULT_FG_COLOR);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_TOP_COLOR, DEFAULT_BORDER_COLOR);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_TOP_WIDTH, DEFAULT_BORDER_WIDTH);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_BOTTOM_COLOR, DEFAULT_BORDER_COLOR);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_BOTTOM_WIDTH, DEFAULT_BORDER_WIDTH);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_LEFT_COLOR, DEFAULT_BORDER_COLOR);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_LEFT_WIDTH, DEFAULT_BORDER_WIDTH);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_RIGHT_COLOR, DEFAULT_BORDER_COLOR);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_BORDER_RIGHT_WIDTH, DEFAULT_BORDER_WIDTH);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_SELECTION_BG_COLOR, DEFAULT_SELECTION_COLOR);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_FONT_SIZE, DEFAULT_FONT_SIZE);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_FONT_ALIGNMENT, DEFAULT_FONT_ALIGNMENT);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_FONT_RICH_TEXT, DEFAULT_FONT_RICH_TEXT);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_DEFAULT_HEIGHT, DEFAULT_CELL_HEIGHT);
- StyleManager.AddDynamicStyle(m_DefaultStyleNamespace, STYLE_CELL_DEFAULT_WIDTH, DEFAULT_CELL_WIDTH);
- StyleManager.ForceRefresh();
- }
-
- #endregion
-
- #region 其他
+ #region 脏标记与滚动回调
public void MarkDirty(TableDirtyType type)
{
@@ -2016,9 +379,8 @@ namespace XericUI.XTable.Rendering.Component
try
{
- // 滚动时仅做轻刷新:更新可见矩形 + 渲染可见单元格内容,
+ // 滚动时仅做轻刷新(RefreshViewLight 内部会调用 UpdateVisibleRect),
// 不重建背景、边框、不重置 content.sizeDelta、不做孤儿清理。
- UpdateVisibleRect();
RefreshViewLight();
}
finally
@@ -2029,337 +391,6 @@ namespace XericUI.XTable.Rendering.Component
#endregion
- #region 四叉树
-
- /// 强制重建四叉树(外部调用入口,适合编辑器调试或手动刷新)
- public void ForceRebuildQuadtree()
- {
- if (m_Quadtree == null)
- m_Quadtree = new XTableQuadtree();
- MaintainQuadtree();
- }
-
- /// 从当前 XTableData 收集单元格信息并重建四叉树
- 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();
- 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, Config.QuadtreeMaxCellsPerNode);
- }
-
- #endregion
-
- #region 边框渲染
-
- ///
- /// 获取或创建边框渲染器 GameObject 和 组件。
- ///
- private UILineRendererV2 GetOrCreateBorderRenderer()
- {
- if (m_BorderRenderer != null)
- return m_BorderRenderer;
-
- Transform contentT = ContentTransform;
- // 查找已有对象
- var existing = contentT.Find("_TableBorders");
- if (existing != null)
- {
- m_BorderRenderer = existing.GetComponent();
- if (m_BorderRenderer != null) return m_BorderRenderer;
- }
-
- var go = new GameObject("_TableBorders",
- typeof(RectTransform), typeof(CanvasRenderer), typeof(UILineRendererV2));
- go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
- go.transform.SetParent(contentT, false);
-
- RectTransform rt = go.GetComponent();
- rt.anchorMin = new Vector2(0, 1);
- rt.anchorMax = new Vector2(0, 1);
- rt.pivot = new Vector2(0, 1);
- float pad = m_ContentOffset * 2f;
- rt.anchoredPosition = new Vector2(m_ContentOffset, -m_ContentOffset);
- rt.sizeDelta = new Vector2(m_TotalWidth + pad, m_TotalHeight + pad);
-
- m_BorderRenderer = go.GetComponent();
- return m_BorderRenderer;
- }
-
- /// 销毁边框渲染器
- private void DestroyBorderRenderer()
- {
- if (m_BorderRenderer != null)
- {
- if (Application.isPlaying)
- Destroy(m_BorderRenderer.gameObject);
- else
- DestroyImmediate(m_BorderRenderer.gameObject);
- m_BorderRenderer = null;
- }
- }
-
- ///
- /// 重建表格的全部边框线段。
- /// 第一阶段:从 OuterBorderNs 绘制 -1 行(顶部外框)和 -1 列(左侧外框)。
- /// 第二阶段:遍历非合并覆盖单元格,从边框缓存读取样式命名空间,按 per-cell 样式绘制右侧和底部边框。
- /// 合并源在最外沿绘制,内部边框自动跳过。
- ///
- private void RebuildBorders()
- {
- if (m_RowHeights == null || m_ColWidths == null) return;
- if (m_RowHeights.Length == 0 || m_ColWidths.Length == 0) return;
-
- var renderer = GetOrCreateBorderRenderer();
- renderer.ClearAll();
-
- var borderRt = renderer.GetComponent();
- borderRt.sizeDelta = new Vector2(m_TotalWidth, m_TotalHeight);
-
-#if UNITY_EDITOR
- Color borderColor;
- float borderWidth;
- if (!Application.isPlaying)
- {
- borderColor = GetCellBorderColor(m_DefaultStyleNamespace);
- borderWidth = GetCellBorderWidth(m_DefaultStyleNamespace);
- }
- else
- {
- borderColor = Config.RuntimeBorderColor;
- borderWidth = Config.RuntimeBorderWidth;
- }
-#else
- Color borderColor = Config.RuntimeBorderColor;
- float borderWidth = Config.RuntimeBorderWidth;
-#endif
-
- int rows = m_RowHeights.Length;
- int cols = m_ColWidths.Length;
-
- // ════════════════════════════════════════════
- // 阶段 1:-1 行(顶部外框)和 -1 列(左侧外框)
- // ════════════════════════════════════════════
-
- // 顶部外框:从 col=0 到 col=cols-1,每列一段
- float topY = 0;
- for (int c = 0; c < cols; c++)
- {
- string ns = m_TableData?.GetOuterBorderNs(-1, c);
- if (string.IsNullOrEmpty(ns)) ns = m_DefaultStyleNamespace;
- Color color = GetCellBorderColor(ns);
- float width = GetCellBorderWidth(ns);
- float leftX = GetColLeftX(c);
- renderer.DrawLine(
- new Vector2(leftX, topY),
- new Vector2(leftX + m_ColWidths[c], topY),
- color, width, UVMode.ByDistance);
- }
-
- // 左侧外框:从 row=0 到 row=rows-1,每行一段
- float xEdge = 0;
- for (int r = 0; r < rows; r++)
- {
- string ns = m_TableData?.GetOuterBorderNs(r, -1);
- if (string.IsNullOrEmpty(ns)) ns = m_DefaultStyleNamespace;
- Color color = GetCellBorderColor(ns);
- float width = GetCellBorderWidth(ns);
- float rowTopY = -GetRowTopY(r);
- renderer.DrawLine(
- new Vector2(xEdge, rowTopY),
- new Vector2(xEdge, rowTopY - m_RowHeights[r]),
- color, width, UVMode.ByDistance);
- }
-
- // ════════════════════════════════════════════
- // 阶段 2:每个单元格的右侧和底部边框
- // ════════════════════════════════════════════
-
- for (int r = 0; r < rows; r++)
- {
- for (int c = 0; c < cols; c++)
- {
- // 跳过被合并覆盖的单元格
- 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);
- }
- }
- }
- }
-
- #endregion
-
- #region Viewport 冻结渲染
-
- /// 创建或重新创建 Viewport 层冻结渲染器
- private void CreateViewportRenderer()
- {
- if (m_ScrollRect == null || m_ScrollRect.viewport == null) return;
- if (m_FreezeRowCount <= 0 && m_FreezeColCount <= 0) return;
-
- // 清理旧渲染器
- m_ViewportRenderer?.DestroyAll();
-
- Transform viewportT = m_ScrollRect.viewport;
- m_ViewportRenderer = new TableRenderer(viewportT, m_DefaultStyleNamespace, OnPoolStyleChanged);
-
- // 配置委托
- m_ViewportRenderer.GetStyleColor = (ns, path, fb) => GetStyleColor(ns, path, fb);
- m_ViewportRenderer.GetStyleFloat = (ns, path, fb) => GetStyleFloat(ns, path, fb);
- m_ViewportRenderer.GetStyleInt = (ns, path, fb) => GetStyleInt(ns, path, fb);
- m_ViewportRenderer.GetStyleFontAsset = (ns, path) => GetStyleFontAsset(ns, path);
- m_ViewportRenderer.GetRowHeights = () => m_RowHeights;
- m_ViewportRenderer.GetColWidths = () => m_ColWidths;
- m_ViewportRenderer.GetTableData = () => m_TableData;
- m_ViewportRenderer.GetDefaultStyleNamespace = () => m_DefaultStyleNamespace;
- m_ViewportRenderer.IsInSelectionRange = (r, c) => IsInSelectionRange(r, c);
- m_ViewportRenderer.GetSelectedRow = () => SelectionHandle.StartRow;
- m_ViewportRenderer.GetSelectedCol = () => SelectionHandle.StartCol;
-
- // 仅渲染冻结范围
- m_ViewportRenderer.StartRow = 0;
- m_ViewportRenderer.EndRow = m_FreezeRowCount;
- m_ViewportRenderer.StartCol = 0;
- m_ViewportRenderer.EndCol = m_FreezeColCount;
-
- // 共享预制体对象池
- m_ViewportRenderer.PrefabPool = m_PrefabPool;
- m_ViewportRenderer.ActivePrefabMap = m_ActivePrefabMap;
- m_ViewportRenderer.CellPadding = Config.CellHorizontalPadding;
- m_ViewportRenderer.Config = Config;
- }
-
- /// 渲染 Viewport 层冻结单元格,随滚动偏移调整位置
- private void RenderFrozenCells()
- {
- if (m_FreezeRowCount <= 0 && m_FreezeColCount <= 0)
- {
- // 无冻结需求时清理渲染器
- if (m_ViewportRenderer != null)
- {
- m_ViewportRenderer.DestroyAll();
- m_ViewportRenderer = null;
- }
- return;
- }
-
- // 检测冻结范围变更,重建渲染器
- if (m_ViewportRenderer == null ||
- m_ViewportRenderer.EndRow != m_FreezeRowCount ||
- m_ViewportRenderer.EndCol != m_FreezeColCount)
- CreateViewportRenderer();
-
- // 计算冻结区域尺寸
- float freezeW = 0, freezeH = 0;
- for (int c = 0; c < m_FreezeColCount && c < m_ColWidths.Length; c++)
- freezeW += m_ColWidths[c];
- for (int r = 0; r < m_FreezeRowCount && r < m_RowHeights.Length; r++)
- freezeH += m_RowHeights[r];
-
- // 创建/更新背景
- m_ViewportRenderer.CreateBackground(freezeW, freezeH, m_DefaultStyleNamespace);
-
- // 获取滚动偏移
- Vector2 scrollOffset = Vector2.zero;
- if (m_ScrollRect != null && m_ScrollRect.content != null)
- {
- scrollOffset.x = Mathf.Max(0, -m_ScrollRect.content.anchoredPosition.x);
- scrollOffset.y = Mathf.Max(0, -m_ScrollRect.content.anchoredPosition.y);
- }
-
- // 调整背景位置(随滚动偏移移动,保持在视口可见范围内)
- if (m_ViewportRenderer.BackgroundGo != null)
- {
- var bgRt = m_ViewportRenderer.BackgroundGo.GetComponent();
- bgRt.anchoredPosition = new Vector2(scrollOffset.x, -scrollOffset.y);
- }
-
- // 回收并重新渲染冻结单元格
- m_ViewportRenderer.ReturnAll();
- m_ViewportRenderer.RenderCells(
- 0, m_FreezeRowCount,
- 0, m_FreezeColCount,
- !Application.isPlaying);
-
- // 冻结单元格需要跟随滚动偏移(相对于 Viewport 定位)
- if (m_ViewportRenderer.Assembler != null)
- {
- // 调整所有活跃的池对象的 anchoredPosition 以抵消滚动
- // 这通过重新定位背景实现,单元格相对于背景 (0,0) 定位,所以随背景一起移动
- }
-
- // 背景始终可见
- m_ViewportRenderer.BackgroundGo?.SetActive(true);
- }
-
- #endregion
-
#region IPointerClickHandler
public void OnPointerClick(PointerEventData eventData)
@@ -2410,4 +441,4 @@ namespace XericUI.XTable.Rendering.Component
#endregion
}
-}
\ No newline at end of file
+}
diff --git a/Runtime/XTable/Rendering/Elements/CellAssembler.cs b/Runtime/XTable/Rendering/Elements/CellAssembler.cs
index a9da1d5..6faee5c 100644
--- a/Runtime/XTable/Rendering/Elements/CellAssembler.cs
+++ b/Runtime/XTable/Rendering/Elements/CellAssembler.cs
@@ -195,6 +195,22 @@ namespace XericUI.XTable.Rendering.Elements
m_ActiveTexts.Clear();
}
+ /// 归还单个 Image 池对象(从活跃列表移除并推入休眠栈)
+ public void ReleaseImage(PooledImage item)
+ {
+ if (item == null) return;
+ m_ActiveImages.Remove(item);
+ ReturnImage(item);
+ }
+
+ /// 归还单个 Text 池对象(从活跃列表移除并推入休眠栈)
+ public void ReleaseText(PooledText item)
+ {
+ if (item == null) return;
+ m_ActiveTexts.Remove(item);
+ ReturnText(item);
+ }
+
private void ReturnImage(PooledImage item)
{
item.Style?.Unbind();