using System; using System.Collections.Generic; using System.IO; using System.Reflection; using UnityEditor; using UnityEngine; using UnityEngine.U2D; using TMPro; using UnityEngine.InputSystem; using InputGlyphsFramework; [CustomEditor(typeof(InputGlyphDatabase))] public class InputGlyphDatabaseEditor : Editor { SerializedProperty tablesProp; InputGlyphDatabase db; int tabIndex = 0; string[] tabNames = new string[] { "Keyboard", "Xbox", "PlayStation", "Other" }; // Pagination const int itemsPerPage = 10; // 10 items per page as requested int currentPage = 0; void OnEnable() { db = target as InputGlyphDatabase; tablesProp = serializedObject.FindProperty("tables"); // Ensure serialized list exists if (tablesProp == null) { // If the field name is different, user should fix it in their class or here. Debug.LogError("Could not find serialized property 'tables' on InputGlyphDatabase. Check field name."); } } public override void OnInspectorGUI() { serializedObject.Update(); if (db == null || tablesProp == null) return; EditorGUILayout.Space(); tabIndex = GUILayout.Toolbar(tabIndex, tabNames, GUILayout.Height(24)); var curDevice = (InputDeviceWatcher.InputDeviceCategory)tabIndex; // Ensure all 4 tables exist (serialized-safe) EnsureTableFor(InputDeviceWatcher.InputDeviceCategory.Keyboard); EnsureTableFor(InputDeviceWatcher.InputDeviceCategory.Xbox); EnsureTableFor(InputDeviceWatcher.InputDeviceCategory.PlayStation); EnsureTableFor(InputDeviceWatcher.InputDeviceCategory.Other); var tableProp = GetTablePropertyFor(curDevice); if (tableProp == null) { EditorGUILayout.HelpBox("Table not found (this should not happen).", MessageType.Warning); serializedObject.ApplyModifiedProperties(); return; } EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField(curDevice.ToString(), EditorStyles.boldLabel); var tmpAssetProp = tableProp.FindPropertyRelative("tmpAsset"); EditorGUILayout.PropertyField(tmpAssetProp, new GUIContent("TMP Sprite Asset")); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Parse TMP Asset")) { ParseTMPAssetIntoTableSerialized(tableProp); } if (GUILayout.Button("Clear")) { var entriesProp = tableProp.FindPropertyRelative("entries"); entriesProp.arraySize = 0; serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(db); currentPage = 0; } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); var entries = tableProp.FindPropertyRelative("entries"); if (entries != null) { int total = entries.arraySize; int totalPages = Mathf.Max(1, (total + itemsPerPage - 1) / itemsPerPage); currentPage = Mathf.Clamp(currentPage, 0, totalPages - 1); // Pagination controls EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("<<", GUILayout.Width(40))) { currentPage = 0; } if (GUILayout.Button("<", GUILayout.Width(40))) { currentPage = Mathf.Max(0, currentPage - 1); } GUILayout.FlexibleSpace(); EditorGUILayout.LabelField(string.Format("Page {0}/{1}", currentPage + 1, totalPages), GUILayout.Width(100)); GUILayout.FlexibleSpace(); if (GUILayout.Button(">", GUILayout.Width(40))) { currentPage = Mathf.Min(totalPages - 1, currentPage + 1); } if (GUILayout.Button(">>", GUILayout.Width(40))) { currentPage = totalPages - 1; } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(4); int start = currentPage * itemsPerPage; int end = Mathf.Min(start + itemsPerPage, total); for (int i = start; i < end; ++i) { var eProp = entries.GetArrayElementAtIndex(i); if (eProp == null) continue; using (new EditorGUILayout.HorizontalScope("box")) { // Left column: sprite preview (fixed width) using (new EditorGUILayout.VerticalScope(GUILayout.Width(80))) { var spriteProp = eProp.FindPropertyRelative("Sprite"); Sprite s = spriteProp.objectReferenceValue as Sprite; EditorGUILayout.LabelField(s != null ? s.name : "", 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(64), GUILayout.Height(64)); } else { EditorGUILayout.PropertyField(spriteProp, GUIContent.none, GUILayout.Width(64), GUILayout.Height(64)); } } else { EditorGUILayout.PropertyField(spriteProp, GUIContent.none, GUILayout.Width(64), GUILayout.Height(64)); } } // Right column: path + action should take the remaining width EditorGUILayout.BeginVertical(); var pathProp = eProp.FindPropertyRelative("controlPath"); var actionProp = eProp.FindPropertyRelative("action"); EditorGUILayout.PropertyField(pathProp, GUIContent.none, GUILayout.ExpandWidth(true)); EditorGUILayout.Space(2); EditorGUILayout.PropertyField(actionProp, GUIContent.none, GUILayout.ExpandWidth(true)); EditorGUILayout.EndVertical(); } EditorGUILayout.Space(6); } } EditorGUILayout.EndVertical(); EditorGUILayout.Space(); if (GUILayout.Button("Save Asset")) { serializedObject.ApplyModifiedProperties(); EditorUtility.SetDirty(db); AssetDatabase.SaveAssets(); } serializedObject.ApplyModifiedProperties(); } // Ensure a table for the device exists. Uses serialized properties (adds element if necessary). void EnsureTableFor(InputDeviceWatcher.InputDeviceCategory device) { if (tablesProp == null) return; // Search existing entries for (int i = 0; i < tablesProp.arraySize; ++i) { var t = tablesProp.GetArrayElementAtIndex(i); var devProp = t.FindPropertyRelative("deviceType"); if (devProp != null && devProp.enumValueIndex == (int)device) return; // exists } // Not found -> append new element int idx = tablesProp.arraySize; tablesProp.InsertArrayElementAtIndex(idx); // Inserts a copy if exists, otherwise default var newTable = tablesProp.GetArrayElementAtIndex(idx); var deviceTypeProp = newTable.FindPropertyRelative("deviceType"); if (deviceTypeProp != null) deviceTypeProp.enumValueIndex = (int)device; 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); } // Return the SerializedProperty representing the DeviceGlyphTable that matches device SerializedProperty GetTablePropertyFor(InputDeviceWatcher.InputDeviceCategory device) { if (tablesProp == null) return null; for (int i = 0; i < tablesProp.arraySize; ++i) { var t = tablesProp.GetArrayElementAtIndex(i); var devProp = t.FindPropertyRelative("deviceType"); if (devProp != null && devProp.enumValueIndex == (int)device) return t; } return null; } // Parse TMP Sprite Asset and populate entries (serialized) void ParseTMPAssetIntoTableSerialized(SerializedProperty tableProp) { 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; var chars = asset.spriteCharacterTable; SpriteAtlas atlas = GetSpriteAtlasFromTMP(asset); string assetPath = AssetDatabase.GetAssetPath(asset); string assetFolder = Path.GetDirectoryName(assetPath); 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; Sprite s = null; try { var glyph = ch.glyph as TMP_SpriteGlyph; if (glyph != null && glyph.sprite != null) s = glyph.sprite; } catch { } if (s == null && atlas != null) { try { s = atlas.GetSprite(name); } catch { s = null; } if (s == null) { try { var m = typeof(SpriteAtlas).GetMethod("GetSprite", new Type[] { typeof(string) }); if (m != null) s = m.Invoke(atlas, new object[] { name }) as Sprite; } catch { } } } 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(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(p); if (sp != null && sp.name == name) { s = sp; break; } } } } // Create new entry and assign fields int newIndex = entriesProp.arraySize; entriesProp.InsertArrayElementAtIndex(newIndex); var entryProp = entriesProp.GetArrayElementAtIndex(newIndex); var spriteProp = entryProp.FindPropertyRelative("Sprite"); var controlPathProp = entryProp.FindPropertyRelative("controlPath"); if (spriteProp != null) spriteProp.objectReferenceValue = s; if (controlPathProp != null) controlPathProp.stringValue = string.Empty; } serializedObject.ApplyModifiedProperties(); 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; } catch { } } } return null; } }