#nullable enable using Cysharp.Threading.Tasks; using NUnit.Framework.Interfaces; using System; using System.Collections.Generic; using System.Threading.Tasks; using UnityEngine; namespace UVC.UI.Tab { /// /// 전체 탭 시스템의 UI를 관리하는 View 클래스입니다. /// TabContentLoader와 협력하여 컨텐츠 로딩도 담당합니다. /// public class TabView : MonoBehaviour { /// /// 전체 탭 시스템의 UI를 관리하는 View 클래스입니다. /// TabContentLoader와 협력하여 컨텐츠 로딩도 담당합니다. /// [Header("UI 설정")] [Tooltip("탭 버튼들이 생성될 부모 Transform")] [SerializeField] private Transform? tabButtonContainer; [Tooltip("탭 컨텐츠들이 표시될 부모 Transform")] [SerializeField] private Transform? contentContainer; [Tooltip("탭 버튼 프리팹")] [SerializeField] private GameObject? tabButtonPrefab; [Header("컨텐츠 로딩")] [Tooltip("컨텐츠 로더 (지연 로딩 기능, 선택사항)")] [SerializeField] private TabContentLoader? contentLoader; // 생성된 탭 버튼 컴포넌트들 private List _tabButtons = new List(); // 모든 컨텐츠 인스턴스를 저장 private List _allContentInstances = new List(); // 탭 설정 정보 (TabController에서 전달받음) private List _tabConfigs = new List(); // 탭 버튼 클릭 시 호출될 콜백 private Action? _onTabButtonClicked; /// /// 컨텐츠 컨테이너를 반환합니다. /// public Transform? ContentContainer => contentContainer; /// /// 탭 시스템을 초기화합니다. /// /// 탭 데이터 목록 /// 탭 설정 목록 (TabController에서 전달) /// 탭 선택 시 호출될 콜백 public void InitializeTabs(IReadOnlyList tabs, List configs, Action onTabSelected) { // 기존 탭 정리 ClearTabs(); _onTabButtonClicked = onTabSelected; _tabConfigs = new List(configs); // 컨텐츠 인스턴스 리스트 초기화 _allContentInstances.Clear(); for (int i = 0; i < tabs.Count; i++) { _allContentInstances.Add(null); } // TabContentLoader 초기화 (있는 경우) if (contentLoader != null) { InitializeContentLoader(); } // 탭 버튼 생성 for (int i = 0; i < tabs.Count; i++) { CreateTabButton(i, tabs[i]); } // 지연 로딩을 사용하지 않는 탭들 미리 로드 PreloadNonLazyTabs(); } /// /// TabContentLoader를 초기화합니다. /// private void InitializeContentLoader() { if (contentLoader == null) return; // 기존 설정 클리어 contentLoader.ClearLazyContents(); // 지연 로딩 탭들을 ContentLoader에 등록 foreach (var config in _tabConfigs) { if (config.useLazyLoading) { contentLoader.AddLazyTabContent(config.tabID, config.contentPath, config.initialData); } } // ContentLoader 초기화 contentLoader.InitializeTabLoader(); } /// /// 지연 로딩을 사용하지 않는 탭들을 미리 로드합니다. /// private void PreloadNonLazyTabs() { for (int i = 0; i < _tabConfigs.Count; i++) { var config = _tabConfigs[i]; if (!config.useLazyLoading) { GameObject? instance = LoadTabContentDirectly(config); if (instance != null) { _allContentInstances[i] = instance; } } } Debug.Log("지연 로딩을 사용하지 않는 탭들이 미리 로드되었습니다."); } /// /// 탭 컨텐츠를 직접 로드합니다. /// private GameObject? LoadTabContentDirectly(TabContentConfig config) { try { // 프리팹 로드 GameObject prefab = Resources.Load(config.contentPath); if (prefab == null) { Debug.LogError($"탭 컨텐츠 프리팹을 찾을 수 없음: {config.contentPath}"); return null; } // 인스턴스 생성 GameObject instance = Instantiate(prefab, contentContainer); instance.name = $"TabContent_{config.tabID}"; instance.SetActive(false); // 초기 데이터 전달 if (config.initialData != null) { ITabContent? tabContent = instance.GetComponent(); tabContent?.SetContentData(config.initialData); } Debug.Log($"탭 컨텐츠 직접 로드됨: {config.tabID}"); return instance; } catch (System.Exception ex) { Debug.LogError($"탭 컨텐츠 직접 로드 중 오류 발생: {ex.Message}"); return null; } } /// /// 탭 버튼을 생성합니다. /// private void CreateTabButton(int index, TabData tabData) { if (tabButtonPrefab == null) { Debug.LogError("탭 버튼 프리팹이 설정되지 않았습니다."); return; } if (tabButtonContainer == null) { Debug.LogError("탭 버튼 컨테이너가 설정되지 않았습니다."); return; } // 탭 버튼 인스턴스 생성 GameObject tabButtonGO = Instantiate(tabButtonPrefab, tabButtonContainer); TabButtonView? tabButton = tabButtonGO.GetComponent(); if (tabButton == null) { Debug.LogError("탭 버튼 프리팹에 TabButtonView 컴포넌트가 없습니다."); Destroy(tabButtonGO); return; } // 탭 버튼 설정 tabButton.Setup(index, tabData.tabName, tabData.tabIconPath, OnTabButtonClicked); _tabButtons.Add(tabButton); } /// /// 탭 버튼 클릭 이벤트 처리 /// private void OnTabButtonClicked(int tabIndex) { _onTabButtonClicked?.Invoke(tabIndex); } /// /// 활성화된 탭을 업데이트합니다. /// 필요한 경우 컨텐츠를 로드합니다. /// /// 활성화할 탭 인덱스 /// 현재 활성화 된 탭 인덱스. 없으면 -1 /// 탭 데이터 /// 업데이트할 데이터 (선택적) public async UniTask UpdateActiveTab(int tabIndex, int prevTabIndex, TabData tabData, object? updateData = null) { if (tabIndex < 0 || tabIndex >= _tabConfigs.Count) return; var config = _tabConfigs[tabIndex]; // 컨텐츠가 아직 로드되지 않은 경우 로드 if (_allContentInstances[tabIndex] == null) { if (config.useLazyLoading && contentLoader != null) { // 지연 로딩을 통해 로드 GameObject? lazyInstance = contentLoader.LoadTabContent(config.tabID, tabData.contentData); if (lazyInstance != null) { // ContentContainer로 이동 (일관성 확보) if (lazyInstance.transform.parent != contentContainer) { lazyInstance.transform.SetParent(contentContainer, false); } _allContentInstances[tabIndex] = lazyInstance; } } else { // 직접 로드 _allContentInstances[tabIndex] = LoadTabContentDirectly(config); } } //현재 활성화 된 탭의 컨텐츠가 있다면 OnClose 호출 if (prevTabIndex > -1) { GameObject? contentInstance = _allContentInstances[prevTabIndex]; if (contentInstance != null) { ITabContent? tabContent = contentInstance.GetComponent(); if (tabContent != null) await tabContent.OnCloseAsync(); } } // 모든 컨텐츠 비활성화 foreach (var content in _allContentInstances) { if (content != null) content.SetActive(false); } // 활성화된 컨텐츠만 활성화 GameObject? activeContent = _allContentInstances[tabIndex]; if (activeContent != null) { activeContent.SetActive(true); // 데이터 업데이트 ITabContent? tabContent = activeContent.GetComponent(); tabContent?.SetContentData(tabData.contentData); if(updateData != null) tabContent?.UpdateContentData(updateData); } // 탭 버튼 상태 업데이트 for (int i = 0; i < _tabButtons.Count; i++) { if (_tabButtons[i] != null) _tabButtons[i].SetActive(i == tabIndex); } } /// /// 특정 탭의 컨텐츠 데이터를 업데이트합니다. /// /// 업데이트할 탭 인덱스 /// 새로운 데이터 public void UpdateTabContentData(int tabIndex, object? data) { if (tabIndex < 0 || tabIndex >= _allContentInstances.Count) return; GameObject? contentInstance = _allContentInstances[tabIndex]; if (contentInstance != null && data != null) { ITabContent? tabContent = contentInstance.GetComponent(); tabContent?.SetContentData(data); } // 지연 로딩 탭인 경우 ContentLoader에도 업데이트 if (tabIndex < _tabConfigs.Count) { var config = _tabConfigs[tabIndex]; if (config.useLazyLoading && contentLoader != null) { contentLoader.SetTabContentData(config.tabID, data); } } } /// /// 특정 탭의 컨텐츠 인스턴스를 가져옵니다. /// /// 탭 ID /// 자동 로드 여부 /// 컨텐츠 인스턴스 public GameObject? GetTabInstance(string tabID, bool autoLoad = false) { int tabIndex = GetTabIndexByID(tabID); if (tabIndex < 0) return null; // 이미 로드된 인스턴스가 있으면 반환 if (_allContentInstances[tabIndex] != null) { return _allContentInstances[tabIndex]; } // 자동 로드가 활성화된 경우 if (autoLoad && tabIndex < _tabConfigs.Count) { var config = _tabConfigs[tabIndex]; if (config.useLazyLoading && contentLoader != null) { // 지연 로딩을 통해 로드 GameObject? instance = contentLoader.LoadTabContent(tabID); if (instance != null) { // ContentContainer로 이동 if (instance.transform.parent != contentContainer) { instance.transform.SetParent(contentContainer, false); } _allContentInstances[tabIndex] = instance; } return instance; } else { // 직접 로드 GameObject? instance = LoadTabContentDirectly(config); if (instance != null) { _allContentInstances[tabIndex] = instance; } return instance; } } return null; } /// /// 특정 탭의 컴포넌트를 가져옵니다. /// public T? GetTabComponent(string tabID, bool autoLoad = false) where T : Component { GameObject? instance = GetTabInstance(tabID, autoLoad); return instance?.GetComponent(); } /// /// 탭 ID로 인덱스를 찾습니다. /// private int GetTabIndexByID(string tabID) { for (int i = 0; i < _tabConfigs.Count; i++) { if (_tabConfigs[i].tabID == tabID) { return i; } } return -1; } /// /// 특정 탭이 로드되었는지 확인합니다. /// public bool IsTabLoaded(string tabID) { int tabIndex = GetTabIndexByID(tabID); if (tabIndex < 0) return false; return _allContentInstances[tabIndex] != null; } /// /// ContentLoader 관련 메서드들 (있는 경우에만 동작) /// public void UnloadAllExceptCurrent(int currentTabIndex) { if (contentLoader != null && currentTabIndex < _tabConfigs.Count) { string currentTabID = _tabConfigs[currentTabIndex].tabID; contentLoader.UnloadAllExceptCurrent(currentTabID); // 언로드된 탭들의 인스턴스 참조도 제거 for (int i = 0; i < _allContentInstances.Count; i++) { if (i != currentTabIndex && _tabConfigs[i].useLazyLoading) { _allContentInstances[i] = null; } } } } public bool UnloadTabContent(string tabID) { if (contentLoader != null) { bool result = contentLoader.UnloadTabContent(tabID); if (result) { // 인스턴스 참조도 제거 int tabIndex = GetTabIndexByID(tabID); if (tabIndex >= 0) { _allContentInstances[tabIndex] = null; } } return result; } return false; } public void PreloadAllTabs() { if (contentLoader != null) { contentLoader.PreloadAllTabs(); // 지연 로딩 탭들의 인스턴스를 _allContentInstances에 등록 for (int i = 0; i < _tabConfigs.Count; i++) { var config = _tabConfigs[i]; if (config.useLazyLoading) { GameObject? instance = contentLoader.GetTabInstance(config.tabID); if (instance != null) { // ContentContainer로 이동 if (instance.transform.parent != contentContainer) { instance.transform.SetParent(contentContainer, false); } _allContentInstances[i] = instance; } } } } } /// /// 모든 탭과 관련 게임오브젝트를 정리합니다. /// private void ClearTabs() { // 탭 버튼 제거 foreach (var button in _tabButtons) { if (button != null) Destroy(button.gameObject); } _tabButtons.Clear(); // 컨텐츠 인스턴스 제거 foreach (var content in _allContentInstances) { if (content != null) Destroy(content); } _allContentInstances.Clear(); // 설정 클리어 _tabConfigs.Clear(); _onTabButtonClicked = null; } protected virtual void OnDestroy() { // 탭과 관련된 모든 리소스를 정리합니다. ClearTabs(); // ContentLoader가 있다면 정리 if (contentLoader != null) { contentLoader.ClearLazyContents(); contentLoader = null; } } } }