2025-12-05 19:04:53 +08:00
|
|
|
|
using System;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.IO;
|
|
|
|
|
|
using System.Reflection;
|
2025-12-09 20:31:44 +08:00
|
|
|
|
using AlicizaX.InputGlyph;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
using UnityEditor;
|
|
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
using UnityEngine.U2D;
|
|
|
|
|
|
using TMPro;
|
|
|
|
|
|
|
|
|
|
|
|
[CustomEditor(typeof(InputGlyphDatabase))]
|
|
|
|
|
|
public class InputGlyphDatabaseEditor : Editor
|
|
|
|
|
|
{
|
2025-12-05 20:57:29 +08:00
|
|
|
|
SerializedProperty tablesProp;
|
2025-12-10 17:38:31 +08:00
|
|
|
|
SerializedProperty placeholderSpriteProp;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
InputGlyphDatabase db;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 动态标签索引(范围:0 .. tablesCount 为各表,最后一个 index = tablesCount 为 Settings)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
int tabIndex = 0;
|
|
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 添加表时使用的临时 UI 状态
|
|
|
|
|
|
bool showAddField = false;
|
|
|
|
|
|
string newTableName = "";
|
|
|
|
|
|
|
|
|
|
|
|
// 每个表的搜索字符串与分页状态(editor 内存,不序列化)
|
|
|
|
|
|
List<string> searchStrings = new List<string>();
|
|
|
|
|
|
List<int> currentPages = new List<int>();
|
|
|
|
|
|
|
|
|
|
|
|
// 常量
|
|
|
|
|
|
const int itemsPerPage = 10;
|
|
|
|
|
|
|
|
|
|
|
|
// 缩小后的预览尺寸
|
|
|
|
|
|
const int previewSize = 52;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-05 19:04:53 +08:00
|
|
|
|
void OnEnable()
|
|
|
|
|
|
{
|
|
|
|
|
|
db = target as InputGlyphDatabase;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
tablesProp = serializedObject.FindProperty("tables");
|
2025-12-10 17:38:31 +08:00
|
|
|
|
placeholderSpriteProp = serializedObject.FindProperty("placeholderSprite");
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
|
|
|
|
|
if (tablesProp == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Debug.LogError("Could not find serialized property 'tables' on InputGlyphDatabase. Check field name.");
|
2025-12-10 17:38:31 +08:00
|
|
|
|
return;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
}
|
2025-12-10 17:38:31 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果没有默认的 Keyboard/Xbox/PlayStation 三个表则确保创建(便于迁移)
|
|
|
|
|
|
EnsureDefaultTable("Keyboard");
|
|
|
|
|
|
EnsureDefaultTable("Xbox");
|
|
|
|
|
|
EnsureDefaultTable("PlayStation");
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化 editor 状态列表,长度与 tablesProp 对应
|
|
|
|
|
|
SyncEditorListsWithTables();
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public override void OnInspectorGUI()
|
|
|
|
|
|
{
|
|
|
|
|
|
serializedObject.Update();
|
2025-12-05 20:57:29 +08:00
|
|
|
|
if (db == null || tablesProp == null) return;
|
|
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 顶部工具栏(与 Save 按钮风格一致),同时放置 Settings 按钮
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
|
|
|
|
|
GUILayout.Space(4);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
if (GUILayout.Button("Save Asset", EditorStyles.toolbarButton))
|
2025-12-05 20:57:29 +08:00
|
|
|
|
{
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
2025-12-10 17:38:31 +08:00
|
|
|
|
EditorUtility.SetDirty(db);
|
|
|
|
|
|
AssetDatabase.SaveAssets();
|
2025-12-05 20:57:29 +08:00
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
GUILayout.FlexibleSpace();
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// + 按钮:切换显示新增输入区域
|
|
|
|
|
|
if (GUILayout.Button(showAddField ? "Cancel +" : "+ Add Table", EditorStyles.toolbarButton, GUILayout.Width(110)))
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
showAddField = !showAddField;
|
|
|
|
|
|
newTableName = "";
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// Settings 按钮也放在这个 toolbar 上(风格保持一致)
|
|
|
|
|
|
int settingsIndex = tablesProp != null ? tablesProp.arraySize : 0;
|
|
|
|
|
|
bool settingsSelected = (tabIndex == settingsIndex);
|
|
|
|
|
|
if (GUILayout.Toggle(settingsSelected, "Settings", EditorStyles.toolbarButton, GUILayout.Width(90)) != settingsSelected)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 切换到 settings 页面或从 settings 切回第一个 table(如果取消)
|
|
|
|
|
|
tabIndex = (tabIndex == settingsIndex) ? 0 : settingsIndex;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 如果正在新增,展示一个横向输入框(下方,同 toolbar 风格)
|
|
|
|
|
|
if (showAddField)
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
|
|
|
|
|
GUILayout.Label("Name:", GUILayout.Width(40));
|
|
|
|
|
|
newTableName = EditorGUILayout.TextField(newTableName);
|
|
|
|
|
|
if (GUILayout.Button("Add", EditorStyles.toolbarButton, GUILayout.Width(80)))
|
|
|
|
|
|
{
|
|
|
|
|
|
string trimmed = newTableName != null ? newTableName.Trim() : "";
|
|
|
|
|
|
if (string.IsNullOrEmpty(trimmed))
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorUtility.DisplayDialog("Invalid Name", "Table name cannot be empty.", "OK");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// 唯一性检查(不区分大小写)
|
|
|
|
|
|
bool exists = false;
|
|
|
|
|
|
for (int i = 0; i < tablesProp.arraySize; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
var t = tablesProp.GetArrayElementAtIndex(i);
|
|
|
|
|
|
var nameProp = t.FindPropertyRelative("deviceName");
|
|
|
|
|
|
if (nameProp != null && string.Equals(nameProp.stringValue, trimmed, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
{
|
|
|
|
|
|
exists = true; break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (exists)
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorUtility.DisplayDialog("Duplicate", "A table with that name already exists.", "OK");
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
int newIndex = tablesProp.arraySize;
|
|
|
|
|
|
tablesProp.InsertArrayElementAtIndex(newIndex);
|
|
|
|
|
|
var newTable = tablesProp.GetArrayElementAtIndex(newIndex);
|
|
|
|
|
|
var nameProp = newTable.FindPropertyRelative("deviceName");
|
|
|
|
|
|
if (nameProp != null) nameProp.stringValue = trimmed;
|
|
|
|
|
|
var tmpAssetProp = newTable.FindPropertyRelative("tmpAsset");
|
|
|
|
|
|
if (tmpAssetProp != null) tmpAssetProp.objectReferenceValue = null;
|
|
|
|
|
|
var entriesProp = newTable.FindPropertyRelative("entries");
|
|
|
|
|
|
if (entriesProp != null) entriesProp.arraySize = 0;
|
|
|
|
|
|
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
EditorUtility.SetDirty(db);
|
|
|
|
|
|
|
|
|
|
|
|
// 重新 sync editor lists
|
|
|
|
|
|
SyncEditorListsWithTables();
|
|
|
|
|
|
|
|
|
|
|
|
showAddField = false;
|
|
|
|
|
|
tabIndex = tablesProp.arraySize - 1; // 选择新建的 tab
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if (GUILayout.Button("Cancel", EditorStyles.toolbarButton, GUILayout.Width(80)))
|
|
|
|
|
|
{
|
|
|
|
|
|
showAddField = false;
|
|
|
|
|
|
newTableName = "";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.Space(6);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 绘制标签行(来自 tables 的 deviceName),但不再包含 Settings(Settings 已在上方 toolbar)
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
|
|
|
|
|
int tablesCount = tablesProp.arraySize;
|
|
|
|
|
|
for (int i = 0; i < tablesCount; ++i)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
var t = tablesProp.GetArrayElementAtIndex(i);
|
|
|
|
|
|
var nameProp = t.FindPropertyRelative("deviceName");
|
|
|
|
|
|
string name = nameProp != null ? nameProp.stringValue : ("Table " + i);
|
|
|
|
|
|
bool selected = (tabIndex == i);
|
|
|
|
|
|
// 采用 toolbarButton 风格的 toggle
|
|
|
|
|
|
if (GUILayout.Toggle(selected, name, EditorStyles.toolbarButton, GUILayout.MinWidth(60)))
|
|
|
|
|
|
{
|
|
|
|
|
|
tabIndex = i;
|
|
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 每个表右侧加一个小删除按钮(Settings 不在这里)
|
|
|
|
|
|
if (GUILayout.Button("×", EditorStyles.toolbarButton, GUILayout.Width(22)))
|
|
|
|
|
|
{
|
|
|
|
|
|
// 二次确认
|
|
|
|
|
|
if (EditorUtility.DisplayDialog("Delete Table?",
|
|
|
|
|
|
$"Delete table '{name}' and all its entries? This cannot be undone.", "Delete", "Cancel"))
|
|
|
|
|
|
{
|
|
|
|
|
|
tablesProp.DeleteArrayElementAtIndex(i);
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
EditorUtility.SetDirty(db);
|
|
|
|
|
|
|
|
|
|
|
|
// 调整 tabIndex 与 editor 状态
|
|
|
|
|
|
SyncEditorListsWithTables();
|
|
|
|
|
|
tabIndex = Mathf.Clamp(tabIndex, 0, Math.Max(0, tablesProp.arraySize - 1));
|
|
|
|
|
|
return; // 直接返回防止继续绘制已修改的 serializedObject
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
EditorGUILayout.EndHorizontal();
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
EditorGUILayout.Space(8);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 绘制选中页内容(如果是 Settings 或某个 table)
|
|
|
|
|
|
EditorGUILayout.BeginVertical("box");
|
|
|
|
|
|
if (tabIndex == tablesProp.arraySize)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Settings 页
|
|
|
|
|
|
EditorGUILayout.LabelField("Settings", EditorStyles.boldLabel);
|
2025-12-05 20:57:29 +08:00
|
|
|
|
EditorGUILayout.Space(4);
|
2025-12-10 17:38:31 +08:00
|
|
|
|
EditorGUILayout.PropertyField(placeholderSpriteProp, new GUIContent("Placeholder Sprite"));
|
|
|
|
|
|
Sprite placeholder = placeholderSpriteProp.objectReferenceValue as Sprite;
|
|
|
|
|
|
EditorGUILayout.Space(6);
|
|
|
|
|
|
EditorGUILayout.LabelField("Preview", EditorStyles.miniBoldLabel);
|
|
|
|
|
|
if (placeholder != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Texture2D preview = AssetPreview.GetAssetPreview(placeholder);
|
|
|
|
|
|
if (preview == null) preview = AssetPreview.GetMiniThumbnail(placeholder);
|
|
|
|
|
|
if (preview != null) GUILayout.Label(preview, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
|
|
|
|
|
|
else EditorGUILayout.ObjectField(placeholder, typeof(Sprite), false, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorGUILayout.HelpBox("No placeholder sprite assigned. If FindEntryByControlPath receives an empty path, it will return null.", MessageType.Info);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
// Table 页
|
|
|
|
|
|
if (tabIndex < 0 || tabIndex >= tablesProp.arraySize)
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorGUILayout.HelpBox("Invalid table index.", MessageType.Error);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
var tableProp = tablesProp.GetArrayElementAtIndex(tabIndex);
|
|
|
|
|
|
// 去掉顶部显示 table 名称的 label
|
|
|
|
|
|
// var nameProp = tableProp.FindPropertyRelative("deviceName");
|
|
|
|
|
|
// string tableName = nameProp != null ? nameProp.stringValue : $"Table {tabIndex}";
|
|
|
|
|
|
// EditorGUILayout.LabelField(tableName, EditorStyles.boldLabel);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// Ensure editor lists 长度一致
|
|
|
|
|
|
EnsureEditorListsLength();
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 搜索框:尽量使用 EditorStyles.toolbarSearchField(去掉左侧标题和 clear 按钮)
|
|
|
|
|
|
GUILayout.BeginHorizontal();
|
|
|
|
|
|
GUIStyle searchStyle = EditorStyles.toolbarSearchField ?? EditorStyles.textField;
|
|
|
|
|
|
searchStrings[tabIndex] = GUILayout.TextField(searchStrings[tabIndex] ?? "", searchStyle);
|
|
|
|
|
|
GUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.Space(6);
|
2025-12-09 20:31:44 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 将 TMP Sprite Asset 的选择框 与 Parse / Clear 按钮 水平显示
|
|
|
|
|
|
var tmpAssetProp = tableProp.FindPropertyRelative("tmpAsset");
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
|
|
GUILayout.Label("TMP Sprite Asset", GUILayout.Width(140));
|
|
|
|
|
|
EditorGUILayout.PropertyField(tmpAssetProp, GUIContent.none, GUILayout.ExpandWidth(true));
|
|
|
|
|
|
if (GUILayout.Button("Parse TMP Asset", GUILayout.Width(120))) ParseTMPAssetIntoTableSerialized(tableProp);
|
|
|
|
|
|
if (GUILayout.Button("Clear", GUILayout.Width(80)))
|
|
|
|
|
|
{
|
|
|
|
|
|
var entriesProp = tableProp.FindPropertyRelative("entries");
|
|
|
|
|
|
if (entriesProp != null) entriesProp.arraySize = 0;
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
EditorUtility.SetDirty(db);
|
|
|
|
|
|
currentPages[tabIndex] = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.Space(6);
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
var entries = tableProp.FindPropertyRelative("entries");
|
|
|
|
|
|
if (entries != null)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
int total = entries.arraySize;
|
|
|
|
|
|
// collect matched indices by searching sprite.name
|
|
|
|
|
|
List<int> matchedIndices = new List<int>();
|
|
|
|
|
|
string query = (searchStrings[tabIndex] ?? "").Trim();
|
|
|
|
|
|
for (int i = 0; i < total; ++i)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
var eProp = entries.GetArrayElementAtIndex(i);
|
|
|
|
|
|
if (eProp == null) continue;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
var spriteProp = eProp.FindPropertyRelative("Sprite");
|
|
|
|
|
|
Sprite s = spriteProp.objectReferenceValue as Sprite;
|
2025-12-10 17:38:31 +08:00
|
|
|
|
string name = s != null ? s.name : "";
|
|
|
|
|
|
if (string.IsNullOrEmpty(query) || name.IndexOf(query, StringComparison.OrdinalIgnoreCase) >= 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
matchedIndices.Add(i);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int matchedTotal = matchedIndices.Count;
|
|
|
|
|
|
int totalPages = Mathf.Max(1, (matchedTotal + itemsPerPage - 1) / itemsPerPage);
|
|
|
|
|
|
currentPages[tabIndex] = Mathf.Clamp(currentPages[tabIndex], 0, totalPages - 1);
|
|
|
|
|
|
|
|
|
|
|
|
// pagination controls (toolbar 风格)
|
|
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
|
|
if (GUILayout.Button("<<", EditorStyles.miniButtonLeft, GUILayout.Width(36))) { currentPages[tabIndex] = 0; }
|
|
|
|
|
|
if (GUILayout.Button("<", EditorStyles.miniButtonMid, GUILayout.Width(36))) { currentPages[tabIndex] = Mathf.Max(0, currentPages[tabIndex] - 1); }
|
|
|
|
|
|
|
|
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
|
|
EditorGUILayout.LabelField(string.Format("Page {0}/{1}", currentPages[tabIndex] + 1, totalPages), GUILayout.Width(120));
|
|
|
|
|
|
GUILayout.FlexibleSpace();
|
|
|
|
|
|
|
|
|
|
|
|
if (GUILayout.Button(">", EditorStyles.miniButtonMid, GUILayout.Width(36))) { currentPages[tabIndex] = Mathf.Min(totalPages - 1, currentPages[tabIndex] + 1); }
|
|
|
|
|
|
if (GUILayout.Button(">>", EditorStyles.miniButtonRight, GUILayout.Width(36))) { currentPages[tabIndex] = totalPages - 1; }
|
|
|
|
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.Space(4);
|
|
|
|
|
|
|
|
|
|
|
|
int start = currentPages[tabIndex] * itemsPerPage;
|
|
|
|
|
|
int end = Math.Min(start + itemsPerPage, matchedTotal);
|
|
|
|
|
|
|
|
|
|
|
|
for (int mi = start; mi < end; ++mi)
|
|
|
|
|
|
{
|
|
|
|
|
|
int i = matchedIndices[mi];
|
|
|
|
|
|
var eProp = entries.GetArrayElementAtIndex(i);
|
|
|
|
|
|
if (eProp == null) continue;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 使用稍小的间距并减小 preview 大小
|
|
|
|
|
|
using (new EditorGUILayout.HorizontalScope("box"))
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 左列 sprite 预览(固定宽)
|
|
|
|
|
|
using (new EditorGUILayout.VerticalScope(GUILayout.Width(80)))
|
2025-12-05 20:57:29 +08:00
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
var spriteProp = eProp.FindPropertyRelative("Sprite");
|
|
|
|
|
|
Sprite s = spriteProp.objectReferenceValue as Sprite;
|
|
|
|
|
|
EditorGUILayout.LabelField(s != null ? s.name : "<missing>", EditorStyles.boldLabel);
|
|
|
|
|
|
|
|
|
|
|
|
if (s != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
Texture2D preview = AssetPreview.GetAssetPreview(s);
|
|
|
|
|
|
if (preview == null) preview = AssetPreview.GetMiniThumbnail(s);
|
|
|
|
|
|
if (preview != null)
|
|
|
|
|
|
{
|
|
|
|
|
|
GUILayout.Label(preview, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorGUILayout.PropertyField(spriteProp, GUIContent.none, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorGUILayout.PropertyField(spriteProp, GUIContent.none, GUILayout.Width(previewSize), GUILayout.Height(previewSize));
|
|
|
|
|
|
}
|
2025-12-05 20:57:29 +08:00
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// 右列 action 字段
|
|
|
|
|
|
EditorGUILayout.BeginVertical();
|
|
|
|
|
|
var actionProp = eProp.FindPropertyRelative("action");
|
|
|
|
|
|
EditorGUILayout.Space(2);
|
|
|
|
|
|
EditorGUILayout.PropertyField(actionProp, GUIContent.none, GUILayout.ExpandWidth(true));
|
|
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
EditorGUILayout.Space(4);
|
|
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
if (matchedTotal == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
EditorGUILayout.HelpBox("No entries match the search.", MessageType.Info);
|
|
|
|
|
|
}
|
2025-12-05 20:57:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
|
|
|
|
|
EditorGUILayout.EndVertical();
|
|
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
EditorGUILayout.Space(6);
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// Ensure table with name exists (用于初次迁移)
|
|
|
|
|
|
void EnsureDefaultTable(string name)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-05 20:57:29 +08:00
|
|
|
|
if (tablesProp == null) return;
|
|
|
|
|
|
for (int i = 0; i < tablesProp.arraySize; ++i)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-05 20:57:29 +08:00
|
|
|
|
var t = tablesProp.GetArrayElementAtIndex(i);
|
2025-12-10 17:38:31 +08:00
|
|
|
|
var nameProp = t.FindPropertyRelative("deviceName");
|
|
|
|
|
|
if (nameProp != null && string.Equals(nameProp.stringValue, name, StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
|
return;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int idx = tablesProp.arraySize;
|
2025-12-10 17:38:31 +08:00
|
|
|
|
tablesProp.InsertArrayElementAtIndex(idx);
|
2025-12-05 20:57:29 +08:00
|
|
|
|
var newTable = tablesProp.GetArrayElementAtIndex(idx);
|
2025-12-10 17:38:31 +08:00
|
|
|
|
var deviceNameProp = newTable.FindPropertyRelative("deviceName");
|
|
|
|
|
|
if (deviceNameProp != null) deviceNameProp.stringValue = name;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
var tmpAssetProp = newTable.FindPropertyRelative("tmpAsset");
|
|
|
|
|
|
if (tmpAssetProp != null) tmpAssetProp.objectReferenceValue = null;
|
|
|
|
|
|
var entriesProp = newTable.FindPropertyRelative("entries");
|
|
|
|
|
|
if (entriesProp != null) entriesProp.arraySize = 0;
|
|
|
|
|
|
|
|
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
|
|
|
|
|
EditorUtility.SetDirty(db);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
void SyncEditorListsWithTables()
|
2025-12-05 20:57:29 +08:00
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
int count = tablesProp != null ? tablesProp.arraySize : 0;
|
|
|
|
|
|
if (searchStrings == null) searchStrings = new List<string>();
|
|
|
|
|
|
if (currentPages == null) currentPages = new List<int>();
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
while (searchStrings.Count < count) searchStrings.Add("");
|
|
|
|
|
|
while (currentPages.Count < count) currentPages.Add(0);
|
|
|
|
|
|
|
|
|
|
|
|
while (searchStrings.Count > count) searchStrings.RemoveAt(searchStrings.Count - 1);
|
|
|
|
|
|
while (currentPages.Count > count) currentPages.RemoveAt(currentPages.Count - 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void EnsureEditorListsLength()
|
|
|
|
|
|
{
|
|
|
|
|
|
if (tablesProp == null) return;
|
|
|
|
|
|
SyncEditorListsWithTables();
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-10 17:38:31 +08:00
|
|
|
|
// Parse TMP Sprite Asset into table (保持你原有实现)
|
2025-12-05 20:57:29 +08:00
|
|
|
|
void ParseTMPAssetIntoTableSerialized(SerializedProperty tableProp)
|
2025-12-05 19:04:53 +08:00
|
|
|
|
{
|
2025-12-05 20:57:29 +08:00
|
|
|
|
if (tableProp == null) return;
|
|
|
|
|
|
var tmpAssetProp = tableProp.FindPropertyRelative("tmpAsset");
|
|
|
|
|
|
var asset = tmpAssetProp.objectReferenceValue as TMP_SpriteAsset;
|
|
|
|
|
|
if (asset == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
var entriesProp = tableProp.FindPropertyRelative("entries");
|
|
|
|
|
|
if (entriesProp == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
entriesProp.arraySize = 0;
|
|
|
|
|
|
|
2025-12-05 19:04:53 +08:00
|
|
|
|
var chars = asset.spriteCharacterTable;
|
|
|
|
|
|
SpriteAtlas atlas = GetSpriteAtlasFromTMP(asset);
|
|
|
|
|
|
string assetPath = AssetDatabase.GetAssetPath(asset);
|
|
|
|
|
|
string assetFolder = Path.GetDirectoryName(assetPath);
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-05 19:04:53 +08:00
|
|
|
|
for (int i = 0; i < chars.Count; ++i)
|
|
|
|
|
|
{
|
|
|
|
|
|
var ch = chars[i];
|
|
|
|
|
|
if (ch == null) continue;
|
|
|
|
|
|
var name = ch.name;
|
|
|
|
|
|
if (string.IsNullOrEmpty(name)) continue;
|
2025-12-05 20:57:29 +08:00
|
|
|
|
|
2025-12-05 19:04:53 +08:00
|
|
|
|
Sprite s = null;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var glyph = ch.glyph as TMP_SpriteGlyph;
|
|
|
|
|
|
if (glyph != null && glyph.sprite != null) s = glyph.sprite;
|
|
|
|
|
|
}
|
2025-12-10 17:38:31 +08:00
|
|
|
|
catch { }
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
|
|
|
|
|
if (s == null && atlas != null)
|
|
|
|
|
|
{
|
2025-12-10 17:38:31 +08:00
|
|
|
|
try { s = atlas.GetSprite(name); }
|
|
|
|
|
|
catch { s = null; }
|
2025-12-05 19:04:53 +08:00
|
|
|
|
|
|
|
|
|
|
if (s == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2025-12-05 20:57:29 +08:00
|
|
|
|
var m = typeof(SpriteAtlas).GetMethod("GetSprite", new Type[] { typeof(string) });
|
2025-12-05 19:04:53 +08:00
|
|
|
|
if (m != null) s = m.Invoke(atlas, new object[] { name }) as Sprite;
|
|
|
|
|
|
}
|
2025-12-10 17:38:31 +08:00
|
|
|
|
catch { }
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (s == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
string[] scoped = AssetDatabase.FindAssets(name + " t:Sprite", new[] { assetFolder });
|
|
|
|
|
|
if (scoped != null && scoped.Length > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var g in scoped)
|
|
|
|
|
|
{
|
|
|
|
|
|
var p = AssetDatabase.GUIDToAssetPath(g);
|
|
|
|
|
|
var sp = AssetDatabase.LoadAssetAtPath<Sprite>(p);
|
|
|
|
|
|
if (sp != null && sp.name == name)
|
|
|
|
|
|
{
|
|
|
|
|
|
s = sp;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (s == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
string[] all = AssetDatabase.FindAssets(name + " t:Sprite");
|
|
|
|
|
|
if (all != null && all.Length > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
foreach (var g in all)
|
|
|
|
|
|
{
|
|
|
|
|
|
var p = AssetDatabase.GUIDToAssetPath(g);
|
|
|
|
|
|
var sp = AssetDatabase.LoadAssetAtPath<Sprite>(p);
|
|
|
|
|
|
if (sp != null && sp.name == name)
|
|
|
|
|
|
{
|
|
|
|
|
|
s = sp;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 20:57:29 +08:00
|
|
|
|
int newIndex = entriesProp.arraySize;
|
|
|
|
|
|
entriesProp.InsertArrayElementAtIndex(newIndex);
|
|
|
|
|
|
var entryProp = entriesProp.GetArrayElementAtIndex(newIndex);
|
|
|
|
|
|
var spriteProp = entryProp.FindPropertyRelative("Sprite");
|
|
|
|
|
|
if (spriteProp != null) spriteProp.objectReferenceValue = s;
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-05 20:57:29 +08:00
|
|
|
|
serializedObject.ApplyModifiedProperties();
|
2025-12-05 19:04:53 +08:00
|
|
|
|
EditorUtility.SetDirty(db);
|
|
|
|
|
|
AssetDatabase.SaveAssets();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
SpriteAtlas GetSpriteAtlasFromTMP(TMP_SpriteAsset asset)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (asset == null) return null;
|
|
|
|
|
|
var t = asset.GetType();
|
|
|
|
|
|
foreach (var f in t.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (typeof(SpriteAtlas).IsAssignableFrom(f.FieldType))
|
|
|
|
|
|
{
|
|
|
|
|
|
var val = f.GetValue(asset) as SpriteAtlas;
|
|
|
|
|
|
if (val != null) return val;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var p in t.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (typeof(SpriteAtlas).IsAssignableFrom(p.PropertyType))
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var val = p.GetValue(asset, null) as SpriteAtlas;
|
|
|
|
|
|
if (val != null) return val;
|
|
|
|
|
|
}
|
2025-12-10 17:38:31 +08:00
|
|
|
|
catch { }
|
2025-12-05 19:04:53 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|