using UnityEngine; using UnityEditor; using UnityEditorInternal; using System.Collections; using System.Collections.Generic; using System.Linq; namespace TMPro.EditorUtilities { [CustomEditor(typeof(TMP_SpriteAsset))] public class TMP_SpriteAssetEditor : Editor { struct UI_PanelState { public static bool spriteAssetInfoPanel = true; public static bool fallbackSpriteAssetPanel = true; public static bool spriteInfoPanel; } int m_moveToIndex; int m_selectedElement = -1; int m_page; const string k_UndoRedo = "UndoRedoPerformed"; string m_searchPattern; List m_searchList; bool m_isSearchDirty; SerializedProperty m_spriteAtlas_prop; SerializedProperty m_material_prop; SerializedProperty m_spriteInfoList_prop; ReorderableList m_fallbackSpriteAssetList; bool isAssetDirty; float m_xOffset; float m_yOffset; float m_xAdvance; float m_scale; public void OnEnable() { m_spriteAtlas_prop = serializedObject.FindProperty("spriteSheet"); m_material_prop = serializedObject.FindProperty("material"); m_spriteInfoList_prop = serializedObject.FindProperty("spriteInfoList"); // Fallback TMP Sprite Asset list m_fallbackSpriteAssetList = new ReorderableList(serializedObject, serializedObject.FindProperty("fallbackSpriteAssets"), true, true, true, true); m_fallbackSpriteAssetList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => { var element = m_fallbackSpriteAssetList.serializedProperty.GetArrayElementAtIndex(index); rect.y += 2; EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), element, GUIContent.none); }; m_fallbackSpriteAssetList.drawHeaderCallback = rect => { EditorGUI.LabelField(rect, new GUIContent("Fallback Sprite Asset List", "Select the Sprite Assets that will be searched and used as fallback when a given sprite is missing from this sprite asset.")); }; } public override void OnInspectorGUI() { //Debug.Log("OnInspectorGUI Called."); Event currentEvent = Event.current; string evt_cmd = currentEvent.commandName; // Get Current Event CommandName to check for Undo Events serializedObject.Update(); // TEXTMESHPRO SPRITE INFO PANEL GUILayout.Label("Sprite Info", EditorStyles.boldLabel); EditorGUI.indentLevel = 1; EditorGUI.BeginChangeCheck(); EditorGUILayout.PropertyField(m_spriteAtlas_prop , new GUIContent("Sprite Atlas")); if (EditorGUI.EndChangeCheck()) { // Assign the new sprite atlas texture to the current material Texture2D tex = m_spriteAtlas_prop.objectReferenceValue as Texture2D; if (tex != null) { Material mat = m_material_prop.objectReferenceValue as Material; if (mat != null) mat.mainTexture = tex; } } EditorGUILayout.PropertyField(m_material_prop, new GUIContent("Default Material")); EditorGUILayout.Space(); // FALLBACK SPRITE ASSETS EditorGUI.indentLevel = 0; UI_PanelState.fallbackSpriteAssetPanel = EditorGUILayout.Foldout(UI_PanelState.fallbackSpriteAssetPanel, new GUIContent("Fallback Sprite Assets", "Select the Sprite Assets that will be searched and used as fallback when a given sprite is missing from this sprite asset."), true, TMP_UIStyleManager.boldFoldout); if (UI_PanelState.fallbackSpriteAssetPanel) { m_fallbackSpriteAssetList.DoLayoutList(); } // SPRITE LIST EditorGUI.indentLevel = 0; UI_PanelState.spriteInfoPanel = EditorGUILayout.Foldout(UI_PanelState.spriteInfoPanel, "Sprite List", true, TMP_UIStyleManager.boldFoldout); if (UI_PanelState.spriteInfoPanel) { int arraySize = m_spriteInfoList_prop.arraySize; int itemsPerPage = 10; // (Screen.height - 292) / 80; // Display Glyph Management Tools EditorGUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.ExpandWidth(true)); { // Search Bar implementation #region DISPLAY SEARCH BAR EditorGUILayout.BeginHorizontal(); { EditorGUIUtility.labelWidth = 110f; EditorGUI.BeginChangeCheck(); string searchPattern = EditorGUILayout.TextField("Sprite Search", m_searchPattern, "SearchTextField"); if (EditorGUI.EndChangeCheck() || m_isSearchDirty) { if (string.IsNullOrEmpty(searchPattern) == false) { //GUIUtility.keyboardControl = 0; m_searchPattern = searchPattern.ToLower(System.Globalization.CultureInfo.InvariantCulture).Trim(); // Search Glyph Table for potential matches SearchGlyphTable(m_searchPattern, ref m_searchList); } m_isSearchDirty = false; } string styleName = string.IsNullOrEmpty(m_searchPattern) ? "SearchCancelButtonEmpty" : "SearchCancelButton"; if (GUILayout.Button(GUIContent.none, styleName)) { GUIUtility.keyboardControl = 0; m_searchPattern = string.Empty; } } EditorGUILayout.EndHorizontal(); #endregion // Display Page Navigation if (!string.IsNullOrEmpty(m_searchPattern)) arraySize = m_searchList.Count; // Display Page Navigation DisplayGlyphPageNavigation(arraySize, itemsPerPage); } EditorGUILayout.EndVertical(); if (arraySize > 0) { // Display each SpriteInfo entry using the SpriteInfo property drawer. for (int i = itemsPerPage * m_page; i < arraySize && i < itemsPerPage * (m_page + 1); i++) { // Define the start of the selection region of the element. Rect elementStartRegion = GUILayoutUtility.GetRect(0f, 0f, GUILayout.ExpandWidth(true)); int elementIndex = i; if (!string.IsNullOrEmpty(m_searchPattern)) elementIndex = m_searchList[i]; SerializedProperty spriteInfo = m_spriteInfoList_prop.GetArrayElementAtIndex(elementIndex); EditorGUILayout.BeginVertical(EditorStyles.helpBox, GUILayout.Height(75)); { EditorGUI.BeginDisabledGroup(i != m_selectedElement); { EditorGUILayout.PropertyField(spriteInfo); } EditorGUI.EndDisabledGroup(); } EditorGUILayout.EndVertical(); // Define the end of the selection region of the element. Rect elementEndRegion = GUILayoutUtility.GetRect(0f, 0f, GUILayout.ExpandWidth(true)); // Check for Item selection Rect selectionArea = new Rect(elementStartRegion.x, elementStartRegion.y, elementEndRegion.width, elementEndRegion.y - elementStartRegion.y); if (DoSelectionCheck(selectionArea)) { if (m_selectedElement == i) { m_selectedElement = -1; } else { m_selectedElement = i; GUIUtility.keyboardControl = 0; } } // Draw & Handle Section Area if (m_selectedElement == i) { // Draw selection highlight TMP_EditorUtility.DrawBox(selectionArea, 2f, new Color32(40, 192, 255, 255)); // Draw options to MoveUp, MoveDown, Add or Remove Sprites Rect controlRect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight * 1f); controlRect.width /= 8; // Move sprite up. bool guiEnabled = GUI.enabled; if (i == 0) { GUI.enabled = false; } if (GUI.Button(controlRect, "Up")) { SwapSpriteElement(i, i - 1); } GUI.enabled = guiEnabled; // Move sprite down. controlRect.x += controlRect.width; if (i == arraySize - 1) { GUI.enabled = false; } if (GUI.Button(controlRect, "Down")) { SwapSpriteElement(i, i + 1); } GUI.enabled = guiEnabled; // Move sprite to new index controlRect.x += controlRect.width * 2; //if (i == arraySize - 1) { GUI.enabled = false; } m_moveToIndex = EditorGUI.IntField(controlRect, m_moveToIndex); controlRect.x -= controlRect.width; if (GUI.Button(controlRect, "Goto")) { MoveSpriteElement(i, m_moveToIndex); } //controlRect.x += controlRect.width; GUI.enabled = guiEnabled; // Add new Sprite controlRect.x += controlRect.width * 4; if (GUI.Button(controlRect, "+")) { m_spriteInfoList_prop.arraySize += 1; int index = m_spriteInfoList_prop.arraySize - 1; SerializedProperty spriteInfo_prop = m_spriteInfoList_prop.GetArrayElementAtIndex(index); // Copy properties of the selected element CopySerializedProperty(m_spriteInfoList_prop.GetArrayElementAtIndex(elementIndex), ref spriteInfo_prop); spriteInfo_prop.FindPropertyRelative("id").intValue = index; serializedObject.ApplyModifiedProperties(); m_isSearchDirty = true; } // Delete selected Sprite controlRect.x += controlRect.width; if (m_selectedElement == -1) GUI.enabled = false; if (GUI.Button(controlRect, "-")) { m_spriteInfoList_prop.DeleteArrayElementAtIndex(elementIndex); m_selectedElement = -1; serializedObject.ApplyModifiedProperties(); m_isSearchDirty = true; return; } } } } DisplayGlyphPageNavigation(arraySize, itemsPerPage); EditorGUIUtility.labelWidth = 40f; EditorGUIUtility.fieldWidth = 20f; GUILayout.Space(5f); // GLOBAL TOOLS #region Global Tools GUI.enabled = true; EditorGUILayout.BeginVertical(EditorStyles.helpBox); Rect rect = EditorGUILayout.GetControlRect(false, 40); float width = (rect.width - 75f) / 4; EditorGUI.LabelField(rect, "Global Offsets & Scale", EditorStyles.boldLabel); rect.x += 70; bool old_ChangedState = GUI.changed; GUI.changed = false; m_xOffset = EditorGUI.FloatField(new Rect(rect.x + 5f + width * 0, rect.y + 20, width - 5f, 18), new GUIContent("OX:"), m_xOffset); if (GUI.changed) UpdateGlobalProperty("xOffset", m_xOffset); m_yOffset = EditorGUI.FloatField(new Rect(rect.x + 5f + width * 1, rect.y + 20, width - 5f, 18), new GUIContent("OY:"), m_yOffset); if (GUI.changed) UpdateGlobalProperty("yOffset", m_yOffset); m_xAdvance = EditorGUI.FloatField(new Rect(rect.x + 5f + width * 2, rect.y + 20, width - 5f, 18), new GUIContent("ADV."), m_xAdvance); if (GUI.changed) UpdateGlobalProperty("xAdvance", m_xAdvance); m_scale = EditorGUI.FloatField(new Rect(rect.x + 5f + width * 3, rect.y + 20, width - 5f, 18), new GUIContent("SF."), m_scale); if (GUI.changed) UpdateGlobalProperty("scale", m_scale); EditorGUILayout.EndVertical(); #endregion GUI.changed = old_ChangedState; } if (serializedObject.ApplyModifiedProperties() || evt_cmd == k_UndoRedo || isAssetDirty) { isAssetDirty = false; EditorUtility.SetDirty(target); //TMPro_EditorUtility.RepaintAll(); // Consider SetDirty } // Clear selection if mouse event was not consumed. GUI.enabled = true; if (currentEvent.type == EventType.MouseDown && currentEvent.button == 0) m_selectedElement = -1; } /// /// /// /// /// void DisplayGlyphPageNavigation(int arraySize, int itemsPerPage) { Rect pagePos = EditorGUILayout.GetControlRect(false, 20); pagePos.width /= 3; int shiftMultiplier = Event.current.shift ? 10 : 1; // Page + Shift goes 10 page forward // Previous Page GUI.enabled = m_page > 0; if (GUI.Button(pagePos, "Previous Page")) { m_page -= 1 * shiftMultiplier; //m_isNewPage = true; } // Page Counter GUI.enabled = true; pagePos.x += pagePos.width; int totalPages = (int)(arraySize / (float)itemsPerPage + 0.999f); GUI.Label(pagePos, "Page " + (m_page + 1) + " / " + totalPages, TMP_UIStyleManager.centeredLabel); // Next Page pagePos.x += pagePos.width; GUI.enabled = itemsPerPage * (m_page + 1) < arraySize; if (GUI.Button(pagePos, "Next Page")) { m_page += 1 * shiftMultiplier; //m_isNewPage = true; } // Clamp page range m_page = Mathf.Clamp(m_page, 0, arraySize / itemsPerPage); GUI.enabled = true; } /// /// Method to update the properties of all sprites /// /// /// void UpdateGlobalProperty(string property, float value) { int arraySize = m_spriteInfoList_prop.arraySize; for (int i = 0; i < arraySize; i++) { SerializedProperty spriteInfo = m_spriteInfoList_prop.GetArrayElementAtIndex(i); spriteInfo.FindPropertyRelative(property).floatValue = value; } GUI.changed = false; } // Check if any of the Style elements were clicked on. private bool DoSelectionCheck(Rect selectionArea) { Event currentEvent = Event.current; switch (currentEvent.type) { case EventType.MouseDown: if (selectionArea.Contains(currentEvent.mousePosition) && currentEvent.button == 0) { currentEvent.Use(); return true; } break; } return false; } /// /// Swap the sprite item at the currently selected array index to another index. /// /// Selected index. /// New index. void SwapSpriteElement(int selectedIndex, int newIndex) { m_spriteInfoList_prop.MoveArrayElement(selectedIndex, newIndex); m_spriteInfoList_prop.GetArrayElementAtIndex(selectedIndex).FindPropertyRelative("id").intValue = selectedIndex; m_spriteInfoList_prop.GetArrayElementAtIndex(newIndex).FindPropertyRelative("id").intValue = newIndex; m_selectedElement = newIndex; m_isSearchDirty = true; } /// /// Move Sprite Element at selected index to another index and reorder sprite list. /// /// /// void MoveSpriteElement(int selectedIndex, int newIndex) { m_spriteInfoList_prop.MoveArrayElement(selectedIndex, newIndex); // Reorder Sprite Element Index for (int i = 0; i < m_spriteInfoList_prop.arraySize; i++) m_spriteInfoList_prop.GetArrayElementAtIndex(i).FindPropertyRelative("id").intValue = i; m_selectedElement = newIndex; m_isSearchDirty = true; } /// /// /// /// /// void CopySerializedProperty(SerializedProperty source, ref SerializedProperty target) { target.FindPropertyRelative("name").stringValue = source.FindPropertyRelative("name").stringValue; target.FindPropertyRelative("hashCode").intValue = source.FindPropertyRelative("hashCode").intValue; target.FindPropertyRelative("x").floatValue = source.FindPropertyRelative("x").floatValue; target.FindPropertyRelative("y").floatValue = source.FindPropertyRelative("y").floatValue; target.FindPropertyRelative("width").floatValue = source.FindPropertyRelative("width").floatValue; target.FindPropertyRelative("height").floatValue = source.FindPropertyRelative("height").floatValue; target.FindPropertyRelative("xOffset").floatValue = source.FindPropertyRelative("xOffset").floatValue; target.FindPropertyRelative("yOffset").floatValue = source.FindPropertyRelative("yOffset").floatValue; target.FindPropertyRelative("xAdvance").floatValue = source.FindPropertyRelative("xAdvance").floatValue; target.FindPropertyRelative("scale").floatValue = source.FindPropertyRelative("scale").floatValue; target.FindPropertyRelative("sprite").objectReferenceValue = source.FindPropertyRelative("sprite").objectReferenceValue; } /// /// /// /// /// void SearchGlyphTable(string searchPattern, ref List searchResults) { if (searchResults == null) searchResults = new List(); searchResults.Clear(); int arraySize = m_spriteInfoList_prop.arraySize; for (int i = 0; i < arraySize; i++) { SerializedProperty sourceSprite = m_spriteInfoList_prop.GetArrayElementAtIndex(i); // Check for potential match against decimal id int id = sourceSprite.FindPropertyRelative("id").intValue; if (id.ToString().Contains(searchPattern)) searchResults.Add(i); // Check for potential match against name string name = sourceSprite.FindPropertyRelative("name").stringValue.ToLower(System.Globalization.CultureInfo.InvariantCulture).Trim(); if (name.Contains(searchPattern)) searchResults.Add(i); } } } }