From 182a801165dec7d3a5cabb764af15e828463e740 Mon Sep 17 00:00:00 2001
From: lrc <571244399@qq.com>
Date: Fri, 26 Jun 2026 23:40:18 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=A1=A8=E6=A0=BC=E7=9A=84?=
=?UTF-8?q?=E5=AE=9A=E4=BD=8D=E6=96=87=E6=9C=AC=EF=BC=8C=E6=AF=94=E5=A6=82?=
=?UTF-8?q?A0:B1=E8=BF=99=E6=A0=B7=E7=9A=84=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../XTable/XericUIActionTableSceneEditor.cs | 244 ++++++++++++++----
.../Rendering/Component/XericUIActionTable.cs | 41 +++
2 files changed, 240 insertions(+), 45 deletions(-)
diff --git a/Editor/XTable/XericUIActionTableSceneEditor.cs b/Editor/XTable/XericUIActionTableSceneEditor.cs
index c16eac4..fb67e07 100644
--- a/Editor/XTable/XericUIActionTableSceneEditor.cs
+++ b/Editor/XTable/XericUIActionTableSceneEditor.cs
@@ -2,14 +2,15 @@ using UnityEditor;
using UnityEngine;
+using XericUI.XTable.Mapping;
using XericUI.XTable.Rendering.Component;
namespace XericUIEditor.XTable
{
///
/// Scene View 表格编辑覆盖层——
- /// 左侧行索引、顶部列索引、底部输入框。
- /// 点击行列交叉区域选中单元格,底部输入框编辑文本。
+ /// 左侧行索引、顶部列索引(A..Z)、底部输入框。
+ /// 点击行列交叉区域选中单元格,底部输入框编辑文本,方向键导航,Shift+方向键多选。
/// 不创建任何场景对象,通过 Handles.BeginGUI/EndGUI 绘制。
///
[InitializeOnLoad]
@@ -22,8 +23,11 @@ namespace XericUIEditor.XTable
private static readonly Color s_LabelBg = new Color(0.12f, 0.12f, 0.18f, 0.9f);
private static readonly Color s_SelectedLabel = new Color(0.2f, 0.5f, 0.9f, 0.6f);
+ private static readonly Color s_MultiSelectLabel = new Color(0.2f, 0.5f, 0.9f, 0.35f);
private static readonly Color s_LabelText = new Color(0.75f, 0.75f, 0.8f);
private static readonly Color s_InputBg = new Color(0.08f, 0.08f, 0.14f, 0.92f);
+ private static readonly Color s_SelectionColor = new Color(0.18f, 0.45f, 0.85f, 0.25f);
+ private static readonly Color s_MultiSelectRangeColor = new Color(0.18f, 0.45f, 0.85f, 0.12f);
private const float ROW_H = 22f;
private const float COL_W = 36f;
@@ -58,7 +62,6 @@ namespace XericUIEditor.XTable
GameObject sel = Selection.activeGameObject;
if (sel == null) return;
- // 不管选了表格祖上还是子孙,都能找到组件
XericUIActionTable table = sel.GetComponentInChildren();
if (table == null)
table = sel.GetComponentInParent();
@@ -73,12 +76,9 @@ namespace XericUIEditor.XTable
float[] cw = table.ColWidths;
if (rh == null || cw == null || rh.Length == 0 || cw.Length == 0) return;
- // GetWorldCorners 返回世界坐标,topleft = corners[1], bottomRight = corners[3]
Vector3[] corners = new Vector3[4];
tableRt.GetWorldCorners(corners);
- // 使用 HandleUtility 将世界坐标投影到 Scene View GUI 坐标系
- // 自动处理相机缩放、平移、旋转——无论何种 Canvas 渲染模式都正确
Vector2 guiTL = HandleUtility.WorldToGUIPoint(corners[1]);
Vector2 guiBR = HandleUtility.WorldToGUIPoint(corners[3]);
@@ -94,7 +94,6 @@ namespace XericUIEditor.XTable
float xScale = tableW / totalW;
float yScale = tableH / totalH;
- // 表格区域(GUI 坐标,Y 向下)
Rect tableRect = new Rect(guiTL.x, guiTL.y, tableW, tableH);
Handles.BeginGUI();
@@ -110,6 +109,46 @@ namespace XericUIEditor.XTable
Handles.EndGUI();
}
+ #region 辅助
+
+ private static int IndexFromCoord(float coord, float[] sizes)
+ {
+ float acc = 0;
+ for (int i = 0; i < sizes.Length; i++)
+ {
+ if (coord >= acc && coord < acc + sizes[i]) return i;
+ acc += sizes[i];
+ }
+ return -1;
+ }
+
+ /// 判断某行是否在多选范围内
+ private static bool IsRowInMultiSelect(XericUIActionTable table, int row)
+ {
+ if (!table.HasMultiSelection) return false;
+ int minR = Mathf.Min(table.SelectStartRow, table.SelectEndRow);
+ int maxR = Mathf.Max(table.SelectStartRow, table.SelectEndRow);
+ return row >= minR && row <= maxR;
+ }
+
+ /// 判断某列是否在多选范围内
+ private static bool IsColInMultiSelect(XericUIActionTable table, int col)
+ {
+ if (!table.HasMultiSelection) return false;
+ int minC = Mathf.Min(table.SelectStartCol, table.SelectEndCol);
+ int maxC = Mathf.Max(table.SelectStartCol, table.SelectEndCol);
+ return col >= minC && col <= maxC;
+ }
+
+ /// 更新 s_EditText 为当前焦点格的单元格内容
+ private static void UpdateEditText(XericUIActionTable table, int row, int col)
+ {
+ var data = table.GetCellData(row, col);
+ s_EditText = data != null ? data.Text ?? "" : "";
+ }
+
+ #endregion
+
#region 行标签
private static void DrawRowLabels(float[] rh, Rect tableRect,
@@ -134,8 +173,15 @@ namespace XericUIEditor.XTable
COL_W,
cellH);
- bool isSelected = (table.SelectedRow == r);
- EditorGUI.DrawRect(labelRect, isSelected ? s_SelectedLabel : s_LabelBg);
+ Color bgColor;
+ if (table.SelectedRow == r)
+ bgColor = s_SelectedLabel;
+ else if (IsRowInMultiSelect(table, r))
+ bgColor = s_MultiSelectLabel;
+ else
+ bgColor = s_LabelBg;
+
+ EditorGUI.DrawRect(labelRect, bgColor);
GUI.Label(labelRect, $"{r}", style);
if (Event.current.type == EventType.MouseDown &&
@@ -144,7 +190,18 @@ namespace XericUIEditor.XTable
{
int col = table.SelectedCol;
if (col < 0) col = 0;
- table.SelectCell(r, col);
+
+ if (Event.current.shift && table.SelectedRow >= 0)
+ {
+ if (!table.HasMultiSelection)
+ table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
+ table.ExtendMultiSelect(r, col);
+ }
+ else
+ {
+ table.SelectCell(r, col);
+ }
+
table.RefreshView();
UpdateEditText(table, r, col);
Event.current.Use();
@@ -181,9 +238,18 @@ namespace XericUIEditor.XTable
cellW,
ROW_H);
- bool isSelected = (table.SelectedCol == c);
- EditorGUI.DrawRect(labelRect, isSelected ? s_SelectedLabel : s_LabelBg);
- string text = cellW > 40 ? $"列{c}" : (cellW > 18 ? $"{c}" : "");
+ Color bgColor;
+ if (table.SelectedCol == c)
+ bgColor = s_SelectedLabel;
+ else if (IsColInMultiSelect(table, c))
+ bgColor = s_MultiSelectLabel;
+ else
+ bgColor = s_LabelBg;
+
+ EditorGUI.DrawRect(labelRect, bgColor);
+
+ string colLetter = XTableCoordinateFormat.ColumnIndexToLetter(c);
+ string text = cellW > 40 ? colLetter : (cellW > 18 ? colLetter : "");
GUI.Label(labelRect, text, style);
if (Event.current.type == EventType.MouseDown &&
@@ -192,7 +258,18 @@ namespace XericUIEditor.XTable
{
int row = table.SelectedRow;
if (row < 0) row = 0;
- table.SelectCell(row, c);
+
+ if (Event.current.shift && table.SelectedCol >= 0)
+ {
+ if (!table.HasMultiSelection)
+ table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
+ table.ExtendMultiSelect(row, c);
+ }
+ else
+ {
+ table.SelectCell(row, c);
+ }
+
table.RefreshView();
UpdateEditText(table, row, c);
Event.current.Use();
@@ -238,17 +315,50 @@ namespace XericUIEditor.XTable
int sc = table.SelectedCol;
if (sr < 0 || sc < 0 || sr >= rh.Length || sc >= cw.Length) return;
- float sx = tr.x;
- for (int c = 0; c < sc; c++) sx += cw[c] * xScale;
+ // 多选范围高亮
+ if (table.HasMultiSelection)
+ {
+ int minR = Mathf.Min(table.SelectStartRow, table.SelectEndRow);
+ int maxR = Mathf.Max(table.SelectStartRow, table.SelectEndRow);
+ int minC = Mathf.Min(table.SelectStartCol, table.SelectEndCol);
+ int maxC = Mathf.Max(table.SelectStartCol, table.SelectEndCol);
- float sy = tr.y;
- for (int r = 0; r < sr; r++) sy += rh[r] * yScale;
+ minR = Mathf.Clamp(minR, 0, rh.Length - 1);
+ maxR = Mathf.Clamp(maxR, 0, rh.Length - 1);
+ minC = Mathf.Clamp(minC, 0, cw.Length - 1);
+ maxC = Mathf.Clamp(maxC, 0, cw.Length - 1);
+ float msx = tr.x + SumBefore(cw, minC) * xScale;
+ float msy = tr.y + SumBefore(rh, minR) * yScale;
+ float msw = SumRange(cw, minC, maxC + 1) * xScale;
+ float msh = SumRange(rh, minR, maxR + 1) * yScale;
+
+ EditorGUI.DrawRect(new Rect(msx + 1, msy + 1, msw - 2, msh - 2),
+ s_MultiSelectRangeColor);
+ }
+
+ // 焦点格高亮(始终绘制)
+ float sx = tr.x + SumBefore(cw, sc) * xScale;
+ float sy = tr.y + SumBefore(rh, sr) * yScale;
float sw = cw[sc] * xScale;
float sh = rh[sr] * yScale;
EditorGUI.DrawRect(new Rect(sx + 1, sy + 1, sw - 2, sh - 2),
- new Color(0.18f, 0.45f, 0.85f, 0.25f));
+ s_SelectionColor);
+ }
+
+ private static float SumBefore(float[] arr, int count)
+ {
+ float sum = 0;
+ for (int i = 0; i < count && i < arr.Length; i++) sum += arr[i];
+ return sum;
+ }
+
+ private static float SumRange(float[] arr, int start, int end)
+ {
+ float sum = 0;
+ for (int i = start; i < end && i < arr.Length; i++) sum += arr[i];
+ return sum;
}
#endregion
@@ -257,7 +367,7 @@ namespace XericUIEditor.XTable
private static void DrawInputBar(XericUIActionTable table, float x, float y)
{
- Rect barRect = new Rect(x, y, 400, 24);
+ Rect barRect = new Rect(x, y, 500, 24);
EditorGUI.DrawRect(barRect, s_InputBg);
if (table.SelectedRow >= 0 && table.SelectedCol >= 0)
@@ -268,15 +378,31 @@ namespace XericUIEditor.XTable
normal = { textColor = s_LabelText },
alignment = TextAnchor.MiddleCenter
};
- GUI.Label(new Rect(barRect.x + 4, barRect.y, 50, barRect.height),
- $"({table.SelectedRow},{table.SelectedCol})", labelStyle);
+
+ // 位置坐标显示当前选中(焦点格)
+ string cellLabel = XTableCoordinateFormat.CellToText(
+ table.SelectedRow, table.SelectedCol);
+ GUI.Label(new Rect(barRect.x + 4, barRect.y, 55, barRect.height),
+ cellLabel, labelStyle);
+
+ var rangeStyle = new GUIStyle(GUI.skin.label)
+ {
+ fontSize = 10,
+ normal = { textColor = new Color(0.5f, 0.6f, 0.8f) },
+ alignment = TextAnchor.MiddleLeft
+ };
+
+ // 多选范围文本
+ string rangeText = table.GetSelectionText();
+ GUI.Label(new Rect(barRect.x + 62, barRect.y, 100, barRect.height),
+ rangeText, rangeStyle);
var inputStyle = new GUIStyle(GUI.skin.textField)
{
fontSize = 12,
margin = new RectOffset(2, 2, 2, 2)
};
- Rect inputRect = new Rect(barRect.x + 56, barRect.y + 2, 180, barRect.height - 4);
+ Rect inputRect = new Rect(barRect.x + 165, barRect.y + 2, 200, barRect.height - 4);
// 回车键提交
if (Event.current.isKey && Event.current.keyCode == KeyCode.Return
@@ -295,7 +421,7 @@ namespace XericUIEditor.XTable
margin = new RectOffset(2, 2, 2, 2),
padding = new RectOffset(4, 4, 2, 2)
};
- if (GUI.Button(new Rect(barRect.x + 242, barRect.y + 2, 50, barRect.height - 4),
+ if (GUI.Button(new Rect(barRect.x + 370, barRect.y + 2, 50, barRect.height - 4),
"应用", btnStyle))
{
GUI.FocusControl(null);
@@ -310,7 +436,7 @@ namespace XericUIEditor.XTable
normal = { textColor = new Color(0.5f, 0.5f, 0.5f) },
alignment = TextAnchor.MiddleCenter
};
- GUI.Label(barRect, "点击左侧行号或上方列号选中单元格", hintStyle);
+ GUI.Label(barRect, "点击左侧行号或上方列名选中单元格 | Shift+方向键多选", hintStyle);
}
}
@@ -322,12 +448,6 @@ namespace XericUIEditor.XTable
SceneView.RepaintAll();
}
- private static void UpdateEditText(XericUIActionTable table, int row, int col)
- {
- var data = table.GetCellData(row, col);
- s_EditText = data != null ? data.Text ?? "" : "";
- }
-
#endregion
#region 表格区域点击
@@ -347,7 +467,17 @@ namespace XericUIEditor.XTable
int row = IndexFromCoord(ry, rh);
if (row < 0 || col < 0) return;
- table.SelectCell(row, col);
+ if (e.shift && table.SelectedRow >= 0 && table.SelectedCol >= 0)
+ {
+ if (!table.HasMultiSelection)
+ table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
+ table.ExtendMultiSelect(row, col);
+ }
+ else
+ {
+ table.SelectCell(row, col);
+ }
+
table.RefreshView();
UpdateEditText(table, row, col);
@@ -368,10 +498,47 @@ namespace XericUIEditor.XTable
if (GUI.GetNameOfFocusedControl() == "XTableSceneEditInput")
return;
+ // Escape 清除选区
+ if (e.keyCode == KeyCode.Escape)
+ {
+ table.ClearSelection();
+ table.RefreshView();
+ s_EditText = "";
+ e.Use();
+ SceneView.RepaintAll();
+ return;
+ }
+
int row = table.SelectedRow;
int col = table.SelectedCol;
bool shift = e.shift;
+ // 无选中时,方向键自动选中第一个单元格
+ if (row < 0 || col < 0)
+ {
+ if (rh.Length == 0 || cw.Length == 0) return;
+ row = 0;
+ col = 0;
+
+ switch (e.keyCode)
+ {
+ case KeyCode.UpArrow:
+ case KeyCode.DownArrow:
+ case KeyCode.LeftArrow:
+ case KeyCode.RightArrow:
+ break;
+ default:
+ return;
+ }
+
+ table.SelectCell(row, col);
+ table.RefreshView();
+ UpdateEditText(table, row, col);
+ e.Use();
+ SceneView.RepaintAll();
+ return;
+ }
+
switch (e.keyCode)
{
case KeyCode.UpArrow:
@@ -392,7 +559,6 @@ namespace XericUIEditor.XTable
if (shift)
{
- // Shift: 扩展/开始多选
if (!table.HasMultiSelection && table.SelectedRow >= 0 && table.SelectedCol >= 0)
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
table.ExtendMultiSelect(row, col);
@@ -408,7 +574,6 @@ namespace XericUIEditor.XTable
SceneView.RepaintAll();
}
- /// 将行列约束到有效范围,若已到边界则返回 false(不移动)
private static bool TryClampToValid(int row, int col, float[] rh, float[] cw,
out int outRow, out int outCol)
{
@@ -424,16 +589,5 @@ namespace XericUIEditor.XTable
}
#endregion
-
- private static int IndexFromCoord(float coord, float[] sizes)
- {
- float acc = 0;
- for (int i = 0; i < sizes.Length; i++)
- {
- if (coord >= acc && coord < acc + sizes[i]) return i;
- acc += sizes[i];
- }
- return -1;
- }
}
}
diff --git a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
index 76fcdac..fa09922 100644
--- a/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
+++ b/Runtime/XTable/Rendering/Component/XericUIActionTable.cs
@@ -9,6 +9,7 @@ using UnityEngine.UI;
using XericUI.Core.Base;
using XericUI.XTable.Core;
+using XericUI.XTable.Mapping;
using XericUI.XTable.Rendering.Elements;
using XericLibrary.Runtime.SuperStyleSheet;
@@ -321,6 +322,46 @@ namespace XericUI.XTable.Rendering.Component
m_SelectEndCol = -1;
}
+ /// 通过文本选择一个单元格(如 "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;
+
+ ClearSelection();
+ m_SelectStartRow = sr;
+ m_SelectStartCol = sc;
+ m_SelectEndRow = er;
+ m_SelectEndCol = ec;
+ m_SelectedRow = sr;
+ m_SelectedCol = sc;
+ MarkDirty(TableDirtyType.SelectionChanged);
+ return true;
+ }
+
+ /// 获取当前选区的文本描述(无多选返回 "A0",有多选返回 "A0:B1")
+ public string GetSelectionText()
+ {
+ if (m_SelectedRow < 0 || m_SelectedCol < 0) return string.Empty;
+ if (HasMultiSelection)
+ {
+ return XTableCoordinateFormat.RangeToText(
+ m_SelectStartRow, m_SelectStartCol,
+ m_SelectEndRow, m_SelectEndCol);
+ }
+ return XTableCoordinateFormat.CellToText(m_SelectedRow, m_SelectedCol);
+ }
+
public void RefreshView()
{
if (m_Assembler == null) return;