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;