using System.Collections.Generic;
using BrewMonster.Scripts.Chat.EmotionData;
using TMPro;
using UnityEditor;
using UnityEngine;
namespace BrewMonster.Scripts.Editor
{
///
/// Tool: atlas + txt → Sprite Mode Multiple (một PNG/bộ) + SO; batch linh hoạt số slot.
///
public class EmotionAtlasConverterWindow : EditorWindow
{
private Texture2D _sourceAtlas;
private TextAsset _txtAsset;
private int _emotionSetIndex;
private int _cellW = 32;
private int _cellH = 32;
private string _outputFolder = "Assets/PerfectWorld/UI/Chat/GeneratedEmotions";
private bool _sliceInPlace;
private List _batchSlots = new List();
private Vector2 _batchScroll;
private string _libraryAssetPath = "Assets/PerfectWorld/UI/Chat/GeneratedEmotions/EmotionLibrary.asset";
// ── TMP Sprite Asset ──────────────────────────────────────────────────
private bool _genTmpOnConvert = true;
private string _tmpSpriteFolder = "Assets/TextMesh Pro/Resources/Sprite Assets";
private float _tmpCellSizePt = 0f; // 0 = lấy từ cellH
private bool _showTmpSection = true;
// Snapshot cuối cùng được convert (cho nút gen TMP standalone)
// Last converted snapshot (for standalone TMP gen button)
private EmotionSetSnapshot _lastSnapshot;
[MenuItem("Tools/Perfect World/ChatSystem/Emotion Atlas Converter…")]
public static void Open()
{
GetWindow(true, "Emotion Atlas Converter", true);
}
private void OnEnable()
{
if (_batchSlots == null)
_batchSlots = new List();
if (_batchSlots.Count == 0)
{
for (int i = 0; i < 8; i++)
_batchSlots.Add(new EmotionBatchSlot { EmotionSetIndex = i });
}
}
private void OnGUI()
{
EditorGUILayout.LabelField("Chung / Shared", EditorStyles.boldLabel);
_cellW = EditorGUILayout.IntField("Cell width (W)", _cellW);
_cellH = EditorGUILayout.IntField("Cell height (H)", _cellH);
_outputFolder = EditorGUILayout.TextField("Output folder", _outputFolder);
_sliceInPlace = EditorGUILayout.ToggleLeft(
"Slice tại asset nguồn (ghi đè import Multiple lên atlas đang chọn) — Slice in-place on source asset",
_sliceInPlace);
if (_sliceInPlace)
{
EditorGUILayout.HelpBox(
"Cảnh báo: Unity sẽ đổi import của file atlas gốc thành Sprite Multiple full lưới.\n" +
"Warning: overwrites source texture import settings.",
MessageType.Warning);
}
else
{
EditorGUILayout.HelpBox(
"Mặc định: copy atlas vào Output/Emotions{N}/Emotions{N}_atlas.png rồi Multiple slice (giữ nguyên file nguồn).\n" +
"Default: copy atlas to output folder then slice (source unchanged).",
MessageType.None);
}
EditorGUILayout.Space(8f);
EditorGUILayout.LabelField("Một bộ / Single set", EditorStyles.boldLabel);
_sourceAtlas = (Texture2D)EditorGUILayout.ObjectField("Atlas", _sourceAtlas, typeof(Texture2D), false);
_txtAsset = (TextAsset)EditorGUILayout.ObjectField("EmotionsN.txt", _txtAsset, typeof(TextAsset), false);
_emotionSetIndex = EditorGUILayout.IntField("Emotion set index (N)", _emotionSetIndex);
EditorGUILayout.Space();
if (GUILayout.Button("Convert → EmotionSetData + Atlas (Multiple)"))
{
RunConvertSingle();
}
EditorGUILayout.Space(12f);
EditorGUILayout.LabelField("Batch → EmotionLibrary", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("+ Thêm slot", GUILayout.Width(100)))
_batchSlots.Add(new EmotionBatchSlot { EmotionSetIndex = NextSuggestedSetIndex() });
if (GUILayout.Button("− Xóa slot cuối", GUILayout.Width(120)) && _batchSlots.Count > 0)
_batchSlots.RemoveAt(_batchSlots.Count - 1);
EditorGUILayout.LabelField($"Số slot: {_batchSlots.Count}");
EditorGUILayout.EndHorizontal();
_batchScroll = EditorGUILayout.BeginScrollView(_batchScroll, GUILayout.MinHeight(160f));
for (int i = 0; i < _batchSlots.Count; i++)
{
var slot = _batchSlots[i];
EditorGUILayout.BeginVertical(EditorStyles.helpBox);
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField($"#{i}", GUILayout.Width(24));
slot.EmotionSetIndex = EditorGUILayout.IntField("Set N", slot.EmotionSetIndex, GUILayout.Width(120));
slot.Atlas = (Texture2D)EditorGUILayout.ObjectField(slot.Atlas, typeof(Texture2D), false);
slot.Txt = (TextAsset)EditorGUILayout.ObjectField(slot.Txt, typeof(TextAsset), false);
if (GUILayout.Button("×", GUILayout.Width(22)))
{
_batchSlots.RemoveAt(i);
i--;
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
continue;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndScrollView();
_libraryAssetPath = EditorGUILayout.TextField("Library .asset path", _libraryAssetPath);
EditorGUILayout.Space();
if (GUILayout.Button("Convert batch → EmotionLibrary.asset"))
{
RunConvertLibrary();
}
EditorGUILayout.Space(12f);
DrawTMPSection();
EditorGUILayout.Space(8f);
EditorGUILayout.HelpBox(
"Mỗi bộ: một texture **Sprite Multiple**, tên sub-sprite `cell_0000` … theo chỉ số ô (giống C++).\n" +
"Each set: one **Sprite Multiple** texture; sub-sprite names `cell_0000` … by cell index.",
MessageType.Info);
}
// ─────────────────────────────────────────────────────────────────────
// TMP Sprite Asset section
// ─────────────────────────────────────────────────────────────────────
private void DrawTMPSection()
{
_showTmpSection = EditorGUILayout.Foldout(_showTmpSection, "TMP Sprite Asset", true, EditorStyles.foldoutHeader);
if (!_showTmpSection) return;
EditorGUI.indentLevel++;
_tmpSpriteFolder = EditorGUILayout.TextField(
new GUIContent("Sprite Assets folder",
"Thư mục lưu TMP_SpriteAsset (.asset). Mặc định: Assets/TextMesh Pro/Resources/Sprite Assets\n" +
"Folder for TMP_SpriteAsset files. Default: Assets/TextMesh Pro/Resources/Sprite Assets"),
_tmpSpriteFolder);
_tmpCellSizePt = EditorGUILayout.FloatField(
new GUIContent("Cell size (pt)",
"Chiều cao glyph theo đơn vị TMP point. 0 = dùng Cell height (pixel).\n" +
"Glyph height in TMP points. 0 = use Cell height in pixels."),
_tmpCellSizePt);
_genTmpOnConvert = EditorGUILayout.ToggleLeft(
"Tự động gen TMP Sprite Asset sau mỗi lần Convert — Auto-generate TMP Sprite Asset after Convert",
_genTmpOnConvert);
EditorGUILayout.HelpBox(
"Output: /Emotions {N}.asset + Emotions {N} Material.mat\n" +
"Tên sub-sprite khớp với atlas: cell_0000 … (dùng cho trong TMP).\n" +
"Sub-sprite names match atlas: cell_0000 … (use in TMP).",
MessageType.Info);
EditorGUILayout.Space(4f);
// Nút gen standalone từ snapshot cuối — Standalone gen button from last snapshot
EditorGUI.BeginDisabledGroup(_lastSnapshot == null);
if (GUILayout.Button("Gen TMP Sprite Asset từ lần Convert cuối — from last Convert"))
RunGenTMPSingle(_lastSnapshot);
EditorGUI.EndDisabledGroup();
if (_lastSnapshot == null)
EditorGUILayout.HelpBox("Chạy Convert single ít nhất một lần để kích hoạt nút này.\nRun Convert (single) once to enable this button.", MessageType.None);
EditorGUI.indentLevel--;
}
private int NextSuggestedSetIndex()
{
int max = -1;
foreach (var s in _batchSlots)
{
if (s.EmotionSetIndex > max)
max = s.EmotionSetIndex;
}
return max + 1;
}
private void RunConvertSingle()
{
if (!EmotionAtlasConverterCore.ConvertOneSet(
_sourceAtlas, _txtAsset, _emotionSetIndex, _cellW, _cellH, _outputFolder, _sliceInPlace,
out var snapshot, out string err))
{
EditorUtility.DisplayDialog("Error", err, "OK");
return;
}
_lastSnapshot = snapshot;
var so = ScriptableObject.CreateInstance();
EmotionAtlasConverterCore.ApplySnapshotToSetSO(so, snapshot);
string setFolder = $"{_outputFolder}/Emotions{_emotionSetIndex}";
string soPath = $"{setFolder}/EmotionSetData_{_emotionSetIndex}.asset";
AssetDatabase.CreateAsset(so, soPath);
AssetDatabase.SaveAssets();
string msg = $"Đã tạo:\n{soPath}\nAtlas (Multiple) trong thư mục set.";
if (_genTmpOnConvert)
{
TMP_SpriteAsset tmpAsset = RunGenTMPSingle(snapshot);
if (tmpAsset != null)
msg += $"\nTMP Sprite Asset: {AssetDatabase.GetAssetPath(tmpAsset)}";
}
EditorUtility.DisplayDialog("Done", msg, "OK");
EditorGUIUtility.PingObject(so);
}
///
/// Gen TMP_SpriteAsset cho một snapshot. Trả về asset nếu thành công, null nếu thất bại.
/// Generate TMP_SpriteAsset for one snapshot. Returns asset on success, null on failure.
///
private TMP_SpriteAsset RunGenTMPSingle(EmotionSetSnapshot snapshot)
{
if (snapshot == null) return null;
string outPath = EmotionAtlasConverterCore.DefaultTMPSpriteAssetPath(_tmpSpriteFolder, snapshot.EmotionSetIndex);
if (!EmotionAtlasConverterCore.CreateTMPSpriteAsset(snapshot, outPath, _tmpCellSizePt, out var tmpAsset, out string err))
{
EditorUtility.DisplayDialog("TMP Gen Error", $"Set {snapshot.EmotionSetIndex}: {err}", "OK");
return null;
}
EditorGUIUtility.PingObject(tmpAsset);
return tmpAsset;
}
private void RunConvertLibrary()
{
if (!EmotionAtlasConverterCore.TryValidateBatchIndices(_batchSlots, out string dupErr))
{
EditorUtility.DisplayDialog("Error", dupErr, "OK");
return;
}
var library = ScriptableObject.CreateInstance();
library.Sets.Clear();
int ok = 0;
int total = _batchSlots.Count;
for (int i = 0; i < total; i++)
{
var slot = _batchSlots[i];
if (slot.Atlas == null && slot.Txt == null)
continue;
if (slot.Atlas == null || slot.Txt == null)
{
EditorUtility.DisplayDialog("Error",
$"Slot #{i} (Set N={slot.EmotionSetIndex}): cần cả Atlas và TXT.",
"OK");
Object.DestroyImmediate(library);
return;
}
EditorUtility.DisplayProgressBar("Emotion Library", $"Set {slot.EmotionSetIndex}…", (float)i / Mathf.Max(1, total));
if (!EmotionAtlasConverterCore.ConvertOneSet(
slot.Atlas, slot.Txt, slot.EmotionSetIndex, _cellW, _cellH, _outputFolder, _sliceInPlace,
out var snapshot, out string err))
{
EditorUtility.ClearProgressBar();
EditorUtility.DisplayDialog("Error", $"Set {slot.EmotionSetIndex}: {err}", "OK");
Object.DestroyImmediate(library);
return;
}
library.Sets.Add(snapshot);
ok++;
}
EditorUtility.ClearProgressBar();
if (ok == 0)
{
EditorUtility.DisplayDialog("Error", "Không có cặp Atlas+TXT hợp lệ.", "OK");
Object.DestroyImmediate(library);
return;
}
string dir = System.IO.Path.GetDirectoryName(_libraryAssetPath)?.Replace('\\', '/') ?? _outputFolder;
if (!string.IsNullOrEmpty(dir))
EmotionAtlasConverterCore.EnsureFolder(dir);
AssetDatabase.CreateAsset(library, _libraryAssetPath);
AssetDatabase.SaveAssets();
int tmpOk = 0;
if (_genTmpOnConvert)
{
for (int i = 0; i < library.Sets.Count; i++)
{
var snap = library.Sets[i];
EditorUtility.DisplayProgressBar("TMP Sprite Assets", $"Set {snap.EmotionSetIndex}…", (float)i / Mathf.Max(1, library.Sets.Count));
string outPath = EmotionAtlasConverterCore.DefaultTMPSpriteAssetPath(_tmpSpriteFolder, snap.EmotionSetIndex);
if (EmotionAtlasConverterCore.CreateTMPSpriteAsset(snap, outPath, _tmpCellSizePt, out _, out string tmpErr))
tmpOk++;
else
Debug.LogWarning($"[EmotionConverter] TMP gen Set {snap.EmotionSetIndex}: {tmpErr}");
}
EditorUtility.ClearProgressBar();
}
string doneMsg = $"EmotionLibrary: {ok} bộ.\n{_libraryAssetPath}";
if (_genTmpOnConvert)
doneMsg += $"\nTMP Sprite Assets: {tmpOk}/{ok} bộ → {_tmpSpriteFolder}";
EditorUtility.DisplayDialog("Done", doneMsg, "OK");
EditorGUIUtility.PingObject(library);
}
}
}