修复表格框选可能随机多选,补充表格拖拽多选,视图自动偏移的功能
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user