This commit is contained in:
陈思海 2026-03-19 16:11:04 +08:00
parent 86a110aacc
commit 0a3fd47070
4 changed files with 372 additions and 120 deletions

View 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}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e43c1733d8e85a5419c0754bbd597e3b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using UnityEditor; using UnityEditor;
using UnityEditorInternal;
using UnityEngine; using UnityEngine;
using UnityEngine.InputSystem; using UnityEngine.InputSystem;
@ -19,15 +18,22 @@ public sealed class InputGlyphDatabaseEditor : Editor
private const float PreviewSize = 52f; private const float PreviewSize = 52f;
private const float ListPreviewSize = 56f; private const float ListPreviewSize = 56f;
private const int MaxValidationIssuesToShow = 10; private const int MaxValidationIssuesToShow = 10;
private const int DefaultEntriesPerPage = 50;
private static readonly string[] DefaultTableNames = { "Keyboard", "Xbox", "PlayStation", "Other" }; 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 private sealed class TableEditorState
{ {
public Sprite PendingSprite; public Sprite PendingSprite;
public bool ShowValidation = true; public bool ShowValidation = true;
public ReorderableList EntriesList; public string EntrySearch = string.Empty;
public string EntriesPropertyPath; 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(); private readonly List<TableEditorState> _tableStates = new();
@ -92,6 +98,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
if (serializedObject.ApplyModifiedProperties()) if (serializedObject.ApplyModifiedProperties())
{ {
InvalidateAllEntryViews();
NotifyDatabaseChanged(); NotifyDatabaseChanged();
} }
} }
@ -103,6 +110,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
if (GUILayout.Button("Save Asset", EditorStyles.toolbarButton, GUILayout.Width(90f))) if (GUILayout.Button("Save Asset", EditorStyles.toolbarButton, GUILayout.Width(90f)))
{ {
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
InvalidateAllEntryViews();
NotifyDatabaseChanged(true); NotifyDatabaseChanged(true);
} }
@ -217,13 +225,13 @@ public sealed class InputGlyphDatabaseEditor : Editor
private void DrawDatabaseValidationPanel() private void DrawDatabaseValidationPanel()
{ {
List<string> issues = CollectDatabaseValidationIssues(); 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); _showDatabaseValidation = EditorGUILayout.BeginFoldoutHeaderGroup(_showDatabaseValidation, title);
if (_showDatabaseValidation) if (_showDatabaseValidation)
{ {
if (issues.Count == 0) if (issues.Count == 0)
{ {
EditorGUILayout.HelpBox("No database-level issues found.", MessageType.Info); EditorGUILayout.HelpBox("未发现数据库级别的问题。", MessageType.Info);
} }
else else
{ {
@ -314,13 +322,13 @@ public sealed class InputGlyphDatabaseEditor : Editor
private void DrawTableValidationPanel(int tableIndex, TableEditorState state) private void DrawTableValidationPanel(int tableIndex, TableEditorState state)
{ {
List<string> issues = CollectTableValidationIssues(tableIndex); 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); state.ShowValidation = EditorGUILayout.BeginFoldoutHeaderGroup(state.ShowValidation, title);
if (state.ShowValidation) if (state.ShowValidation)
{ {
if (issues.Count == 0) if (issues.Count == 0)
{ {
EditorGUILayout.HelpBox("No table-level issues found.", MessageType.Info); EditorGUILayout.HelpBox("未发现当前表的问题。", MessageType.Info);
} }
else else
{ {
@ -341,122 +349,200 @@ public sealed class InputGlyphDatabaseEditor : Editor
if (issues.Count > visibleCount) 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) 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]; TableEditorState state = _tableStates[tableIndex];
if (state.EntriesList != null && state.EntriesPropertyPath == entriesProperty.propertyPath) EditorGUILayout.LabelField("Entries", EditorStyles.boldLabel);
{ DrawEntriesControls(state, entriesProperty.arraySize);
return state.EntriesList;
}
ReorderableList list = new ReorderableList(serializedObject, entriesProperty, true, true, false, true); if (entriesProperty.arraySize == 0)
state.EntriesList = list;
state.EntriesPropertyPath = entriesProperty.propertyPath;
list.drawHeaderCallback = rect =>
{
EditorGUI.LabelField(rect, $"Entries ({entriesProperty.arraySize}) - drag to reorder");
};
list.elementHeightCallback = index =>
{
if (index < 0 || index >= entriesProperty.arraySize)
{
return EditorGUIUtility.singleLineHeight + 8f;
}
return GetEntryElementHeight(entriesProperty.GetArrayElementAtIndex(index));
};
list.drawElementCallback = (rect, index, active, focused) =>
{
if (index < 0 || index >= entriesProperty.arraySize)
{ {
EditorGUILayout.HelpBox("当前表里还没有任何条目。", MessageType.Info);
return; return;
} }
DrawEntryElement(rect, entriesProperty.GetArrayElementAtIndex(index)); List<int> filteredIndices = GetFilteredEntryIndices(tableIndex, entriesProperty, state);
}; if (filteredIndices.Count == 0)
list.onReorderCallback = _ =>
{
ApplyPendingInspectorChanges();
};
list.onRemoveCallback = currentList =>
{
if (currentList.index < 0 || currentList.index >= entriesProperty.arraySize)
{ {
EditorGUILayout.HelpBox("没有匹配搜索条件的条目。", MessageType.Info);
return; return;
} }
if (!EditorUtility.DisplayDialog("Remove Entry", "Remove the selected entry from the table?", "Remove", "Cancel")) 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++)
{ {
return; int entryIndex = filteredIndices[i];
DrawEntryElement(tableIndex, entryIndex, entriesProperty.GetArrayElementAtIndex(entryIndex));
EditorGUILayout.Space(4f);
} }
Undo.RecordObject(_database, "Remove glyph entry"); if (totalPages > 1)
entriesProperty.DeleteArrayElementAtIndex(currentList.index);
serializedObject.ApplyModifiedProperties();
InvalidateEntriesList(tableIndex);
NotifyDatabaseChanged();
};
return list;
}
private float GetEntryElementHeight(SerializedProperty entryProperty)
{ {
SerializedProperty spriteProperty = entryProperty.FindPropertyRelative(EntrySpritePropertyName); DrawEntriesPagination(state, filteredIndices.Count, entriesProperty.arraySize);
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);
} }
private void DrawEntryElement(Rect rect, SerializedProperty entryProperty) private void DrawEntriesControls(TableEditorState state, int totalEntries)
{
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 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 spriteProperty = entryProperty.FindPropertyRelative(EntrySpritePropertyName);
SerializedProperty actionProperty = entryProperty.FindPropertyRelative(EntryActionPropertyName); SerializedProperty actionProperty = entryProperty.FindPropertyRelative(EntryActionPropertyName);
Sprite sprite = spriteProperty.objectReferenceValue as Sprite; Sprite sprite = spriteProperty.objectReferenceValue as Sprite;
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
using (new EditorGUILayout.HorizontalScope())
{
GUILayout.Label(GetEntryTitle(tableIndex, entryIndex), EditorStyles.boldLabel);
GUILayout.FlexibleSpace();
rect.y += 4f; using (new EditorGUI.DisabledScope(entryIndex <= 0))
rect.height -= 8f; {
if (GUILayout.Button("↑", GUILayout.Width(28f)))
{
ApplyPendingInspectorChanges();
MoveEntry(tableIndex, entryIndex, entryIndex - 1);
GUIUtility.ExitGUI();
}
}
Rect previewRect = new Rect(rect.x, rect.y, ListPreviewSize, ListPreviewSize); using (new EditorGUI.DisabledScope(entryIndex >= _database.tables[tableIndex].entries.Count - 1))
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); if (GUILayout.Button("↓", GUILayout.Width(28f)))
{
DrawSpritePreview(previewRect, sprite); ApplyPendingInspectorChanges();
MoveEntry(tableIndex, entryIndex, entryIndex + 1);
float currentY = fieldsRect.y; GUIUtility.ExitGUI();
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 EditorGUI.DisabledScope(sprite == null))
{ {
if (GUI.Button(pingRect, "Ping")) if (GUILayout.Button("Ping", GUILayout.Width(48f)))
{ {
EditorGUIUtility.PingObject(sprite); 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);
}
}
}
} }
private void DrawSpritePreview(Sprite sprite, string label) private void DrawSpritePreview(Sprite sprite, string label)
@ -509,14 +595,14 @@ public sealed class InputGlyphDatabaseEditor : Editor
List<string> issues = new(); List<string> issues = new();
if (_database.tables == null || _database.tables.Count == 0) 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; return issues;
} }
List<string> missingTables = GetMissingDefaultTables(); List<string> missingTables = GetMissingDefaultTables();
if (missingTables.Count > 0) if (missingTables.Count > 0)
{ {
issues.Add($"Recommended tables are missing: {string.Join(", ", missingTables)}."); issues.Add($"缺少推荐表: {string.Join(", ", missingTables)}。");
} }
HashSet<string> seenNames = new(StringComparer.OrdinalIgnoreCase); 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; string tableName = _database.tables[i] != null ? _database.tables[i].deviceName : string.Empty;
if (string.IsNullOrWhiteSpace(tableName)) if (string.IsNullOrWhiteSpace(tableName))
{ {
issues.Add($"Table {i + 1} has an empty device name."); issues.Add($"表 {i + 1} 的设备名称为空。");
continue; continue;
} }
@ -538,7 +624,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
foreach (string duplicateName in duplicateNames) foreach (string duplicateName in duplicateNames)
{ {
issues.Add($"Duplicate table name '{duplicateName}' detected."); issues.Add($"检测到重复的表名 '{duplicateName}'。");
} }
return issues; return issues;
@ -549,14 +635,14 @@ public sealed class InputGlyphDatabaseEditor : Editor
List<string> issues = new(); List<string> issues = new();
if (!IsValidTableIndex(tableIndex)) if (!IsValidTableIndex(tableIndex))
{ {
issues.Add("The selected table is invalid."); issues.Add("当前选中的表无效。");
return issues; return issues;
} }
DeviceGlyphTable table = _database.tables[tableIndex]; DeviceGlyphTable table = _database.tables[tableIndex];
if (table.entries == null || table.entries.Count == 0) if (table.entries == null || table.entries.Count == 0)
{ {
issues.Add("This table has no entries."); issues.Add("当前表没有任何条目。");
return issues; return issues;
} }
@ -571,7 +657,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
GlyphEntry entry = table.entries[i]; GlyphEntry entry = table.entries[i];
if (entry == null) if (entry == null)
{ {
issues.Add($"Entry {i + 1} is null."); issues.Add($"条目 {i + 1} 为空。");
continue; continue;
} }
@ -601,16 +687,17 @@ public sealed class InputGlyphDatabaseEditor : Editor
if (missingSpriteCount > 0) if (missingSpriteCount > 0)
{ {
issues.Add($"{missingSpriteCount} entr{(missingSpriteCount == 1 ? "y has" : "ies have")} no sprite assigned."); issues.Add($"{missingSpriteCount} 个条目未绑定 Sprite。");
} }
if (missingActionCount > 0) 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) 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) foreach (KeyValuePair<string, List<string>> pair in bindingOwners)
@ -620,7 +707,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
continue; 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; return issues;
@ -658,7 +745,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
table.entries ??= new List<GlyphEntry>(); table.entries ??= new List<GlyphEntry>();
table.entries.Add(new GlyphEntry { Sprite = sprite, action = null }); table.entries.Add(new GlyphEntry { Sprite = sprite, action = null });
serializedObject.Update(); serializedObject.Update();
InvalidateEntriesList(tableIndex); InvalidateEntryView(tableIndex);
NotifyDatabaseChanged(); NotifyDatabaseChanged();
} }
@ -674,7 +761,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
table.entries ??= new List<GlyphEntry>(); table.entries ??= new List<GlyphEntry>();
table.entries.Clear(); table.entries.Clear();
serializedObject.Update(); serializedObject.Update();
InvalidateEntriesList(tableIndex); InvalidateEntryView(tableIndex);
NotifyDatabaseChanged(); NotifyDatabaseChanged();
} }
@ -691,7 +778,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
}); });
SyncTableStates(); SyncTableStates();
serializedObject.Update(); serializedObject.Update();
InvalidateAllEntriesLists(); InvalidateAllEntryViews();
NotifyDatabaseChanged(); NotifyDatabaseChanged();
} }
@ -707,7 +794,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
SyncTableStates(); SyncTableStates();
ClampSelectedTab(); ClampSelectedTab();
serializedObject.Update(); serializedObject.Update();
InvalidateAllEntriesLists(); InvalidateAllEntryViews();
NotifyDatabaseChanged(); NotifyDatabaseChanged();
} }
@ -734,7 +821,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
SyncTableStates(); SyncTableStates();
serializedObject.Update(); serializedObject.Update();
InvalidateAllEntriesLists(); InvalidateAllEntryViews();
NotifyDatabaseChanged(); NotifyDatabaseChanged();
} }
@ -822,7 +909,7 @@ public sealed class InputGlyphDatabaseEditor : Editor
} }
serializedObject.Update(); serializedObject.Update();
InvalidateEntriesList(tableIndex); InvalidateEntryView(tableIndex);
NotifyDatabaseChanged(); NotifyDatabaseChanged();
Debug.Log($"[InputGlyphDatabase] Merged sprite sheet '{table.spriteSheetTexture.name}' into '{table.deviceName}'. sprites={sprites.Count}, replaced={replaced}, added={added}, total={table.entries.Count}"); 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()) if (serializedObject.ApplyModifiedProperties())
{ {
InvalidateAllEntryViews();
NotifyDatabaseChanged(); NotifyDatabaseChanged();
} }
} }
@ -865,23 +953,163 @@ public sealed class InputGlyphDatabaseEditor : Editor
_selectedTab = Mathf.Clamp(_selectedTab, 0, maxIndex); _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) if (tableIndex < 0 || tableIndex >= _tableStates.Count)
{ {
return; return;
} }
_tableStates[tableIndex].EntriesList = null; InvalidateEntryView(_tableStates[tableIndex]);
_tableStates[tableIndex].EntriesPropertyPath = null;
} }
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++) for (int i = 0; i < _tableStates.Count; i++)
{ {
_tableStates[i].EntriesList = null; InvalidateEntryView(_tableStates[i]);
_tableStates[i].EntriesPropertyPath = null;
} }
} }

@ -1 +1 @@
Subproject commit a978c68586cf761c577842532dfa6af5847851a4 Subproject commit e7c6b174fe953381196760a54432048990f74c99