修复表格框选可能随机多选,补充表格拖拽多选,视图自动偏移的功能

This commit is contained in:
2026-07-29 11:27:03 +08:00
parent 5c51d35d8e
commit 2fb8af52a6
9 changed files with 361 additions and 134 deletions
@@ -14,7 +14,6 @@ namespace XericUI.XTable.Rendering.Component
{
public CellAssembler.PooledText Text;
public CellAssembler.PooledImage Image;
public CellAssembler.PooledImage Selection;
}
private readonly Transform m_Parent;
@@ -41,9 +40,6 @@ namespace XericUI.XTable.Rendering.Component
public System.Func<float[]> GetColWidths;
public System.Func<XTableData> GetTableData;
public System.Func<string> GetDefaultStyleNamespace;
public System.Func<int, int, bool> IsInSelectionRange;
public System.Func<int> GetSelectedRow;
public System.Func<int> GetSelectedCol;
public Transform Parent => m_Parent;
public GameObject BackgroundGo => m_BackgroundGo;
@@ -144,7 +140,6 @@ namespace XericUI.XTable.Rendering.Component
{
Assembler.ReleaseText(tracked.Text);
Assembler.ReleaseImage(tracked.Image);
Assembler.ReleaseImage(tracked.Selection);
}
private void RenderCell(int row, int col, float[] rows, float[] cols, bool editorMode)
@@ -165,22 +160,6 @@ namespace XericUI.XTable.Rendering.Component
int cellId = row * cols.Length + col;
var tracked = new Tracked();
bool selected = IsInSelectionRange?.Invoke(row, col) == true ||
(row == GetSelectedRow?.Invoke() && col == GetSelectedCol?.Invoke());
if (selected)
{
var selection = Assembler.GetImage(cellId, "cell_selection", ns, m_Parent);
if (selection != null)
{
selection.Rect.anchoredPosition = new Vector2(x, y);
selection.Rect.sizeDelta = new Vector2(width, height);
selection.Image.color = editorMode
? GetStyleColor(ns, XericUIActionTable.STYLE_CELL_SELECTION_BG_COLOR, XericUIActionTable.SELECTION_COLOR_FALLBACK)
: Config.RuntimeSelectionColor;
tracked.Selection = selection;
}
}
var cell = data?.GetCell(row, col);
if (cell != null)
{
@@ -0,0 +1,120 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using XericLibrary.Runtime.MacroLibrary;
namespace XericUI.XTable.Rendering.Component
{
/// <summary>单个渲染层的固定框选输入与视觉覆盖层。</summary>
[RequireComponent(typeof(RectTransform), typeof(Image))]
public sealed class XTableSelectionOverlay : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler, IEndDragHandler
{
private XericUIActionTable m_Table;
private Image m_Fill;
private int m_RowStart;
private int m_RowEnd;
private int m_ColStart;
private int m_ColEnd;
private bool m_Dragging;
public void Configure(XericUIActionTable table, int rowStart, int rowEnd, int colStart, int colEnd)
{
m_Table = table;
m_RowStart = rowStart;
m_RowEnd = rowEnd;
m_ColStart = colStart;
m_ColEnd = colEnd;
Rect region = table.GetTableRect(rowStart, rowEnd, colStart, colEnd);
var rt = transform.RectTransform();
rt.anchorMin = new Vector2(0, 1);
rt.anchorMax = new Vector2(0, 1);
rt.pivot = new Vector2(0, 1);
rt.anchoredPosition = new Vector2(region.x, -region.y);
rt.sizeDelta = region.size;
EnsureVisuals();
RefreshVisual();
}
public void RefreshVisual()
{
EnsureVisuals();
if (m_Table == null || !m_Table.SelectionHandle.HasSelection || m_RowStart >= m_RowEnd || m_ColStart >= m_ColEnd)
{
m_Fill.gameObject.SetActive(false);
return;
}
int rs = Mathf.Max(m_RowStart, m_Table.SelectionHandle.MinRow);
int re = Mathf.Min(m_RowEnd - 1, m_Table.SelectionHandle.MaxRow);
int cs = Mathf.Max(m_ColStart, m_Table.SelectionHandle.MinCol);
int ce = Mathf.Min(m_ColEnd - 1, m_Table.SelectionHandle.MaxCol);
if (rs > re || cs > ce)
{
m_Fill.gameObject.SetActive(false);
return;
}
Rect rect = m_Table.GetTableRect(rs, re + 1, cs, ce + 1);
Rect region = m_Table.GetTableRect(m_RowStart, m_RowEnd, m_ColStart, m_ColEnd);
var fillRt = m_Fill.rectTransform;
fillRt.anchoredPosition = new Vector2(rect.x - region.x, region.y - rect.y);
fillRt.sizeDelta = rect.size;
m_Fill.color = m_Table.SelectionOverlayColor;
m_Fill.gameObject.SetActive(true);
}
public void OnPointerDown(PointerEventData eventData)
{
if (m_Table == null || !m_Table.TryGetCellFromScreenPoint(transform.RectTransform(), eventData.position, eventData.pressEventCamera, true, out int row, out int col))
return;
m_Dragging = true;
m_Table.BeginSelection(row, col);
eventData.Use();
}
public void OnDrag(PointerEventData eventData)
{
if (!m_Dragging || m_Table == null) return;
m_Table.UpdateSelectionDrag(transform.RectTransform(), eventData.position, eventData.pressEventCamera);
eventData.Use();
}
public void OnPointerUp(PointerEventData eventData)
{
EndDrag();
eventData.Use();
}
public void OnEndDrag(PointerEventData eventData) => EndDrag();
private void OnDisable() => EndDrag();
private void OnDestroy() => EndDrag();
private void EndDrag()
{
if (!m_Dragging) return;
m_Dragging = false;
m_Table?.EndSelection();
}
private void EnsureVisuals()
{
var input = GetComponent<Image>();
input.color = Color.clear;
input.raycastTarget = true;
if (m_Fill != null) return;
var go = new GameObject("_SelectionRect", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Outline));
go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
go.transform.SetParent(transform, false);
m_Fill = go.GetComponent<Image>();
m_Fill.raycastTarget = false;
var outline = go.GetComponent<Outline>();
outline.effectColor = new Color(0.2f, 0.55f, 1f, 1f);
outline.effectDistance = new Vector2(1f, -1f);
var rt = m_Fill.rectTransform;
rt.anchorMin = new Vector2(0, 1);
rt.anchorMax = new Vector2(0, 1);
rt.pivot = new Vector2(0, 1);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2746af0dac5753f4289a900b400c5c9e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -416,6 +416,7 @@ namespace XericUI.XTable.Rendering.Component
private void OnHandleSelectionChanged(XTableSelectionHandle handle)
{
MarkDirty(TableDirtyType.SelectionChanged);
RefreshSelectionOverlays();
}
/// <summary>裁剪选区到当前行列尺寸范围内</summary>
@@ -63,9 +63,6 @@ namespace XericUI.XTable.Rendering.Component
GetColWidths = () => m_ColWidths,
GetTableData = () => m_TableData,
GetDefaultStyleNamespace = () => m_DefaultStyleNamespace,
IsInSelectionRange = (r, c) => IsInSelectionRange(r, c),
GetSelectedRow = () => SelectionHandle.StartRow,
GetSelectedCol = () => SelectionHandle.StartCol,
};
return renderer;
}
@@ -79,11 +76,22 @@ namespace XericUI.XTable.Rendering.Component
m_FrozenColumnRenderer = null;
m_FrozenRowRenderer = null;
m_FrozenCornerRenderer = null;
DestroySelectionOverlay(ref m_FrozenColumnSelectionOverlay);
DestroySelectionOverlay(ref m_FrozenRowSelectionOverlay);
DestroySelectionOverlay(ref m_FrozenCornerSelectionOverlay);
DestroyFrozenContainer(ref m_FrozenColumnsContainer);
DestroyFrozenContainer(ref m_FrozenRowsContainer);
DestroyFrozenContainer(ref m_FrozenCornerContainer);
}
private static void DestroySelectionOverlay(ref XTableSelectionOverlay overlay)
{
if (overlay == null) return;
if (Application.isPlaying) Object.Destroy(overlay.gameObject);
else Object.DestroyImmediate(overlay.gameObject);
overlay = null;
}
private static void DestroyFrozenContainer(ref RectTransform container)
{
if (container == null) return;
@@ -108,11 +116,14 @@ namespace XericUI.XTable.Rendering.Component
if (freezeRows == 0 && freezeCols == 0)
{
DestroyFrozenRenderers();
if (m_RowHeights != null && m_ColWidths != null)
CreateOrUpdateSelectionOverlays(0, 0);
return;
}
if (m_FrozenColumnRenderer == null) CreateViewportRenderer();
if (m_FrozenColumnRenderer == null) return;
CreateOrUpdateSelectionOverlays(freezeRows, freezeCols);
CreateFrozenBackgrounds(freezeRows, freezeCols);
SyncFrozenContainers();
CalcVisibleRange(out int visibleStartRow, out int visibleEndRow, out int visibleStartCol, out int visibleEndCol);
@@ -145,6 +156,40 @@ namespace XericUI.XTable.Rendering.Component
m_FrozenCornerRenderer.CreateBackground(freezeWidth, freezeHeight, m_DefaultStyleNamespace);
}
private void CreateOrUpdateSelectionOverlays(int freezeRows, int freezeCols)
{
int rows = m_RowHeights.Length;
int cols = m_ColWidths.Length;
m_NormalSelectionOverlay = CreateOrUpdateSelectionOverlay(ContentTransform, m_NormalSelectionOverlay, freezeRows, rows, freezeCols, cols, "_NormalSelectionOverlay");
m_FrozenColumnSelectionOverlay = CreateOrUpdateSelectionOverlay(m_FrozenColumnsContainer, m_FrozenColumnSelectionOverlay, freezeRows, rows, 0, freezeCols, "_FrozenColumnSelectionOverlay");
m_FrozenRowSelectionOverlay = CreateOrUpdateSelectionOverlay(m_FrozenRowsContainer, m_FrozenRowSelectionOverlay, 0, freezeRows, freezeCols, cols, "_FrozenRowSelectionOverlay");
m_FrozenCornerSelectionOverlay = CreateOrUpdateSelectionOverlay(m_FrozenCornerContainer, m_FrozenCornerSelectionOverlay, 0, freezeRows, 0, freezeCols, "_FrozenCornerSelectionOverlay");
}
private XTableSelectionOverlay CreateOrUpdateSelectionOverlay(Transform parent, XTableSelectionOverlay overlay,
int rowStart, int rowEnd, int colStart, int colEnd, string name)
{
if (parent == null) return overlay;
if (overlay == null)
{
var go = new GameObject(name, typeof(RectTransform), typeof(CanvasRenderer), typeof(UnityEngine.UI.Image), typeof(XTableSelectionOverlay));
go.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
go.transform.SetParent(parent, false);
overlay = go.GetComponent<XTableSelectionOverlay>();
}
overlay.transform.SetAsLastSibling();
overlay.Configure(this, rowStart, rowEnd, colStart, colEnd);
return overlay;
}
private void RefreshSelectionOverlays()
{
m_NormalSelectionOverlay?.RefreshVisual();
m_FrozenColumnSelectionOverlay?.RefreshVisual();
m_FrozenRowSelectionOverlay?.RefreshVisual();
m_FrozenCornerSelectionOverlay?.RefreshVisual();
}
private void SyncFrozenContainers()
{
if (m_ScrollRect == null || m_ScrollRect.content == null) return;
@@ -29,7 +29,6 @@ namespace XericUI.XTable.Rendering.Component
{
public CellAssembler.PooledText CellText;
public CellAssembler.PooledImage CellImage;
public CellAssembler.PooledImage SelectBg;
}
#endregion
@@ -42,7 +41,6 @@ namespace XericUI.XTable.Rendering.Component
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_CellTracked.Remove(key);
}
@@ -53,7 +51,6 @@ namespace XericUI.XTable.Rendering.Component
var objs = kv.Value;
m_Assembler?.ReleaseText(objs.CellText);
m_Assembler?.ReleaseImage(objs.CellImage);
m_Assembler?.ReleaseImage(objs.SelectBg);
}
m_CellTracked.Clear();
m_LastRenderedCells.Clear();
@@ -150,9 +147,6 @@ namespace XericUI.XTable.Rendering.Component
int cellId = row * m_ColWidths.Length + col;
var objs = new CellTrackedObjs();
// ── 选择高亮 ──
ApplySelectionHighlight(cellId, row, col, cellX, cellY, cellW, cellH, cellNS, editorMode, ref objs);
// ── 单元格内容 ──
if (data != null)
{
@@ -174,44 +168,6 @@ namespace XericUI.XTable.Rendering.Component
m_LastRenderedCells.Add((row, col));
}
private void ApplySelectionHighlight(int cellId, 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(cellId, key, cellNS);
if (img == null) return;
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 RenderCellText(int cellId, XTableCellData data,
float cellX, float cellY, float cellW, float cellH,
string cellNS, bool editorMode, ref CellTrackedObjs objs)
@@ -368,27 +324,19 @@ namespace XericUI.XTable.Rendering.Component
#endif
string ns = m_DefaultStyleNamespace;
// 选区变更时:所有单元格的选择高亮需要重绘,但预制体不受影响
bool selectionChanged = m_DirtyFlag.Has(TableDirtyType.SelectionChanged);
if (selectionChanged)
{
ReleaseAllTrackedCells();
}
// 选区由固定 Overlay 绘制,选择变化无需回收或重绘单元格内容。
// 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);
}
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)
@@ -408,7 +356,7 @@ namespace XericUI.XTable.Rendering.Component
// 4. 渲染单元格:选区变更时全量重渲所有可见单元格,否则仅新进入的
foreach (var cell in newVisible)
{
if (selectionChanged || !m_LastRenderedCells.Contains(cell))
if (!m_LastRenderedCells.Contains(cell))
RenderCellContent(cell.Item1, cell.Item2, ns, editorMode);
}
@@ -0,0 +1,141 @@
using UnityEngine;
namespace XericUI.XTable.Rendering.Component
{
public partial class XericUIActionTable
{
internal float TotalWidth => m_TotalWidth;
internal float TotalHeight => m_TotalHeight;
internal Color SelectionOverlayColor => Application.isPlaying ? Config.RuntimeSelectionColor : GetCellSelectionBgColor(m_DefaultStyleNamespace);
internal void BeginSelection(int row, int col)
{
m_SelectionDragActive = true;
SelectionHandle.Select(row, col);
OnCellSelected?.Invoke(row, col);
}
internal void UpdateSelectionDrag(RectTransform source, Vector2 screenPoint, Camera eventCamera)
{
if (!m_SelectionDragActive) return;
m_LastSelectionPointer = screenPoint;
m_SelectionPointerCamera = eventCamera;
if (TryGetCellFromScreenPoint(source, screenPoint, eventCamera, true, out int row, out int col))
SelectionHandle.ExtendTo(row, col);
}
internal void EndSelection()
{
m_SelectionDragActive = false;
m_SelectionPointerCamera = null;
}
internal bool TryGetCellFromScreenPoint(RectTransform source, Vector2 screenPoint, Camera eventCamera,
bool clamp, out int row, out int col)
{
row = -1;
col = -1;
if (source == null || ContentTransform is not RectTransform contentRt) return false;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(source, screenPoint, eventCamera, out Vector2 sourceLocal))
return false;
Vector3 world = source.TransformPoint(sourceLocal);
Vector3 contentLocal = contentRt.InverseTransformPoint(world);
Vector2 point = new(
contentLocal.x - contentRt.rect.xMin - m_ContentOffset,
contentRt.rect.yMax - contentLocal.y - m_ContentOffset);
return TryGetCellFromTablePoint(point, clamp, out row, out col);
}
internal bool TryGetCellFromTablePoint(Vector2 point, bool clamp, out int row, out int col)
{
row = -1;
col = -1;
if (m_RowHeights == null || m_ColWidths == null || m_RowHeights.Length == 0 || m_ColWidths.Length == 0)
return false;
if (clamp)
{
point.x = Mathf.Clamp(point.x, 0f, Mathf.Max(0f, m_TotalWidth - .001f));
point.y = Mathf.Clamp(point.y, 0f, Mathf.Max(0f, m_TotalHeight - .001f));
}
else if (point.x < 0f || point.x >= m_TotalWidth || point.y < 0f || point.y >= m_TotalHeight)
return false;
float cursor = 0f;
for (col = 0; col < m_ColWidths.Length; col++)
{
cursor += m_ColWidths[col];
if (point.x < cursor) break;
}
cursor = 0f;
for (row = 0; row < m_RowHeights.Length; row++)
{
cursor += m_RowHeights[row];
if (point.y < cursor) break;
}
if (row >= m_RowHeights.Length || col >= m_ColWidths.Length) return false;
NormalizeMergeSource(ref row, ref col);
return true;
}
internal Rect GetTableRect(int rowStart, int rowEnd, int colStart, int colEnd)
{
return new Rect(GetColLeftX(colStart), GetRowTopY(rowStart),
GetColLeftX(colEnd) - GetColLeftX(colStart), GetRowTopY(rowEnd) - GetRowTopY(rowStart));
}
private void NormalizeMergeSource(ref int row, ref int col)
{
if (m_TableData == null || !m_TableData.IsCellMerged(row, col)) return;
for (int r = 0; r <= row; r++)
for (int c = 0; c <= col; c++)
if (m_TableData.IsMergeSource(r, c, out int rowSpan, out int colSpan)
&& row < r + rowSpan && col < c + colSpan)
{
row = r;
col = c;
return;
}
}
private void UpdateSelectionAutoScroll()
{
if (!m_SelectionDragActive || !m_EnableSelectionAutoScroll || m_ScrollRect?.viewport == null || m_ScrollRect.content == null)
return;
RectTransform viewport = m_ScrollRect.viewport;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(viewport, m_LastSelectionPointer, m_SelectionPointerCamera, out Vector2 local))
return;
Rect rect = viewport.rect;
if (!rect.Contains(local)) return;
float x = local.x - rect.xMin;
float y = rect.yMax - local.y;
float freezeWidth = GetColLeftX(Mathf.Clamp(m_FreezeColCount, 0, m_ColWidths.Length));
float freezeHeight = GetRowTopY(Mathf.Clamp(m_FreezeRowCount, 0, m_RowHeights.Length));
if (x < freezeWidth || y < freezeHeight) return;
float width = rect.width - freezeWidth;
float height = rect.height - freezeHeight;
float px = x - freezeWidth;
float py = y - freezeHeight;
float threshold = Mathf.Min(m_SelectionAutoScrollEdgeThreshold, Mathf.Min(width, height) * .5f);
float sx = GetAutoScrollSpeed(px, width, threshold);
float sy = GetAutoScrollSpeed(py, height, threshold);
if (Mathf.Approximately(sx, 0f) && Mathf.Approximately(sy, 0f)) return;
var content = m_ScrollRect.content;
Vector2 position = content.anchoredPosition;
position.x = Mathf.Clamp(position.x + sx * Time.unscaledDeltaTime, 0f, Mathf.Max(0f, m_TotalWidth - width));
position.y = Mathf.Clamp(position.y + sy * Time.unscaledDeltaTime, 0f, Mathf.Max(0f, m_TotalHeight - height));
content.anchoredPosition = position;
UpdateSelectionDrag(viewport, m_LastSelectionPointer, m_SelectionPointerCamera);
}
private float GetAutoScrollSpeed(float point, float length, float threshold)
{
float direction = point < threshold ? -1f : point > length - threshold ? 1f : 0f;
if (direction == 0f) return 0f;
float distance = direction < 0f ? point : length - point;
float strength = 1f - Mathf.Clamp01(distance / threshold);
return direction * m_SelectionAutoScrollMaxSpeed * Mathf.Pow(strength, m_SelectionAutoScrollAcceleration);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 625bc0906554ad34eac6487aa103746c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -23,7 +23,7 @@ namespace XericUI.XTable.Rendering.Component
/// </summary>
[ExecuteAlways]
[AddComponentMenu("Xeric UI Vessel/Table/Xeric UI Action Table", 60)]
public partial class XericUIActionTable : XericUIBehaviour, IPointerClickHandler
public partial class XericUIActionTable : XericUIBehaviour
{
#region 序列化字段
@@ -53,6 +53,14 @@ namespace XericUI.XTable.Rendering.Component
[Tooltip("在 Hierarchy 中显示动态生成的子对象(仅编辑器,不保存到场景)")]
private bool m_ChildrenVisible;
[SerializeField] private bool m_EnableSelectionAutoScroll = true;
[SerializeField, Min(1f), Tooltip("框选自动滚动的 Content 边缘阈值(像素)")]
private float m_SelectionAutoScrollEdgeThreshold = 32f;
[SerializeField, Min(1f), Tooltip("框选自动滚动的最大速度(像素/秒)")]
private float m_SelectionAutoScrollMaxSpeed = 600f;
[SerializeField, Min(1f), Tooltip("框选自动滚动的边缘加速度指数")]
private float m_SelectionAutoScrollAcceleration = 1.5f;
#endregion
#region 私有字段
@@ -69,6 +77,13 @@ namespace XericUI.XTable.Rendering.Component
[System.NonSerialized] private RectTransform m_FrozenCornerContainer;
[System.NonSerialized] private XTableSelectionHandle m_SelectionHandle;
[System.NonSerialized] private XTableSelectionOverlay m_NormalSelectionOverlay;
[System.NonSerialized] private XTableSelectionOverlay m_FrozenColumnSelectionOverlay;
[System.NonSerialized] private XTableSelectionOverlay m_FrozenRowSelectionOverlay;
[System.NonSerialized] private XTableSelectionOverlay m_FrozenCornerSelectionOverlay;
[System.NonSerialized] private bool m_SelectionDragActive;
[System.NonSerialized] private Vector2 m_LastSelectionPointer;
[System.NonSerialized] private Camera m_SelectionPointerCamera;
[System.NonSerialized] private XTableBackgroundRenderer m_BackgroundRenderer;
@@ -146,7 +161,10 @@ namespace XericUI.XTable.Rendering.Component
get
{
if (m_SelectionHandle == null)
{
m_SelectionHandle = new XTableSelectionHandle();
m_SelectionHandle.OnSelectionChanged += OnHandleSelectionChanged;
}
return m_SelectionHandle;
}
}
@@ -318,6 +336,7 @@ namespace XericUI.XTable.Rendering.Component
DestroyBackgroundRenderer();
DestroyFrozenRenderers();
DestroySelectionOverlay(ref m_NormalSelectionOverlay);
base.OnDestroy();
}
@@ -327,6 +346,7 @@ namespace XericUI.XTable.Rendering.Component
private void Update()
{
UpdateSelectionAutoScroll();
if (m_IsRebuilding || !m_DirtyFlag.IsDirty) return;
var flags = m_DirtyFlag.Flags;
@@ -400,54 +420,5 @@ namespace XericUI.XTable.Rendering.Component
#endregion
#region IPointerClickHandler
public void OnPointerClick(PointerEventData eventData)
{
// 直接用 ContentTransform 做坐标转换:单元格是 Content 的子对象,
// 其 anchoredPosition 就在 Content 局部空间中,无需手工换算 scroll 偏移。
RectTransform contentRt = ContentTransform as RectTransform;
if (contentRt == null) return;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
contentRt, eventData.position, eventData.pressEventCamera, out Vector2 localPoint))
return;
// 转换为左上角坐标系(Content 的 anchor/pivot 都是 (0,1))
float tableX = localPoint.x - contentRt.rect.xMin - m_ContentOffset;
float tableY = contentRt.rect.yMax - localPoint.y - m_ContentOffset;
int col = -1;
float ax = 0;
for (int c = 0; c < m_ColWidths.Length; c++)
{
if (tableX >= ax && tableX < ax + m_ColWidths[c])
{
col = c;
break;
}
ax += m_ColWidths[c];
}
int row = -1;
float ay = 0;
for (int r = 0; r < m_RowHeights.Length; r++)
{
if (tableY >= ay && tableY < ay + m_RowHeights[r])
{
row = r;
break;
}
ay += m_RowHeights[r];
}
if (row >= 0 && col >= 0)
{
SelectCell(row, col);
OnCellSelected?.Invoke(row, col);
}
}
#endregion
}
}