372 lines
10 KiB
C#
372 lines
10 KiB
C#
using UnityEditor;
|
|
|
|
using UnityEngine;
|
|
|
|
using XericUI.XTable.Rendering.Component;
|
|
|
|
namespace XericUIEditor.XTable
|
|
{
|
|
/// <summary>
|
|
/// Scene View 表格编辑覆盖层——
|
|
/// 左侧行索引、顶部列索引、底部输入框。
|
|
/// 点击行列交叉区域选中单元格,底部输入框编辑文本。
|
|
/// 不创建任何场景对象,通过 Handles.BeginGUI/EndGUI 绘制。
|
|
/// </summary>
|
|
[InitializeOnLoad]
|
|
public static class XericUIActionTableSceneEditor
|
|
{
|
|
private static bool s_Enabled;
|
|
private const string ENABLED_KEY = "XericUI_TableSceneEditor_Enabled";
|
|
|
|
private static string s_EditText = "";
|
|
|
|
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_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 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 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;
|
|
|
|
// GetWorldCorners 返回世界坐标,topleft = corners[1]
|
|
Vector3[] corners = new Vector3[4];
|
|
tableRt.GetWorldCorners(corners);
|
|
|
|
Camera svCam = sceneView.camera;
|
|
Rect camRect = svCam.pixelRect;
|
|
|
|
// 对 ScreenSpaceOverlay Canvas,世界坐标即屏幕像素坐标
|
|
// 需要映射到 SceneView 本地 GUI 坐标
|
|
Vector2 guiTL = ScreenToSceneViewGUI(corners[1], camRect);
|
|
Vector2 guiBR = ScreenToSceneViewGUI(corners[3], camRect);
|
|
|
|
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;
|
|
|
|
// 表格区域(GUI 坐标,Y 向下)
|
|
Rect tableRect = new Rect(guiTL.x, guiTL.y, tableW, tableH);
|
|
|
|
Handles.BeginGUI();
|
|
|
|
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);
|
|
DrawInputBar(table, tableRect.x, tableRect.y + tableRect.height + MARGIN);
|
|
HandleInput(table, rh, cw, tableRect, xScale, yScale);
|
|
|
|
Handles.EndGUI();
|
|
}
|
|
|
|
/// <summary>屏幕坐标转 SceneView 本地 GUI 坐标</summary>
|
|
private static Vector2 ScreenToSceneViewGUI(Vector2 screenPos, Rect camPixelRect)
|
|
{
|
|
return new Vector2(
|
|
screenPos.x - camPixelRect.x,
|
|
camPixelRect.yMax - screenPos.y
|
|
);
|
|
}
|
|
|
|
#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);
|
|
|
|
bool isSelected = (table.SelectedRow == r);
|
|
EditorGUI.DrawRect(labelRect, isSelected ? s_SelectedLabel : s_LabelBg);
|
|
GUI.Label(labelRect, $"{r}", style);
|
|
|
|
if (Event.current.type == EventType.MouseDown &&
|
|
Event.current.button == 0 &&
|
|
labelRect.Contains(Event.current.mousePosition))
|
|
{
|
|
int col = table.SelectedCol;
|
|
if (col < 0) col = 0;
|
|
table.SelectCell(r, col);
|
|
table.RefreshView();
|
|
UpdateEditText(table, r, col);
|
|
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);
|
|
|
|
bool isSelected = (table.SelectedCol == c);
|
|
EditorGUI.DrawRect(labelRect, isSelected ? s_SelectedLabel : s_LabelBg);
|
|
string text = cellW > 40 ? $"列{c}" : (cellW > 18 ? $"{c}" : "");
|
|
GUI.Label(labelRect, text, style);
|
|
|
|
if (Event.current.type == EventType.MouseDown &&
|
|
Event.current.button == 0 &&
|
|
labelRect.Contains(Event.current.mousePosition))
|
|
{
|
|
int row = table.SelectedRow;
|
|
if (row < 0) row = 0;
|
|
table.SelectCell(row, c);
|
|
table.RefreshView();
|
|
UpdateEditText(table, row, c);
|
|
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;
|
|
|
|
float sx = tr.x;
|
|
for (int c = 0; c < sc; c++) sx += cw[c] * xScale;
|
|
|
|
float sy = tr.y;
|
|
for (int r = 0; r < sr; r++) sy += rh[r] * 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));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region 底部输入框
|
|
|
|
private static void DrawInputBar(XericUIActionTable table, float x, float y)
|
|
{
|
|
Rect barRect = new Rect(x, y, 400, 24);
|
|
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
|
|
};
|
|
GUI.Label(new Rect(barRect.x + 4, barRect.y, 50, barRect.height),
|
|
$"({table.SelectedRow},{table.SelectedCol})", labelStyle);
|
|
|
|
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);
|
|
s_EditText = GUI.TextField(inputRect, s_EditText, inputStyle);
|
|
|
|
var btnStyle = new GUIStyle(GUI.skin.button)
|
|
{
|
|
fontSize = 11,
|
|
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),
|
|
"应用", btnStyle))
|
|
{
|
|
ApplyEdit(table);
|
|
}
|
|
}
|
|
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(barRect, "点击左侧行号或上方列号选中单元格", hintStyle);
|
|
}
|
|
}
|
|
|
|
private static void ApplyEdit(XericUIActionTable table)
|
|
{
|
|
if (table.SelectedRow < 0 || table.SelectedCol < 0) return;
|
|
table.SetCellText(table.SelectedRow, table.SelectedCol, s_EditText);
|
|
table.RefreshView();
|
|
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 表格区域点击
|
|
|
|
private static void HandleInput(XericUIActionTable table,
|
|
float[] rh, float[] cw, Rect tr,
|
|
float xScale, float yScale)
|
|
{
|
|
Event e = Event.current;
|
|
if (e.type != EventType.MouseDown || e.button != 0) return;
|
|
if (!tr.Contains(e.mousePosition)) return;
|
|
|
|
float rx = (e.mousePosition.x - tr.x) / xScale;
|
|
float ry = (e.mousePosition.y - tr.y) / yScale;
|
|
|
|
int col = IndexFromCoord(rx, cw);
|
|
int row = IndexFromCoord(ry, rh);
|
|
if (row < 0 || col < 0) return;
|
|
|
|
table.SelectCell(row, col);
|
|
table.RefreshView();
|
|
UpdateEditText(table, row, col);
|
|
|
|
e.Use();
|
|
SceneView.RepaintAll();
|
|
}
|
|
|
|
#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;
|
|
}
|
|
}
|
|
}
|