using System.Collections.Generic; using System.IO; using TMPro; using UnityEngine; using UnityEngine.UI; public class SongSelectManager : MonoBehaviour { [SerializeField] private Button tabAllBtn; [SerializeField] private Button tabOwnedBtn; [SerializeField] private RectTransform cardContainer; [SerializeField] private SongDetailPanel detailPanel; [SerializeField] private DownloadManager downloadManager; [SerializeField] private GameObject loadingOverlay; [SerializeField] private GameObject errorOverlay; [SerializeField] private TMP_Text errorText; private static readonly Color TabActiveBg = new Color(0.05f, 0.82f, 0.95f, 0.42f); private static readonly Color TabInactiveBg = new Color(0.09f, 0.22f, 0.27f, 0.66f); private static readonly Color TabActiveText = new Color(0.92f, 1.0f, 1.0f, 1.0f); private static readonly Color TabInactiveText = new Color(0.72f, 0.86f, 0.90f, 0.82f); private static readonly Color TabActiveOutline = new Color(0.25f, 0.96f, 1.0f, 0.55f); private static string CachePath => Path.Combine(Application.persistentDataPath, "songs_cache.json"); private List allSongs = new List(); private bool showingOwned = false; private TMP_FontAsset _cardFont; private void Start() { // NanumGothic SDF를 직접 로드 — Resources 경로에 있어야 함 _cardFont = Resources.Load("Fonts & Materials/NanumGothic SDF"); if (_cardFont == null) _cardFont = Resources.Load("Fonts & Materials/LiberationSans SDF"); tabAllBtn .onClick.AddListener(() => SwitchTab(false)); tabOwnedBtn.onClick.AddListener(() => SwitchTab(true)); detailPanel.gameObject.SetActive(false); SetTabVisual(false); FetchSongs(); } private void SwitchTab(bool owned) { showingOwned = owned; SetTabVisual(owned); RefreshCards(); } private void SetTabVisual(bool owned) { ApplyTabStyle(tabAllBtn, !owned); ApplyTabStyle(tabOwnedBtn, owned); } private static void ApplyTabStyle(Button btn, bool active) { if (btn == null) return; Color bg = active ? TabActiveBg : TabInactiveBg; if (btn.targetGraphic is Image img) img.color = bg; var colors = btn.colors; colors.normalColor = bg; colors.highlightedColor = active ? new Color(0.10f, 0.95f, 1.0f, 0.58f) : new Color(0.14f, 0.34f, 0.40f, 0.72f); colors.pressedColor = active ? new Color(0.02f, 0.58f, 0.72f, 0.72f) : new Color(0.08f, 0.20f, 0.24f, 0.82f); colors.selectedColor = colors.highlightedColor; colors.disabledColor = new Color(0.05f, 0.10f, 0.12f, 0.45f); btn.colors = colors; TMP_Text label = btn.GetComponentInChildren(); if (label != null) label.color = active ? TabActiveText : TabInactiveText; Outline outline = btn.GetComponent() ?? btn.gameObject.AddComponent(); outline.enabled = active; outline.effectColor = TabActiveOutline; outline.effectDistance = new Vector2(0.0f, -0.35f); } private void FetchSongs() { loadingOverlay.SetActive(true); errorOverlay .SetActive(false); downloadManager.FetchSongsList( onSuccess: list => { allSongs = list.songs ?? new List(); AddLocalForcedRankDummies(allSongs); SaveCache(new SongsList { version = list.version, songs = allSongs }); SongLibrary.Instance.ValidateWithFileSystem(downloadManager, allSongs); loadingOverlay.SetActive(false); RefreshCards(); }, onError: _ => { SongsList cached = LoadCache(); loadingOverlay.SetActive(false); if (cached != null) { allSongs = cached.songs; RefreshCards(); } else { errorOverlay.SetActive(true); errorText.text = "Failed to connect to server\nPlease check your internet connection"; } }); } public void RefreshCards() { // DestroyImmediate to avoid deferred-destroy interfering with layout for (int i = cardContainer.childCount - 1; i >= 0; i--) DestroyImmediate(cardContainer.GetChild(i).gameObject); List songs = showingOwned ? allSongs.FindAll(s => SongLibrary.Instance.IsSongDownloaded(s.id)) : allSongs; foreach (SongInfo song in songs) SpawnCard(song); // Order matters: layout first → card gets size → then canvas update → anchored children recalculate LayoutRebuilder.ForceRebuildLayoutImmediate(cardContainer); Canvas.ForceUpdateCanvases(); } private void SpawnCard(SongInfo song) { bool downloaded = SongLibrary.Instance.IsSongDownloaded(song.id); var card = new GameObject(song.title); card.transform.SetParent(cardContainer, false); var le = card.AddComponent(); le.preferredHeight = 13f; le.flexibleWidth = 1f; var bg = card.AddComponent(); bg.color = new Color(1f, 1f, 1f, 0.06f); var btn = card.AddComponent