1016 lines
30 KiB
C#
1016 lines
30 KiB
C#
using UnityEditor;
|
|
|
|
using UnityEngine;
|
|
|
|
using XericLibrary.Runtime.SuperStyleSheet;
|
|
using XericUI.XTable.Mapping;
|
|
using XericUI.XTable.Rendering.Component;
|
|
|
|
namespace XericUIEditor.XTable
|
|
{
|
|
/// <summary>
|
|
/// Scene View 表格编辑覆盖层——
|
|
/// 左侧行索引、顶部列索引(A..Z)、底部输入框。
|
|
/// 点击行列交叉区域选中单元格,底部输入框编辑文本,方向键导航,Shift+方向键多选。
|
|
/// 不创建任何场景对象,通过 Handles.BeginGUI/EndGUI 绘制。
|
|
/// </summary>
|
|
[InitializeOnLoad]
|
|
public static class XericUIActionTableSceneEditor
|
|
{
|
|
// ─────────────────────────── 交互状态机 ───────────────────────────
|
|
private enum EditorState
|
|
{
|
|
TableNavigation, // 默认:键盘/鼠标在表格上导航与选择
|
|
SizeEditing, // 气泡输入框中编辑行高/列宽
|
|
CellEditing // 底部工具栏中编辑单元格文本内容
|
|
}
|
|
|
|
private static EditorState s_State;
|
|
private static string s_CurrentControlName; // 当前聚焦的控件名(每帧由焦点决定)
|
|
|
|
private static bool s_Enabled;
|
|
private const string ENABLED_KEY = "XericUI_TableSceneEditor_Enabled";
|
|
|
|
private static string s_EditText = "";
|
|
private static string s_SizeEditText = ""; // 气泡输入框当前编辑文本
|
|
private static int s_SizeEditTargetIndex = -1;
|
|
private static bool s_SizeEditIsColumn;
|
|
private static string s_LastFocusedControl = "";
|
|
|
|
// 拖拽框选状态
|
|
private static bool s_IsDragging;
|
|
private static int s_DragStartRow = -1;
|
|
private static int s_DragStartCol = -1;
|
|
private static Rect s_TableRect;
|
|
private static float s_XScale, s_YScale;
|
|
private static float[] s_RowHeights, s_ColWidths;
|
|
private static XericUIActionTable s_ActiveTable;
|
|
|
|
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 static readonly Color s_BubbleBg = new Color(0.08f, 0.1f, 0.2f, 0.95f);
|
|
private static readonly Color s_BubbleBorder = new Color(0.3f, 0.5f, 0.9f, 0.8f);
|
|
private static readonly Color s_ApplyBtnColor = new Color(0.15f, 0.4f, 0.15f, 0.9f);
|
|
private static readonly Color s_AddBtnBg = new Color(0.15f, 0.3f, 0.15f, 0.8f);
|
|
private static readonly Color s_AddBtnHover = new Color(0.2f, 0.45f, 0.2f, 0.9f);
|
|
|
|
private const float ROW_H = 22f;
|
|
private const float COL_W = 36f;
|
|
private const float MARGIN = 3f;
|
|
|
|
static XericUIActionTableSceneEditor()
|
|
{
|
|
s_Enabled = EditorPrefs.GetBool(ENABLED_KEY, true);
|
|
SceneView.duringSceneGui += OnSceneGUI;
|
|
}
|
|
|
|
[MenuItem("Xeric UI/表格编辑器覆盖层", false, 200)]
|
|
private static void ToggleEnabled()
|
|
{
|
|
s_Enabled = !s_Enabled;
|
|
EditorPrefs.SetBool(ENABLED_KEY, s_Enabled);
|
|
Menu.SetChecked("Xeric UI/表格编辑器覆盖层", s_Enabled);
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
[MenuItem("Xeric UI/表格编辑器覆盖层", true)]
|
|
private static bool ToggleEnabledValidate()
|
|
{
|
|
Menu.SetChecked("Xeric UI/表格编辑器覆盖层", s_Enabled);
|
|
return true;
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// 状态机入口
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
private static void UpdateState()
|
|
{
|
|
string focused = GUI.GetNameOfFocusedControl();
|
|
|
|
// 检测焦点切换 → 种子编辑文本
|
|
if (focused != s_LastFocusedControl)
|
|
{
|
|
s_LastFocusedControl = focused;
|
|
OnFocusChanged(focused);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(focused))
|
|
s_State = EditorState.TableNavigation;
|
|
else if (focused.StartsWith("XTableColSizeEdit_") || focused.StartsWith("XTableRowSizeEdit_"))
|
|
s_State = EditorState.SizeEditing;
|
|
else if (focused == "XTableSceneEditInput")
|
|
s_State = EditorState.CellEditing;
|
|
else
|
|
s_State = EditorState.TableNavigation;
|
|
|
|
s_CurrentControlName = focused;
|
|
}
|
|
|
|
private static void OnFocusChanged(string newFocus)
|
|
{
|
|
// 气泡输入框获得焦点:种子为当前尺寸值
|
|
if (newFocus.StartsWith("XTableColSizeEdit_") && s_ActiveTable != null)
|
|
{
|
|
if (int.TryParse(newFocus.Substring("XTableColSizeEdit_".Length), out int idx))
|
|
{
|
|
s_SizeEditIsColumn = true;
|
|
s_SizeEditTargetIndex = idx;
|
|
s_SizeEditText = s_ColWidths != null && idx < s_ColWidths.Length
|
|
? s_ColWidths[idx].ToString("F0") : "";
|
|
}
|
|
}
|
|
else if (newFocus.StartsWith("XTableRowSizeEdit_") && s_ActiveTable != null)
|
|
{
|
|
if (int.TryParse(newFocus.Substring("XTableRowSizeEdit_".Length), out int idx))
|
|
{
|
|
s_SizeEditIsColumn = false;
|
|
s_SizeEditTargetIndex = idx;
|
|
s_SizeEditText = s_RowHeights != null && idx < s_RowHeights.Length
|
|
? s_RowHeights[idx].ToString("F0") : "";
|
|
}
|
|
}
|
|
// 底部内容输入框获得焦点:种子为当前单元格内容
|
|
else if (newFocus == "XTableSceneEditInput" && s_ActiveTable != null)
|
|
{
|
|
SyncEditText(s_ActiveTable);
|
|
}
|
|
}
|
|
|
|
/// <summary>将 s_EditText 同步为当前选中单元格的内容(选中变更时调用)</summary>
|
|
private static void SyncEditText(XericUIActionTable table)
|
|
{
|
|
int sr = table.SelectedRow;
|
|
int sc = table.SelectedCol;
|
|
if (sr >= 0 && sc >= 0)
|
|
{
|
|
var data = table.GetCellData(sr, sc);
|
|
s_EditText = (data != null) ? (data.Text ?? "") : "";
|
|
}
|
|
else
|
|
{
|
|
s_EditText = "";
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════════════════════════════════════
|
|
// 主入口
|
|
// ═══════════════════════════════════════════════════════════════
|
|
|
|
private static void OnSceneGUI(SceneView sceneView)
|
|
{
|
|
if (!s_Enabled) return;
|
|
|
|
GameObject sel = Selection.activeGameObject;
|
|
if (sel == null) return;
|
|
|
|
XericUIActionTable table = sel.GetComponentInChildren<XericUIActionTable>();
|
|
if (table == null)
|
|
table = sel.GetComponentInParent<XericUIActionTable>();
|
|
if (table == null || !table.isActiveAndEnabled) return;
|
|
|
|
#if UNITY_EDITOR
|
|
Canvas.ForceUpdateCanvases();
|
|
#endif
|
|
|
|
RectTransform tableRt = table.GetComponent<RectTransform>();
|
|
float[] rh = table.RowHeights;
|
|
float[] cw = table.ColWidths;
|
|
if (rh == null || cw == null || rh.Length == 0 || cw.Length == 0) return;
|
|
|
|
Vector3[] corners = new Vector3[4];
|
|
tableRt.GetWorldCorners(corners);
|
|
|
|
Vector2 guiTL = HandleUtility.WorldToGUIPoint(corners[1]);
|
|
Vector2 guiBR = HandleUtility.WorldToGUIPoint(corners[3]);
|
|
|
|
float tableW = guiBR.x - guiTL.x;
|
|
float tableH = guiBR.y - guiTL.y;
|
|
if (tableW <= 0 || tableH <= 0) return;
|
|
|
|
float totalW = 0, totalH = 0;
|
|
foreach (float w in cw) totalW += w;
|
|
foreach (float h in rh) totalH += h;
|
|
if (totalW <= 0 || totalH <= 0) return;
|
|
|
|
float xScale = tableW / totalW;
|
|
float yScale = tableH / totalH;
|
|
|
|
Rect tableRect = new Rect(guiTL.x, guiTL.y, tableW, tableH);
|
|
|
|
// 更新缓存
|
|
s_TableRect = tableRect;
|
|
s_XScale = xScale;
|
|
s_YScale = yScale;
|
|
s_RowHeights = rh;
|
|
s_ColWidths = cw;
|
|
s_ActiveTable = table;
|
|
|
|
Handles.BeginGUI();
|
|
|
|
// ★ 状态机:必须最先执行,后续所有逻辑依赖 s_State
|
|
UpdateState();
|
|
|
|
DrawRowLabels(rh, tableRect, xScale, yScale, table);
|
|
DrawColumnLabels(cw, tableRect, xScale, yScale, table);
|
|
DrawGridLines(rh, cw, tableRect, xScale, yScale);
|
|
DrawSelectionHighlight(rh, cw, tableRect, xScale, yScale, table);
|
|
DrawAddButtons(rh, cw, tableRect, xScale, yScale, table);
|
|
DrawSizeEditBubble(rh, cw, tableRect, xScale, yScale, table);
|
|
DrawInputBar(table, tableRect.x, tableRect.y + tableRect.height + MARGIN);
|
|
|
|
// 非导航状态不处理鼠标/键盘表格操作
|
|
if (s_State == EditorState.TableNavigation)
|
|
{
|
|
HandleTableMouse(table, rh, cw, tableRect, xScale, yScale);
|
|
HandleKeyboard(table, rh, cw);
|
|
}
|
|
|
|
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 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>创建纯色 1x1 纹理,用于按钮背景</summary>
|
|
private static Texture2D MakeTex(int w, int h, Color col)
|
|
{
|
|
Color[] pix = new Color[w * h];
|
|
for (int i = 0; i < pix.Length; i++)
|
|
pix[i] = col;
|
|
Texture2D result = new Texture2D(w, h);
|
|
result.SetPixels(pix);
|
|
result.Apply();
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 行标签
|
|
|
|
private static void DrawRowLabels(float[] rh, Rect tableRect,
|
|
float xScale, float yScale, XericUIActionTable table)
|
|
{
|
|
var style = new GUIStyle(GUI.skin.label)
|
|
{
|
|
fontSize = 10,
|
|
normal = { textColor = s_LabelText },
|
|
alignment = TextAnchor.MiddleCenter
|
|
};
|
|
|
|
float y = tableRect.y;
|
|
for (int r = 0; r < rh.Length; r++)
|
|
{
|
|
float cellH = rh[r] * yScale;
|
|
if (cellH < 2) { y += cellH; continue; }
|
|
|
|
Rect labelRect = new Rect(
|
|
tableRect.x - COL_W - MARGIN,
|
|
y,
|
|
COL_W,
|
|
cellH);
|
|
|
|
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 (s_State != EditorState.TableNavigation) { y += cellH; continue; }
|
|
|
|
if (Event.current.type == EventType.MouseDown &&
|
|
Event.current.button == 0 &&
|
|
labelRect.Contains(Event.current.mousePosition))
|
|
{
|
|
int col = table.SelectedCol;
|
|
if (col < 0) col = 0;
|
|
|
|
s_IsDragging = true;
|
|
s_DragStartRow = r;
|
|
s_DragStartCol = col;
|
|
|
|
if (Event.current.shift && table.SelectedRow >= 0)
|
|
{
|
|
if (!table.HasMultiSelection)
|
|
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
|
|
table.ExtendMultiSelect(r, col);
|
|
}
|
|
else
|
|
{
|
|
table.StartMultiSelect(r, col);
|
|
}
|
|
|
|
table.RefreshView();
|
|
SyncEditText(table);
|
|
Event.current.Use();
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
y += cellH;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 列标签
|
|
|
|
private static void DrawColumnLabels(float[] cw, Rect tableRect,
|
|
float xScale, float yScale, XericUIActionTable table)
|
|
{
|
|
var style = new GUIStyle(GUI.skin.label)
|
|
{
|
|
fontSize = 10,
|
|
normal = { textColor = s_LabelText },
|
|
alignment = TextAnchor.MiddleCenter
|
|
};
|
|
|
|
float x = tableRect.x;
|
|
for (int c = 0; c < cw.Length; c++)
|
|
{
|
|
float cellW = cw[c] * xScale;
|
|
if (cellW < 2) { x += cellW; continue; }
|
|
|
|
Rect labelRect = new Rect(
|
|
x,
|
|
tableRect.y - ROW_H - MARGIN,
|
|
cellW,
|
|
ROW_H);
|
|
|
|
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 (s_State != EditorState.TableNavigation) { x += cellW; continue; }
|
|
|
|
if (Event.current.type == EventType.MouseDown &&
|
|
Event.current.button == 0 &&
|
|
labelRect.Contains(Event.current.mousePosition))
|
|
{
|
|
int row = table.SelectedRow;
|
|
if (row < 0) row = 0;
|
|
|
|
s_IsDragging = true;
|
|
s_DragStartRow = row;
|
|
s_DragStartCol = c;
|
|
|
|
if (Event.current.shift && table.SelectedCol >= 0)
|
|
{
|
|
if (!table.HasMultiSelection)
|
|
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
|
|
table.ExtendMultiSelect(row, c);
|
|
}
|
|
else
|
|
{
|
|
table.StartMultiSelect(row, c);
|
|
}
|
|
|
|
table.RefreshView();
|
|
SyncEditText(table);
|
|
Event.current.Use();
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
x += cellW;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 网格线
|
|
|
|
private static void DrawGridLines(float[] rh, float[] cw, Rect tr,
|
|
float xScale, float yScale)
|
|
{
|
|
Color lineColor = new Color(0.5f, 0.5f, 0.55f, 0.18f);
|
|
|
|
float y = tr.y;
|
|
for (int r = 0; r < rh.Length; r++)
|
|
{
|
|
y += rh[r] * yScale;
|
|
EditorGUI.DrawRect(new Rect(tr.x, y, tr.width, 1), lineColor);
|
|
}
|
|
|
|
float x = tr.x;
|
|
for (int c = 0; c < cw.Length; c++)
|
|
{
|
|
x += cw[c] * xScale;
|
|
EditorGUI.DrawRect(new Rect(x, tr.y, 1, tr.height), lineColor);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 选中高亮
|
|
|
|
private static void DrawSelectionHighlight(float[] rh, float[] cw, Rect tr,
|
|
float xScale, float yScale, XericUIActionTable table)
|
|
{
|
|
int sr = table.SelectedRow;
|
|
int sc = table.SelectedCol;
|
|
if (sr < 0 || sc < 0 || sr >= rh.Length || sc >= cw.Length) return;
|
|
|
|
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);
|
|
|
|
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),
|
|
s_SelectionColor);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 添加行列按钮
|
|
|
|
private static void DrawAddButtons(float[] rh, float[] cw, Rect tr,
|
|
float xScale, float yScale, XericUIActionTable table)
|
|
{
|
|
var plusStyle = new GUIStyle(GUI.skin.label)
|
|
{
|
|
fontSize = 14,
|
|
fontStyle = FontStyle.Bold,
|
|
normal = { textColor = Color.white },
|
|
alignment = TextAnchor.MiddleCenter
|
|
};
|
|
|
|
float yEnd = tr.y + tr.height;
|
|
Rect rowBtn = new Rect(tr.x - COL_W - MARGIN, yEnd + MARGIN, COL_W, ROW_H);
|
|
|
|
bool rowHover = rowBtn.Contains(Event.current.mousePosition);
|
|
EditorGUI.DrawRect(rowBtn, rowHover ? s_AddBtnHover : s_AddBtnBg);
|
|
GUI.Label(rowBtn, "+", plusStyle);
|
|
|
|
if (Event.current.type == EventType.MouseDown &&
|
|
Event.current.button == 0 && rowHover)
|
|
{
|
|
table.AddRow();
|
|
table.RefreshView();
|
|
Event.current.Use();
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
float xEnd = tr.x + tr.width;
|
|
Rect colBtn = new Rect(xEnd + MARGIN, tr.y - ROW_H - MARGIN, COL_W, ROW_H);
|
|
|
|
bool colHover = colBtn.Contains(Event.current.mousePosition);
|
|
EditorGUI.DrawRect(colBtn, colHover ? s_AddBtnHover : s_AddBtnBg);
|
|
GUI.Label(colBtn, "+", plusStyle);
|
|
|
|
if (Event.current.type == EventType.MouseDown &&
|
|
Event.current.button == 0 && colHover)
|
|
{
|
|
table.AddColumn();
|
|
table.RefreshView();
|
|
Event.current.Use();
|
|
SceneView.RepaintAll();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 气泡尺寸编辑器(含应用按钮)
|
|
|
|
private const float BUBBLE_W = 60;
|
|
private const float BUBBLE_H = 22;
|
|
private const float BUBBLE_APPLY_BTN_W = 24;
|
|
private const float BUBBLE_DEL_BTN_W = 24;
|
|
|
|
private static GUIStyle s_BubbleApplyBtnStyle;
|
|
private static GUIStyle s_BubbleApplyBtnStyleActive;
|
|
private static GUIStyle s_BubbleDelBtnStyle;
|
|
|
|
private static void EnsureBubbleStyles()
|
|
{
|
|
if (s_BubbleApplyBtnStyle != null) return;
|
|
s_BubbleApplyBtnStyle = new GUIStyle(GUI.skin.button)
|
|
{
|
|
fontSize = 11,
|
|
fontStyle = FontStyle.Bold,
|
|
normal = { textColor = new Color(0.5f, 0.5f, 0.5f), background = MakeTex(1, 1, new Color(0.25f, 0.25f, 0.25f, 0.6f)) },
|
|
hover = { textColor = new Color(0.7f, 0.7f, 0.7f), background = MakeTex(1, 1, new Color(0.35f, 0.35f, 0.35f, 0.8f)) },
|
|
active = { textColor = Color.white, background = MakeTex(1, 1, new Color(0.15f, 0.15f, 0.15f, 0.8f)) },
|
|
padding = new RectOffset(0, 0, 0, 0),
|
|
margin = new RectOffset(0, 0, 0, 0),
|
|
alignment = TextAnchor.MiddleCenter,
|
|
border = new RectOffset(0, 0, 0, 0)
|
|
};
|
|
s_BubbleApplyBtnStyleActive = new GUIStyle(s_BubbleApplyBtnStyle)
|
|
{
|
|
normal = { textColor = Color.white, background = MakeTex(1, 1, new Color(0.2f, 0.6f, 0.2f, 0.9f)) },
|
|
hover = { textColor = Color.white, background = MakeTex(1, 1, new Color(0.2f, 0.55f, 0.2f, 0.95f)) },
|
|
active = { textColor = Color.white, background = MakeTex(1, 1, new Color(0.1f, 0.4f, 0.1f, 0.95f)) }
|
|
};
|
|
s_BubbleDelBtnStyle = new GUIStyle(s_BubbleApplyBtnStyle)
|
|
{
|
|
normal = { textColor = new Color(0.6f, 0.3f, 0.3f), background = MakeTex(1, 1, new Color(0.3f, 0.15f, 0.15f, 0.6f)) },
|
|
hover = { textColor = Color.white, background = MakeTex(1, 1, new Color(0.7f, 0.2f, 0.2f, 0.95f)) },
|
|
active = { textColor = Color.white, background = MakeTex(1, 1, new Color(0.5f, 0.1f, 0.1f, 0.95f)) }
|
|
};
|
|
}
|
|
|
|
private static void DrawSizeEditBubble(float[] rh, float[] cw, Rect tr,
|
|
float xScale, float yScale, XericUIActionTable table)
|
|
{
|
|
int selRow = table.SelectedRow;
|
|
int selCol = table.SelectedCol;
|
|
|
|
// 气泡固定总宽:输入框 + 应用按钮 + 删除按钮 + 间距
|
|
const float totalW = BUBBLE_W + BUBBLE_APPLY_BTN_W + BUBBLE_DEL_BTN_W + 4f;
|
|
|
|
// ── 列宽气泡:左对齐列左边缘,位于列标签上方 ──
|
|
if (selCol >= 0 && selCol < cw.Length && selRow < 0)
|
|
{
|
|
float cx = tr.x + SumBefore(cw, selCol) * xScale;
|
|
float bx = cx + 2f;
|
|
float by = tr.y - ROW_H - MARGIN - BUBBLE_H - 4f;
|
|
DrawBubble(table, bx, by, cw[selCol], totalW, isColumn: true, index: selCol);
|
|
}
|
|
|
|
// ── 行高气泡:左对齐行标签左侧 ──
|
|
if (selRow >= 0 && selRow < rh.Length && selCol < 0)
|
|
{
|
|
float ry = tr.y + SumBefore(rh, selRow) * yScale;
|
|
float rhScaled = rh[selRow] * yScale;
|
|
float bx = tr.x - COL_W - MARGIN - totalW - 4f;
|
|
float by = ry + (rhScaled - BUBBLE_H) / 2f;
|
|
DrawBubble(table, bx, by, rh[selRow], totalW, isColumn: false, index: selRow);
|
|
}
|
|
|
|
// ── 单元格选中时同时显示 ──
|
|
if (selRow >= 0 && selCol >= 0)
|
|
{
|
|
if (selCol < cw.Length)
|
|
{
|
|
float cx = tr.x + SumBefore(cw, selCol) * xScale;
|
|
float bx = cx + 2f;
|
|
float by = tr.y - ROW_H - MARGIN - BUBBLE_H - 4f;
|
|
DrawBubble(table, bx, by, cw[selCol], totalW, isColumn: true, index: selCol);
|
|
}
|
|
if (selRow < rh.Length)
|
|
{
|
|
float ry = tr.y + SumBefore(rh, selRow) * yScale;
|
|
float rhScaled = rh[selRow] * yScale;
|
|
float bx = tr.x - COL_W - MARGIN - totalW - 4f;
|
|
float by = ry + (rhScaled - BUBBLE_H) / 2f;
|
|
DrawBubble(table, bx, by, rh[selRow], totalW, isColumn: false, index: selRow);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void DrawBubble(XericUIActionTable table,
|
|
float x, float y, float currentValue,
|
|
float totalW, bool isColumn, int index)
|
|
{
|
|
string controlName = isColumn
|
|
? $"XTableColSizeEdit_{index}"
|
|
: $"XTableRowSizeEdit_{index}";
|
|
bool isFocused = GUI.GetNameOfFocusedControl() == controlName;
|
|
|
|
Rect bubbleRect = new Rect(x, y, totalW, BUBBLE_H);
|
|
|
|
EditorGUI.DrawRect(bubbleRect, s_BubbleBg);
|
|
EditorGUI.DrawRect(new Rect(x, y, totalW, 1), s_BubbleBorder);
|
|
EditorGUI.DrawRect(new Rect(x, y + BUBBLE_H - 1, totalW, 1), s_BubbleBorder);
|
|
EditorGUI.DrawRect(new Rect(x, y, 1, BUBBLE_H), s_BubbleBorder);
|
|
EditorGUI.DrawRect(new Rect(x + totalW - 1, y, 1, BUBBLE_H), s_BubbleBorder);
|
|
|
|
var inputStyle = new GUIStyle(GUI.skin.textField)
|
|
{
|
|
fontSize = 11,
|
|
margin = new RectOffset(1, 1, 1, 1),
|
|
alignment = TextAnchor.MiddleCenter
|
|
};
|
|
|
|
// 输入框占左
|
|
Rect inputRect = new Rect(x + 2, y + 2, BUBBLE_W - 4, BUBBLE_H - 4);
|
|
GUI.SetNextControlName(controlName);
|
|
|
|
if (isFocused)
|
|
s_SizeEditText = GUI.TextField(inputRect, s_SizeEditText, inputStyle);
|
|
else
|
|
GUI.TextField(inputRect, currentValue.ToString("F0"), inputStyle);
|
|
|
|
// 应用按钮 — 聚焦时绿色,否则灰色
|
|
EnsureBubbleStyles();
|
|
Rect applyBtnRect = new Rect(x + BUBBLE_W + 1f, y + 1, BUBBLE_APPLY_BTN_W, BUBBLE_H - 2);
|
|
var btnStyle = isFocused ? s_BubbleApplyBtnStyleActive : s_BubbleApplyBtnStyle;
|
|
|
|
if (GUI.Button(applyBtnRect, "✓", btnStyle))
|
|
{
|
|
ApplyBubbleEdit(table, isColumn, index);
|
|
GUI.FocusControl(null);
|
|
}
|
|
|
|
// 删除按钮 — 删除当前行/列
|
|
Rect delBtnRect = new Rect(x + BUBBLE_W + BUBBLE_APPLY_BTN_W + 2f, y + 1, BUBBLE_DEL_BTN_W, BUBBLE_H - 2);
|
|
if (GUI.Button(delBtnRect, "✕", s_BubbleDelBtnStyle))
|
|
{
|
|
ApplyBubbleDelete(table, isColumn, index);
|
|
}
|
|
|
|
// 回车提交
|
|
if (isFocused &&
|
|
Event.current.type == EventType.KeyDown &&
|
|
Event.current.keyCode == KeyCode.Return)
|
|
{
|
|
ApplyBubbleEdit(table, isColumn, index);
|
|
Event.current.Use();
|
|
}
|
|
}
|
|
|
|
private static void ApplyBubbleEdit(XericUIActionTable table, bool isColumn, int index)
|
|
{
|
|
string trimmed = s_SizeEditText.Trim();
|
|
if (float.TryParse(trimmed, out float value) && value > 0)
|
|
{
|
|
if (isColumn)
|
|
table.SetColWidth(index, value);
|
|
else
|
|
table.SetRowHeight(index, value);
|
|
table.RefreshView();
|
|
}
|
|
GUI.FocusControl(null);
|
|
s_State = EditorState.TableNavigation;
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
private static void ApplyBubbleDelete(XericUIActionTable table, bool isColumn, int index)
|
|
{
|
|
if (isColumn)
|
|
table.RemoveColumn(index);
|
|
else
|
|
table.RemoveRow(index);
|
|
|
|
GUI.FocusControl(null);
|
|
s_State = EditorState.TableNavigation;
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 底部输入框
|
|
|
|
private static void DrawInputBar(XericUIActionTable table, float x, float y)
|
|
{
|
|
const float barW = 450;
|
|
const float barH = 52;
|
|
const float rowH = 22;
|
|
const float pad = 2;
|
|
|
|
Rect barRect = new Rect(x, y, barW, barH);
|
|
EditorGUI.DrawRect(barRect, s_InputBg);
|
|
|
|
if (table.SelectedRow >= 0 && table.SelectedCol >= 0)
|
|
{
|
|
var labelStyle = new GUIStyle(GUI.skin.label)
|
|
{
|
|
fontSize = 11,
|
|
normal = { textColor = s_LabelText },
|
|
alignment = TextAnchor.MiddleCenter
|
|
};
|
|
var inputStyle = new GUIStyle(GUI.skin.textField)
|
|
{
|
|
fontSize = 12,
|
|
margin = new RectOffset(1, 1, 1, 1)
|
|
};
|
|
var btnStyle = new GUIStyle(GUI.skin.button)
|
|
{
|
|
fontSize = 11,
|
|
margin = new RectOffset(1, 1, 1, 1),
|
|
padding = new RectOffset(4, 4, 2, 2)
|
|
};
|
|
|
|
// Row 1: 坐标标签 | 输入框 | 应用按钮
|
|
float btnW = 44;
|
|
float row1Y = barRect.y + pad;
|
|
float row2Y = barRect.y + rowH + pad + 4;
|
|
string cellLabel = table.GetSelectionText();
|
|
|
|
GUI.Label(new Rect(barRect.x + 4, row1Y, 60, rowH), cellLabel, labelStyle);
|
|
Rect inputRect = new Rect(barRect.x + 68, row1Y, barW - 68 - btnW - pad * 2, rowH);
|
|
|
|
// 回车提交(仅在 CellEditing 状态下)
|
|
if (s_State == EditorState.CellEditing &&
|
|
Event.current.type == EventType.KeyDown &&
|
|
Event.current.keyCode == KeyCode.Return)
|
|
{
|
|
ApplyCellEdit(table);
|
|
Event.current.Use();
|
|
}
|
|
|
|
GUI.SetNextControlName("XTableSceneEditInput");
|
|
s_EditText = GUI.TextField(inputRect, s_EditText, inputStyle);
|
|
|
|
if (GUI.Button(new Rect(barRect.x + barW - btnW - pad, row1Y, btnW, rowH),
|
|
"应用", btnStyle))
|
|
{
|
|
GUI.FocusControl(null);
|
|
ApplyCellEdit(table);
|
|
}
|
|
|
|
// Row 2: 样式命名空间下拉框
|
|
var nsLabelStyle = new GUIStyle(GUI.skin.label)
|
|
{
|
|
fontSize = 10,
|
|
normal = { textColor = new Color(0.6f, 0.6f, 0.6f) },
|
|
alignment = TextAnchor.MiddleRight
|
|
};
|
|
GUI.Label(new Rect(barRect.x + 4, row2Y, 36, rowH), "样式:", nsLabelStyle);
|
|
|
|
// 获取当前命名空间
|
|
string currentNS = table.GetCellStyleNamespace(
|
|
table.SelectedRow, table.SelectedCol) ?? "";
|
|
|
|
// 构建下拉选项(StyleManager.GetAvailableNamespaces 已保证 "default" 在首位)
|
|
var namespaces = StyleManager.GetAvailableNamespaces();
|
|
int currentIdx = namespaces.IndexOf(currentNS);
|
|
if (currentIdx < 0) currentIdx = 0;
|
|
|
|
Rect dropRect = new Rect(barRect.x + 44, row2Y, barW - 44 - pad, rowH);
|
|
int newIdx = UnityEditor.EditorGUI.Popup(dropRect, currentIdx,
|
|
namespaces.ToArray(), UnityEditor.EditorStyles.popup);
|
|
|
|
if (newIdx != currentIdx && newIdx >= 0 && newIdx < namespaces.Count)
|
|
{
|
|
table.SetCellStyleNamespace(table.SelectedRow, table.SelectedCol,
|
|
namespaces[newIdx]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var hintStyle = new GUIStyle(GUI.skin.label)
|
|
{
|
|
fontSize = 10,
|
|
normal = { textColor = new Color(0.5f, 0.5f, 0.5f) },
|
|
alignment = TextAnchor.MiddleCenter
|
|
};
|
|
GUI.Label(new Rect(barRect.x, barRect.y, barW, barH / 2),
|
|
"点击左侧行号或上方列名选中单元格 | Shift+方向键多选", hintStyle);
|
|
}
|
|
}
|
|
|
|
private static void ApplyCellEdit(XericUIActionTable table)
|
|
{
|
|
if (table.SelectedRow < 0 || table.SelectedCol < 0) return;
|
|
table.SetCellText(table.SelectedRow, table.SelectedCol, s_EditText);
|
|
table.RefreshView();
|
|
s_State = EditorState.TableNavigation;
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 表格区域鼠标(仅 TableNavigation 状态调用)
|
|
|
|
private static void HandleTableMouse(XericUIActionTable table,
|
|
float[] rh, float[] cw, Rect tr,
|
|
float xScale, float yScale)
|
|
{
|
|
Event e = Event.current;
|
|
|
|
switch (e.type)
|
|
{
|
|
case EventType.MouseDown:
|
|
if (e.button != 0) break;
|
|
if (!tr.Contains(e.mousePosition)) break;
|
|
|
|
CellFromMouse(tr, xScale, yScale, out int mdRow, out int mdCol);
|
|
if (mdRow < 0 || mdCol < 0) break;
|
|
|
|
s_IsDragging = true;
|
|
s_DragStartRow = mdRow;
|
|
s_DragStartCol = mdCol;
|
|
|
|
if (e.shift && table.SelectedRow >= 0 && table.SelectedCol >= 0)
|
|
{
|
|
if (!table.HasMultiSelection)
|
|
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
|
|
table.ExtendMultiSelect(mdRow, mdCol);
|
|
}
|
|
else
|
|
{
|
|
table.StartMultiSelect(mdRow, mdCol);
|
|
}
|
|
|
|
table.RefreshView();
|
|
SyncEditText(table);
|
|
e.Use();
|
|
SceneView.RepaintAll();
|
|
break;
|
|
|
|
case EventType.MouseDrag:
|
|
if (!s_IsDragging || e.button != 0) break;
|
|
|
|
CellFromMouse(tr, xScale, yScale, out int dragRow, out int dragCol);
|
|
if (dragRow < 0 || dragCol < 0) break;
|
|
|
|
table.ExtendMultiSelect(dragRow, dragCol);
|
|
table.RefreshView();
|
|
SyncEditText(table);
|
|
e.Use();
|
|
SceneView.RepaintAll();
|
|
break;
|
|
|
|
case EventType.MouseUp:
|
|
if (e.button != 0) break;
|
|
s_IsDragging = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private static void CellFromMouse(Rect tr, float xScale, float yScale,
|
|
out int row, out int col)
|
|
{
|
|
float rx = (Event.current.mousePosition.x - tr.x) / xScale;
|
|
float ry = (Event.current.mousePosition.y - tr.y) / yScale;
|
|
col = IndexFromCoord(rx, s_ColWidths);
|
|
row = IndexFromCoord(ry, s_RowHeights);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 键盘(仅 TableNavigation 状态调用)
|
|
|
|
private static void HandleKeyboard(XericUIActionTable table, float[] rh, float[] cw)
|
|
{
|
|
Event e = Event.current;
|
|
if (e.type != EventType.KeyDown) return;
|
|
|
|
// Escape 清除选区
|
|
if (e.keyCode == KeyCode.Escape)
|
|
{
|
|
table.ClearSelection();
|
|
table.RefreshView();
|
|
s_EditText = "";
|
|
SyncEditText(table);
|
|
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();
|
|
SyncEditText(table);
|
|
e.Use();
|
|
SceneView.RepaintAll();
|
|
return;
|
|
}
|
|
|
|
switch (e.keyCode)
|
|
{
|
|
case KeyCode.UpArrow:
|
|
if (!TryClampToValid(row - 1, col, rh, cw, out row, out col)) return;
|
|
break;
|
|
case KeyCode.DownArrow:
|
|
if (!TryClampToValid(row + 1, col, rh, cw, out row, out col)) return;
|
|
break;
|
|
case KeyCode.LeftArrow:
|
|
if (!TryClampToValid(row, col - 1, rh, cw, out row, out col)) return;
|
|
break;
|
|
case KeyCode.RightArrow:
|
|
if (!TryClampToValid(row, col + 1, rh, cw, out row, out col)) return;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if (shift)
|
|
{
|
|
if (!table.HasMultiSelection && table.SelectedRow >= 0 && table.SelectedCol >= 0)
|
|
table.StartMultiSelect(table.SelectedRow, table.SelectedCol);
|
|
table.ExtendMultiSelect(row, col);
|
|
}
|
|
else
|
|
{
|
|
table.SelectCell(row, col);
|
|
}
|
|
|
|
table.RefreshView();
|
|
SyncEditText(table);
|
|
e.Use();
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
private static bool TryClampToValid(int row, int col, float[] rh, float[] cw,
|
|
out int outRow, out int outCol)
|
|
{
|
|
if (row < 0 || row >= rh.Length || col < 0 || col >= cw.Length)
|
|
{
|
|
outRow = row;
|
|
outCol = col;
|
|
return false;
|
|
}
|
|
outRow = row;
|
|
outCol = col;
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|