using System.Collections.Generic; using System.Linq; using TMPro; using UnityEditor; using UnityEngine; using UnityEngine.TextCore; namespace BrewMonster.Scripts.Editor { /// /// Bulk chỉnh BX/BY/AD/Scale trên TMP_SpriteAsset (spriteGlyphTable). /// Bulk-edit BX/BY/AD/Scale on TMP_SpriteAsset (spriteGlyphTable). /// public sealed class TmpSpriteAssetGlyphMetricsBulkWindow : EditorWindow { private enum ValueMode { Absolute, AddDelta } private readonly List _targets = new List(); private Vector2 _scroll; private string _nameContains = ""; private ValueMode _mode = ValueMode.Absolute; private bool _setBx = true; private bool _setBy = true; private bool _setAd = true; private bool _setScale; private float _bx; private float _by = 10f; private float _ad = 32f; private float _scale = 1f; [MenuItem("Tools/Perfect World/ChatSystem/TMP Sprite Metrics (BX/BY/AD)…")] public static void Open() { var w = GetWindow(false, "TMP Sprite Metrics", true); w.minSize = new Vector2(420f, 360f); } private void OnGUI() { EditorGUILayout.Space(4f); EditorGUILayout.LabelField("TMP_SpriteAsset — BX / BY / AD / Scale", EditorStyles.boldLabel); EditorGUILayout.HelpBox( "Áp dụng lên bảng glyph (spriteGlyphTable). Lọc theo tên sprite (cột Name) nếu cần.\n" + "Applies to spriteGlyphTable. Optional name filter uses sprite character names.", MessageType.None); EditorGUILayout.Space(6f); DrawTargetsSection(); EditorGUILayout.Space(8f); _nameContains = EditorGUILayout.TextField("Name contains (optional)", _nameContains); _mode = (ValueMode)EditorGUILayout.EnumPopup("Mode", _mode); EditorGUILayout.Space(4f); EditorGUILayout.LabelField("Fields to write", EditorStyles.boldLabel); EditorGUILayout.BeginHorizontal(); _setBx = GUILayout.Toggle(_setBx, "BX", GUILayout.Width(44)); _setBy = GUILayout.Toggle(_setBy, "BY", GUILayout.Width(44)); _setAd = GUILayout.Toggle(_setAd, "AD", GUILayout.Width(44)); _setScale = GUILayout.Toggle(_setScale, "Scale", GUILayout.Width(60)); EditorGUILayout.EndHorizontal(); string hint = _mode == ValueMode.Absolute ? "Giá trị gán thẳng vào metrics / Set metrics to these values." : "Cộng dồn lên giá trị hiện tại / Add to current values."; EditorGUILayout.HelpBox(hint, MessageType.None); using (new EditorGUI.DisabledScope(!_setBx)) _bx = EditorGUILayout.FloatField("BX (bearing X)", _bx); using (new EditorGUI.DisabledScope(!_setBy)) _by = EditorGUILayout.FloatField("BY (bearing Y)", _by); using (new EditorGUI.DisabledScope(!_setAd)) _ad = EditorGUILayout.FloatField("AD (advance)", _ad); using (new EditorGUI.DisabledScope(!_setScale)) _scale = EditorGUILayout.FloatField("Scale", _scale); EditorGUILayout.Space(10f); using (new EditorGUI.DisabledScope(_targets.Count == 0 || (!_setBx && !_setBy && !_setAd && !_setScale))) { if (GUILayout.Button("Apply to assets", GUILayout.Height(32))) ApplyAll(); } EditorGUILayout.Space(6f); DrawSummary(); } private void DrawTargetsSection() { EditorGUILayout.LabelField("Sprite assets", EditorStyles.boldLabel); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Use selection (Project)", GUILayout.Height(22))) AddFromSelection(); if (GUILayout.Button("Clear list", GUILayout.Width(100))) _targets.Clear(); EditorGUILayout.EndHorizontal(); _scroll = EditorGUILayout.BeginScrollView(_scroll, GUILayout.MinHeight(120f)); for (int i = 0; i < _targets.Count; i++) { EditorGUILayout.BeginHorizontal(); _targets[i] = (TMP_SpriteAsset)EditorGUILayout.ObjectField( _targets[i], typeof(TMP_SpriteAsset), false); if (GUILayout.Button("−", GUILayout.Width(24))) { _targets.RemoveAt(i); i--; } EditorGUILayout.EndHorizontal(); } EditorGUILayout.EndScrollView(); var drop = (TMP_SpriteAsset)EditorGUILayout.ObjectField( "Drag asset here to add", null, typeof(TMP_SpriteAsset), false); if (drop != null && !_targets.Contains(drop)) _targets.Add(drop); } private void AddFromSelection() { foreach (Object o in Selection.objects) { if (o is TMP_SpriteAsset sa && !_targets.Contains(sa)) _targets.Add(sa); } } private void DrawSummary() { int n = _targets.Count; if (n == 0) { EditorGUILayout.LabelField("No assets in list."); return; } int glyphs = _targets.Where(t => t != null).Sum(t => t.spriteGlyphTable?.Count ?? 0); EditorGUILayout.LabelField($"Assets: {n} | Total glyphs: {glyphs}"); } private void ApplyAll() { if (!_setBx && !_setBy && !_setAd && !_setScale) { EditorUtility.DisplayDialog("TMP Sprite Metrics", "Chọn ít nhất một field (BX/BY/AD/Scale).", "OK"); return; } int changedAssets = 0; int changedGlyphs = 0; foreach (TMP_SpriteAsset asset in _targets.Where(a => a != null)) { string path = AssetDatabase.GetAssetPath(asset); if (string.IsNullOrEmpty(path)) continue; Undo.RegisterCompleteObjectUndo(asset, "TMP Sprite glyph metrics"); var glyphTable = asset.spriteGlyphTable; if (glyphTable == null || glyphTable.Count == 0) continue; HashSet allowed = BuildGlyphIndexFilter(asset); bool touched = false; foreach (TMP_SpriteGlyph g in glyphTable) { if (g == null) continue; if (allowed != null && !allowed.Contains(g.index)) continue; GlyphMetrics m = g.metrics; if (_setBx) m.horizontalBearingX = _mode == ValueMode.Absolute ? _bx : m.horizontalBearingX + _bx; if (_setBy) m.horizontalBearingY = _mode == ValueMode.Absolute ? _by : m.horizontalBearingY + _by; if (_setAd) m.horizontalAdvance = _mode == ValueMode.Absolute ? _ad : m.horizontalAdvance + _ad; g.metrics = m; if (_setScale) g.scale = _mode == ValueMode.Absolute ? _scale : g.scale + _scale; touched = true; changedGlyphs++; } if (_setScale) { var chars = asset.spriteCharacterTable; if (chars != null) { foreach (TMP_SpriteCharacter c in chars) { if (c == null) continue; if (allowed != null && !allowed.Contains(c.glyphIndex)) continue; c.scale = _mode == ValueMode.Absolute ? _scale : c.scale + _scale; } } } if (touched) { asset.UpdateLookupTables(); EditorUtility.SetDirty(asset); changedAssets++; } } AssetDatabase.SaveAssets(); Debug.Log($"[Cuong] TMP Sprite Metrics: updated {changedGlyphs} glyph(s) across {changedAssets} asset(s)."); } /// /// null = không lọc / no filter /// private HashSet BuildGlyphIndexFilter(TMP_SpriteAsset asset) { string f = _nameContains?.Trim(); if (string.IsNullOrEmpty(f)) return null; var set = new HashSet(); var chars = asset.spriteCharacterTable; if (chars == null) return set; foreach (TMP_SpriteCharacter c in chars) { if (c == null || string.IsNullOrEmpty(c.name)) continue; if (c.name.IndexOf(f, System.StringComparison.OrdinalIgnoreCase) < 0) continue; set.Add(c.glyphIndex); } return set; } } }