11
This commit is contained in:
parent
86a110aacc
commit
0a3fd47070
16
Client/Assets/InputGlyphDatabase.asset
Normal file
16
Client/Assets/InputGlyphDatabase.asset
Normal file
@ -0,0 +1,16 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 53ed017cef844d11842ba16553c6391d, type: 3}
|
||||
m_Name: InputGlyphDatabase
|
||||
m_EditorClassIdentifier:
|
||||
tables: []
|
||||
placeholderSprite: {fileID: 0}
|
||||
8
Client/Assets/InputGlyphDatabase.asset.meta
Normal file
8
Client/Assets/InputGlyphDatabase.asset.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e43c1733d8e85a5419c0754bbd597e3b
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
@ -19,15 +18,22 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
private const float PreviewSize = 52f;
|
||||
private const float ListPreviewSize = 56f;
|
||||
private const int MaxValidationIssuesToShow = 10;
|
||||
private const int DefaultEntriesPerPage = 50;
|
||||
|
||||
private static readonly string[] DefaultTableNames = { "Keyboard", "Xbox", "PlayStation", "Other" };
|
||||
private static readonly int[] EntriesPerPageOptions = { 25, 50, 100, 200 };
|
||||
private static readonly string[] EntriesPerPageLabels = { "25 / 页", "50 / 页", "100 / 页", "200 / 页" };
|
||||
|
||||
private sealed class TableEditorState
|
||||
{
|
||||
public Sprite PendingSprite;
|
||||
public bool ShowValidation = true;
|
||||
public ReorderableList EntriesList;
|
||||
public string EntriesPropertyPath;
|
||||
public string EntrySearch = string.Empty;
|
||||
public int CurrentPage;
|
||||
public int EntriesPerPage = DefaultEntriesPerPage;
|
||||
public readonly List<int> FilteredEntryIndices = new();
|
||||
public string CachedSearch = string.Empty;
|
||||
public int CachedEntryCount = -1;
|
||||
}
|
||||
|
||||
private readonly List<TableEditorState> _tableStates = new();
|
||||
@ -92,6 +98,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
InvalidateAllEntryViews();
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
}
|
||||
@ -103,6 +110,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
if (GUILayout.Button("Save Asset", EditorStyles.toolbarButton, GUILayout.Width(90f)))
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InvalidateAllEntryViews();
|
||||
NotifyDatabaseChanged(true);
|
||||
}
|
||||
|
||||
@ -217,13 +225,13 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
private void DrawDatabaseValidationPanel()
|
||||
{
|
||||
List<string> issues = CollectDatabaseValidationIssues();
|
||||
string title = issues.Count == 0 ? "Database Validation" : $"Database Validation ({issues.Count})";
|
||||
string title = issues.Count == 0 ? "数据库校验" : $"数据库校验 ({issues.Count})";
|
||||
_showDatabaseValidation = EditorGUILayout.BeginFoldoutHeaderGroup(_showDatabaseValidation, title);
|
||||
if (_showDatabaseValidation)
|
||||
{
|
||||
if (issues.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No database-level issues found.", MessageType.Info);
|
||||
EditorGUILayout.HelpBox("未发现数据库级别的问题。", MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -314,13 +322,13 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
private void DrawTableValidationPanel(int tableIndex, TableEditorState state)
|
||||
{
|
||||
List<string> issues = CollectTableValidationIssues(tableIndex);
|
||||
string title = issues.Count == 0 ? "Validation" : $"Validation ({issues.Count})";
|
||||
string title = issues.Count == 0 ? "校验结果" : $"校验结果 ({issues.Count})";
|
||||
state.ShowValidation = EditorGUILayout.BeginFoldoutHeaderGroup(state.ShowValidation, title);
|
||||
if (state.ShowValidation)
|
||||
{
|
||||
if (issues.Count == 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox("No table-level issues found.", MessageType.Info);
|
||||
EditorGUILayout.HelpBox("未发现当前表的问题。", MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -341,120 +349,198 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
|
||||
if (issues.Count > visibleCount)
|
||||
{
|
||||
EditorGUILayout.HelpBox($"{issues.Count - visibleCount} more issues are hidden to keep the inspector readable.", MessageType.None);
|
||||
EditorGUILayout.HelpBox($"还有 {issues.Count - visibleCount} 条问题未展开显示,以保持检视面板可读。", MessageType.None);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEntriesList(int tableIndex, SerializedProperty entriesProperty)
|
||||
{
|
||||
EditorGUILayout.LabelField("Entries", EditorStyles.boldLabel);
|
||||
ReorderableList list = GetEntriesList(tableIndex, entriesProperty);
|
||||
list.DoLayoutList();
|
||||
}
|
||||
private ReorderableList GetEntriesList(int tableIndex, SerializedProperty entriesProperty)
|
||||
{
|
||||
TableEditorState state = _tableStates[tableIndex];
|
||||
if (state.EntriesList != null && state.EntriesPropertyPath == entriesProperty.propertyPath)
|
||||
EditorGUILayout.LabelField("Entries", EditorStyles.boldLabel);
|
||||
DrawEntriesControls(state, entriesProperty.arraySize);
|
||||
|
||||
if (entriesProperty.arraySize == 0)
|
||||
{
|
||||
return state.EntriesList;
|
||||
EditorGUILayout.HelpBox("当前表里还没有任何条目。", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
ReorderableList list = new ReorderableList(serializedObject, entriesProperty, true, true, false, true);
|
||||
state.EntriesList = list;
|
||||
state.EntriesPropertyPath = entriesProperty.propertyPath;
|
||||
|
||||
list.drawHeaderCallback = rect =>
|
||||
List<int> filteredIndices = GetFilteredEntryIndices(tableIndex, entriesProperty, state);
|
||||
if (filteredIndices.Count == 0)
|
||||
{
|
||||
EditorGUI.LabelField(rect, $"Entries ({entriesProperty.arraySize}) - drag to reorder");
|
||||
};
|
||||
EditorGUILayout.HelpBox("没有匹配搜索条件的条目。", MessageType.Info);
|
||||
return;
|
||||
}
|
||||
|
||||
list.elementHeightCallback = index =>
|
||||
int entriesPerPage = Mathf.Max(1, state.EntriesPerPage);
|
||||
int totalPages = Mathf.Max(1, Mathf.CeilToInt(filteredIndices.Count / (float)entriesPerPage));
|
||||
state.CurrentPage = Mathf.Clamp(state.CurrentPage, 0, totalPages - 1);
|
||||
|
||||
DrawEntriesPagination(state, filteredIndices.Count, entriesProperty.arraySize);
|
||||
EditorGUILayout.Space(4f);
|
||||
|
||||
int startIndex = state.CurrentPage * entriesPerPage;
|
||||
int endIndex = Mathf.Min(startIndex + entriesPerPage, filteredIndices.Count);
|
||||
for (int i = startIndex; i < endIndex; i++)
|
||||
{
|
||||
if (index < 0 || index >= entriesProperty.arraySize)
|
||||
{
|
||||
return EditorGUIUtility.singleLineHeight + 8f;
|
||||
}
|
||||
int entryIndex = filteredIndices[i];
|
||||
DrawEntryElement(tableIndex, entryIndex, entriesProperty.GetArrayElementAtIndex(entryIndex));
|
||||
EditorGUILayout.Space(4f);
|
||||
}
|
||||
|
||||
return GetEntryElementHeight(entriesProperty.GetArrayElementAtIndex(index));
|
||||
};
|
||||
|
||||
list.drawElementCallback = (rect, index, active, focused) =>
|
||||
if (totalPages > 1)
|
||||
{
|
||||
if (index < 0 || index >= entriesProperty.arraySize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DrawEntryElement(rect, entriesProperty.GetArrayElementAtIndex(index));
|
||||
};
|
||||
|
||||
list.onReorderCallback = _ =>
|
||||
{
|
||||
ApplyPendingInspectorChanges();
|
||||
};
|
||||
|
||||
list.onRemoveCallback = currentList =>
|
||||
{
|
||||
if (currentList.index < 0 || currentList.index >= entriesProperty.arraySize)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EditorUtility.DisplayDialog("Remove Entry", "Remove the selected entry from the table?", "Remove", "Cancel"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RecordObject(_database, "Remove glyph entry");
|
||||
entriesProperty.DeleteArrayElementAtIndex(currentList.index);
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
InvalidateEntriesList(tableIndex);
|
||||
NotifyDatabaseChanged();
|
||||
};
|
||||
|
||||
return list;
|
||||
DrawEntriesPagination(state, filteredIndices.Count, entriesProperty.arraySize);
|
||||
}
|
||||
}
|
||||
|
||||
private float GetEntryElementHeight(SerializedProperty entryProperty)
|
||||
private void DrawEntriesControls(TableEditorState state, int totalEntries)
|
||||
{
|
||||
SerializedProperty spriteProperty = entryProperty.FindPropertyRelative(EntrySpritePropertyName);
|
||||
SerializedProperty actionProperty = entryProperty.FindPropertyRelative(EntryActionPropertyName);
|
||||
float spriteHeight = EditorGUI.GetPropertyHeight(spriteProperty, true);
|
||||
float actionHeight = EditorGUI.GetPropertyHeight(actionProperty, true);
|
||||
float fieldHeight = spriteHeight + actionHeight + 10f;
|
||||
return Mathf.Max(ListPreviewSize + 10f, fieldHeight + 8f);
|
||||
string search = EditorGUILayout.TextField("Search", state.EntrySearch);
|
||||
if (!string.Equals(search, state.EntrySearch, StringComparison.Ordinal))
|
||||
{
|
||||
state.EntrySearch = search;
|
||||
state.CurrentPage = 0;
|
||||
InvalidateEntryView(state);
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
int entriesPerPage = EditorGUILayout.IntPopup("Page Size", state.EntriesPerPage, EntriesPerPageLabels, EntriesPerPageOptions);
|
||||
if (entriesPerPage != state.EntriesPerPage)
|
||||
{
|
||||
state.EntriesPerPage = entriesPerPage;
|
||||
state.CurrentPage = 0;
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.LabelField($"总数: {totalEntries}", EditorStyles.miniLabel, GUILayout.Width(80f));
|
||||
|
||||
using (new EditorGUI.DisabledScope(string.IsNullOrEmpty(state.EntrySearch)))
|
||||
{
|
||||
if (GUILayout.Button("Clear Search", GUILayout.Width(100f)))
|
||||
{
|
||||
state.EntrySearch = string.Empty;
|
||||
state.CurrentPage = 0;
|
||||
InvalidateEntryView(state);
|
||||
GUI.FocusControl(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEntryElement(Rect rect, SerializedProperty entryProperty)
|
||||
private void DrawEntriesPagination(TableEditorState state, int filteredCount, int totalEntries)
|
||||
{
|
||||
int entriesPerPage = Mathf.Max(1, state.EntriesPerPage);
|
||||
int totalPages = Mathf.Max(1, Mathf.CeilToInt(filteredCount / (float)entriesPerPage));
|
||||
int startEntry = filteredCount == 0 ? 0 : state.CurrentPage * entriesPerPage + 1;
|
||||
int endEntry = Mathf.Min(filteredCount, (state.CurrentPage + 1) * entriesPerPage);
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope(EditorStyles.helpBox))
|
||||
{
|
||||
EditorGUILayout.LabelField(
|
||||
$"显示 {startEntry}-{endEntry} / {filteredCount} 条",
|
||||
EditorStyles.miniLabel,
|
||||
GUILayout.Width(140f));
|
||||
|
||||
if (filteredCount != totalEntries)
|
||||
{
|
||||
EditorGUILayout.LabelField($"(筛选自 {totalEntries} 条)", EditorStyles.miniLabel, GUILayout.Width(100f));
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
using (new EditorGUI.DisabledScope(state.CurrentPage <= 0))
|
||||
{
|
||||
if (GUILayout.Button("<<", GUILayout.Width(32f)))
|
||||
{
|
||||
state.CurrentPage = 0;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("<", GUILayout.Width(28f)))
|
||||
{
|
||||
state.CurrentPage--;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Label($"第 {state.CurrentPage + 1} / {totalPages} 页", EditorStyles.miniLabel, GUILayout.Width(72f));
|
||||
|
||||
using (new EditorGUI.DisabledScope(state.CurrentPage >= totalPages - 1))
|
||||
{
|
||||
if (GUILayout.Button(">", GUILayout.Width(28f)))
|
||||
{
|
||||
state.CurrentPage++;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(">>", GUILayout.Width(32f)))
|
||||
{
|
||||
state.CurrentPage = totalPages - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEntryElement(int tableIndex, int entryIndex, SerializedProperty entryProperty)
|
||||
{
|
||||
SerializedProperty spriteProperty = entryProperty.FindPropertyRelative(EntrySpritePropertyName);
|
||||
SerializedProperty actionProperty = entryProperty.FindPropertyRelative(EntryActionPropertyName);
|
||||
Sprite sprite = spriteProperty.objectReferenceValue as Sprite;
|
||||
|
||||
rect.y += 4f;
|
||||
rect.height -= 8f;
|
||||
|
||||
Rect previewRect = new Rect(rect.x, rect.y, ListPreviewSize, ListPreviewSize);
|
||||
Rect fieldsRect = new Rect(rect.x + ListPreviewSize + 8f, rect.y, rect.width - ListPreviewSize - 44f, rect.height);
|
||||
Rect pingRect = new Rect(rect.xMax - 30f, rect.y, 30f, EditorGUIUtility.singleLineHeight);
|
||||
|
||||
DrawSpritePreview(previewRect, sprite);
|
||||
|
||||
float currentY = fieldsRect.y;
|
||||
float spriteHeight = EditorGUI.GetPropertyHeight(spriteProperty, true);
|
||||
Rect spriteRect = new Rect(fieldsRect.x, currentY, fieldsRect.width, spriteHeight);
|
||||
EditorGUI.PropertyField(spriteRect, spriteProperty, new GUIContent("Sprite"), true);
|
||||
|
||||
currentY += spriteHeight + 4f;
|
||||
float actionHeight = EditorGUI.GetPropertyHeight(actionProperty, true);
|
||||
Rect actionRect = new Rect(fieldsRect.x, currentY, fieldsRect.width, actionHeight);
|
||||
EditorGUI.PropertyField(actionRect, actionProperty, new GUIContent("Action"), true);
|
||||
|
||||
using (new EditorGUI.DisabledScope(sprite == null))
|
||||
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
|
||||
{
|
||||
if (GUI.Button(pingRect, "Ping"))
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
EditorGUIUtility.PingObject(sprite);
|
||||
GUILayout.Label(GetEntryTitle(tableIndex, entryIndex), EditorStyles.boldLabel);
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
using (new EditorGUI.DisabledScope(entryIndex <= 0))
|
||||
{
|
||||
if (GUILayout.Button("↑", GUILayout.Width(28f)))
|
||||
{
|
||||
ApplyPendingInspectorChanges();
|
||||
MoveEntry(tableIndex, entryIndex, entryIndex - 1);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(entryIndex >= _database.tables[tableIndex].entries.Count - 1))
|
||||
{
|
||||
if (GUILayout.Button("↓", GUILayout.Width(28f)))
|
||||
{
|
||||
ApplyPendingInspectorChanges();
|
||||
MoveEntry(tableIndex, entryIndex, entryIndex + 1);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUI.DisabledScope(sprite == null))
|
||||
{
|
||||
if (GUILayout.Button("Ping", GUILayout.Width(48f)))
|
||||
{
|
||||
EditorGUIUtility.PingObject(sprite);
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Remove", GUILayout.Width(64f)))
|
||||
{
|
||||
if (EditorUtility.DisplayDialog("Remove Entry", "Remove the selected entry from the table?", "Remove", "Cancel"))
|
||||
{
|
||||
ApplyPendingInspectorChanges();
|
||||
RemoveEntry(tableIndex, entryIndex);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
Rect previewRect = GUILayoutUtility.GetRect(ListPreviewSize, ListPreviewSize, GUILayout.Width(ListPreviewSize), GUILayout.Height(ListPreviewSize));
|
||||
DrawSpritePreview(previewRect, sprite);
|
||||
|
||||
using (new EditorGUILayout.VerticalScope())
|
||||
{
|
||||
EditorGUILayout.PropertyField(spriteProperty, new GUIContent("Sprite"), true);
|
||||
EditorGUILayout.PropertyField(actionProperty, new GUIContent("Action"), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -509,14 +595,14 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
List<string> issues = new();
|
||||
if (_database.tables == null || _database.tables.Count == 0)
|
||||
{
|
||||
issues.Add("The database has no tables. Runtime glyph lookup will always fall back to the placeholder sprite.");
|
||||
issues.Add("数据库中没有任何表,运行时查询将始终回退到占位图标。");
|
||||
return issues;
|
||||
}
|
||||
|
||||
List<string> missingTables = GetMissingDefaultTables();
|
||||
if (missingTables.Count > 0)
|
||||
{
|
||||
issues.Add($"Recommended tables are missing: {string.Join(", ", missingTables)}.");
|
||||
issues.Add($"缺少推荐表: {string.Join(", ", missingTables)}。");
|
||||
}
|
||||
|
||||
HashSet<string> seenNames = new(StringComparer.OrdinalIgnoreCase);
|
||||
@ -526,7 +612,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
string tableName = _database.tables[i] != null ? _database.tables[i].deviceName : string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(tableName))
|
||||
{
|
||||
issues.Add($"Table {i + 1} has an empty device name.");
|
||||
issues.Add($"表 {i + 1} 的设备名称为空。");
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -538,7 +624,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
|
||||
foreach (string duplicateName in duplicateNames)
|
||||
{
|
||||
issues.Add($"Duplicate table name '{duplicateName}' detected.");
|
||||
issues.Add($"检测到重复的表名 '{duplicateName}'。");
|
||||
}
|
||||
|
||||
return issues;
|
||||
@ -549,14 +635,14 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
List<string> issues = new();
|
||||
if (!IsValidTableIndex(tableIndex))
|
||||
{
|
||||
issues.Add("The selected table is invalid.");
|
||||
issues.Add("当前选中的表无效。");
|
||||
return issues;
|
||||
}
|
||||
|
||||
DeviceGlyphTable table = _database.tables[tableIndex];
|
||||
if (table.entries == null || table.entries.Count == 0)
|
||||
{
|
||||
issues.Add("This table has no entries.");
|
||||
issues.Add("当前表没有任何条目。");
|
||||
return issues;
|
||||
}
|
||||
|
||||
@ -571,7 +657,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
GlyphEntry entry = table.entries[i];
|
||||
if (entry == null)
|
||||
{
|
||||
issues.Add($"Entry {i + 1} is null.");
|
||||
issues.Add($"条目 {i + 1} 为空。");
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -601,16 +687,17 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
|
||||
if (missingSpriteCount > 0)
|
||||
{
|
||||
issues.Add($"{missingSpriteCount} entr{(missingSpriteCount == 1 ? "y has" : "ies have")} no sprite assigned.");
|
||||
issues.Add($"{missingSpriteCount} 个条目未绑定 Sprite。");
|
||||
}
|
||||
|
||||
if (missingActionCount > 0)
|
||||
{
|
||||
issues.Add($"{missingActionCount} entr{(missingActionCount == 1 ? "y has" : "ies have")} no action assigned. Those entries will not participate in runtime path lookup.");
|
||||
issues.Add($"{missingActionCount} 个条目未绑定 Action,这些条目不会参与运行时路径查找。");
|
||||
}
|
||||
|
||||
foreach (string spriteName in duplicateSprites)
|
||||
{
|
||||
issues.Add($"Duplicate sprite name '{spriteName}' found in this table.");
|
||||
issues.Add($"当前表中存在重复的 Sprite 名称 '{spriteName}'。");
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, List<string>> pair in bindingOwners)
|
||||
@ -620,7 +707,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
continue;
|
||||
}
|
||||
|
||||
issues.Add($"Binding '{pair.Key}' is mapped by multiple entries: {string.Join(", ", pair.Value)}. Runtime lookup keeps the first match.");
|
||||
issues.Add($"绑定 '{pair.Key}' 被多个条目共用: {string.Join(", ", pair.Value)}。运行时只会保留第一个匹配项。");
|
||||
}
|
||||
|
||||
return issues;
|
||||
@ -658,7 +745,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
table.entries ??= new List<GlyphEntry>();
|
||||
table.entries.Add(new GlyphEntry { Sprite = sprite, action = null });
|
||||
serializedObject.Update();
|
||||
InvalidateEntriesList(tableIndex);
|
||||
InvalidateEntryView(tableIndex);
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
|
||||
@ -674,7 +761,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
table.entries ??= new List<GlyphEntry>();
|
||||
table.entries.Clear();
|
||||
serializedObject.Update();
|
||||
InvalidateEntriesList(tableIndex);
|
||||
InvalidateEntryView(tableIndex);
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
|
||||
@ -691,7 +778,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
});
|
||||
SyncTableStates();
|
||||
serializedObject.Update();
|
||||
InvalidateAllEntriesLists();
|
||||
InvalidateAllEntryViews();
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
|
||||
@ -707,7 +794,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
SyncTableStates();
|
||||
ClampSelectedTab();
|
||||
serializedObject.Update();
|
||||
InvalidateAllEntriesLists();
|
||||
InvalidateAllEntryViews();
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
|
||||
@ -734,7 +821,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
|
||||
SyncTableStates();
|
||||
serializedObject.Update();
|
||||
InvalidateAllEntriesLists();
|
||||
InvalidateAllEntryViews();
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
|
||||
@ -822,7 +909,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
}
|
||||
|
||||
serializedObject.Update();
|
||||
InvalidateEntriesList(tableIndex);
|
||||
InvalidateEntryView(tableIndex);
|
||||
NotifyDatabaseChanged();
|
||||
Debug.Log($"[InputGlyphDatabase] Merged sprite sheet '{table.spriteSheetTexture.name}' into '{table.deviceName}'. sprites={sprites.Count}, replaced={replaced}, added={added}, total={table.entries.Count}");
|
||||
}
|
||||
@ -831,6 +918,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
{
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
InvalidateAllEntryViews();
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
}
|
||||
@ -865,23 +953,163 @@ public sealed class InputGlyphDatabaseEditor : Editor
|
||||
_selectedTab = Mathf.Clamp(_selectedTab, 0, maxIndex);
|
||||
}
|
||||
|
||||
private void InvalidateEntriesList(int tableIndex)
|
||||
private void MoveEntry(int tableIndex, int fromIndex, int toIndex)
|
||||
{
|
||||
if (!IsValidTableIndex(tableIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<GlyphEntry> entries = _database.tables[tableIndex].entries;
|
||||
if (entries == null || fromIndex < 0 || fromIndex >= entries.Count || toIndex < 0 || toIndex >= entries.Count || fromIndex == toIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RecordObject(_database, "Move glyph entry");
|
||||
GlyphEntry entry = entries[fromIndex];
|
||||
entries.RemoveAt(fromIndex);
|
||||
entries.Insert(toIndex, entry);
|
||||
serializedObject.Update();
|
||||
InvalidateEntryView(tableIndex);
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
|
||||
private void RemoveEntry(int tableIndex, int entryIndex)
|
||||
{
|
||||
if (!IsValidTableIndex(tableIndex))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
List<GlyphEntry> entries = _database.tables[tableIndex].entries;
|
||||
if (entries == null || entryIndex < 0 || entryIndex >= entries.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Undo.RecordObject(_database, "Remove glyph entry");
|
||||
entries.RemoveAt(entryIndex);
|
||||
serializedObject.Update();
|
||||
InvalidateEntryView(tableIndex);
|
||||
NotifyDatabaseChanged();
|
||||
}
|
||||
|
||||
private List<int> GetFilteredEntryIndices(int tableIndex, SerializedProperty entriesProperty, TableEditorState state)
|
||||
{
|
||||
string search = state.EntrySearch != null ? state.EntrySearch.Trim() : string.Empty;
|
||||
if (state.CachedEntryCount == entriesProperty.arraySize && string.Equals(state.CachedSearch, search, StringComparison.Ordinal))
|
||||
{
|
||||
return state.FilteredEntryIndices;
|
||||
}
|
||||
|
||||
state.FilteredEntryIndices.Clear();
|
||||
for (int i = 0; i < entriesProperty.arraySize; i++)
|
||||
{
|
||||
if (DoesEntryMatchSearch(tableIndex, i, search))
|
||||
{
|
||||
state.FilteredEntryIndices.Add(i);
|
||||
}
|
||||
}
|
||||
|
||||
state.CachedEntryCount = entriesProperty.arraySize;
|
||||
state.CachedSearch = search;
|
||||
return state.FilteredEntryIndices;
|
||||
}
|
||||
|
||||
private bool DoesEntryMatchSearch(int tableIndex, int entryIndex, string search)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(search))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!IsValidTableIndex(tableIndex))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
List<GlyphEntry> entries = _database.tables[tableIndex].entries;
|
||||
if (entries == null || entryIndex < 0 || entryIndex >= entries.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GlyphEntry entry = entries[entryIndex];
|
||||
if (entry == null)
|
||||
{
|
||||
return ContainsIgnoreCase("null", search);
|
||||
}
|
||||
|
||||
if (ContainsIgnoreCase(entry.Sprite != null ? entry.Sprite.name : string.Empty, search)
|
||||
|| ContainsIgnoreCase(entry.action != null ? entry.action.name : string.Empty, search))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (entry.action == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < entry.action.bindings.Count; i++)
|
||||
{
|
||||
InputBinding binding = entry.action.bindings[i];
|
||||
if (ContainsIgnoreCase(binding.path, search) || ContainsIgnoreCase(binding.effectivePath, search))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool ContainsIgnoreCase(string value, string search)
|
||||
{
|
||||
return !string.IsNullOrEmpty(value) && value.IndexOf(search, StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
private string GetEntryTitle(int tableIndex, int entryIndex)
|
||||
{
|
||||
if (!IsValidTableIndex(tableIndex))
|
||||
{
|
||||
return $"Entry #{entryIndex + 1}";
|
||||
}
|
||||
|
||||
List<GlyphEntry> entries = _database.tables[tableIndex].entries;
|
||||
if (entries == null || entryIndex < 0 || entryIndex >= entries.Count)
|
||||
{
|
||||
return $"Entry #{entryIndex + 1}";
|
||||
}
|
||||
|
||||
GlyphEntry entry = entries[entryIndex];
|
||||
string spriteName = entry?.Sprite != null ? entry.Sprite.name : "No Sprite";
|
||||
string actionName = entry?.action != null ? entry.action.name : "No Action";
|
||||
return $"#{entryIndex + 1} {spriteName} / {actionName}";
|
||||
}
|
||||
|
||||
private void InvalidateEntryView(int tableIndex)
|
||||
{
|
||||
if (tableIndex < 0 || tableIndex >= _tableStates.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_tableStates[tableIndex].EntriesList = null;
|
||||
_tableStates[tableIndex].EntriesPropertyPath = null;
|
||||
InvalidateEntryView(_tableStates[tableIndex]);
|
||||
}
|
||||
|
||||
private void InvalidateAllEntriesLists()
|
||||
private static void InvalidateEntryView(TableEditorState state)
|
||||
{
|
||||
state.FilteredEntryIndices.Clear();
|
||||
state.CachedSearch = string.Empty;
|
||||
state.CachedEntryCount = -1;
|
||||
}
|
||||
|
||||
private void InvalidateAllEntryViews()
|
||||
{
|
||||
for (int i = 0; i < _tableStates.Count; i++)
|
||||
{
|
||||
_tableStates[i].EntriesList = null;
|
||||
_tableStates[i].EntriesPropertyPath = null;
|
||||
InvalidateEntryView(_tableStates[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit a978c68586cf761c577842532dfa6af5847851a4
|
||||
Subproject commit e7c6b174fe953381196760a54432048990f74c99
|
||||
Loading…
Reference in New Issue
Block a user