Files

519 lines
18 KiB
C#

#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
{
/// <summary>
/// 전체 탭 시스템의 UI를 관리하는 View 클래스입니다.
/// TabContentLoader와 협력하여 컨텐츠 로딩도 담당합니다.
/// </summary>
public class TabView : MonoBehaviour
{
/// <summary>
/// 전체 탭 시스템의 UI를 관리하는 View 클래스입니다.
/// TabContentLoader와 협력하여 컨텐츠 로딩도 담당합니다.
/// </summary>
[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<TabButtonView> _tabButtons = new List<TabButtonView>();
// 모든 컨텐츠 인스턴스를 저장
private List<GameObject?> _allContentInstances = new List<GameObject?>();
// 탭 설정 정보 (TabController에서 전달받음)
private List<TabContentConfig> _tabConfigs = new List<TabContentConfig>();
// 탭 버튼 클릭 시 호출될 콜백
private Action<int>? _onTabButtonClicked;
/// <summary>
/// 컨텐츠 컨테이너를 반환합니다.
/// </summary>
public Transform? ContentContainer => contentContainer;
//활성화 탭 버튼을 클릭 시 해당 탭 비활성화 할지 여부
private bool togglableTab = false;
/// <summary>
/// 탭 시스템을 초기화합니다.
/// </summary>
/// <param name="tabs">탭 데이터 목록</param>
/// <param name="configs">탭 설정 목록 (TabController에서 전달)</param>
/// <param name="onTabSelected">탭 선택 시 호출될 콜백</param>
/// <param name="togglableTab">전체를 비활성화 할 수 있는지</param>
public void InitializeTabs(IReadOnlyList<TabData> tabs, List<TabContentConfig> configs, Action<int> onTabSelected, bool togglableTab = false)
{
this.togglableTab = togglableTab;
// 기존 탭 정리
ClearTabs();
_onTabButtonClicked = onTabSelected;
_tabConfigs = new List<TabContentConfig>(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();
}
/// <summary>
/// TabContentLoader를 초기화합니다.
/// </summary>
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();
}
/// <summary>
/// 지연 로딩을 사용하지 않는 탭들을 미리 로드합니다.
/// </summary>
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("지연 로딩을 사용하지 않는 탭들이 미리 로드되었습니다.");
}
/// <summary>
/// 탭 컨텐츠를 직접 로드합니다.
/// </summary>
private GameObject? LoadTabContentDirectly(TabContentConfig config)
{
try
{
// 프리팹 로드
GameObject prefab = Resources.Load<GameObject>(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<ITabContent>();
tabContent?.SetContentData(config.initialData);
}
Debug.Log($"탭 컨텐츠 직접 로드됨: {config.tabID}");
return instance;
}
catch (System.Exception ex)
{
Debug.LogError($"탭 컨텐츠 직접 로드 중 오류 발생: {ex.Message}");
return null;
}
}
/// <summary>
/// 탭 버튼을 생성합니다.
/// </summary>
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<TabButtonView>();
if (tabButton == null)
{
Debug.LogError("탭 버튼 프리팹에 TabButtonView 컴포넌트가 없습니다.");
Destroy(tabButtonGO);
return;
}
// 탭 버튼 설정
tabButton.Setup(index, tabData.tabName, tabData.tabIconPath, OnTabButtonClicked);
_tabButtons.Add(tabButton);
}
/// <summary>
/// 탭 버튼 클릭 이벤트 처리
/// </summary>
private void OnTabButtonClicked(int tabIndex)
{
_onTabButtonClicked?.Invoke(tabIndex);
}
/// <summary>
/// 활성화된 탭을 업데이트합니다.
/// 필요한 경우 컨텐츠를 로드합니다.
/// </summary>
/// <param name="tabIndex">활성화할 탭 인덱스</param>
/// <param name="prevTabIndex">현재 활성화 된 탭 인덱스. 없으면 -1</param>
/// <param name="tabData">탭 데이터</param>
/// <param name="updateData">업데이트할 데이터 (선택적)</param>
public async UniTask UpdateActiveTab(int tabIndex, int prevTabIndex, TabData? tabData, object? updateData = null)
{
if ((tabIndex < 0 && !togglableTab) || tabIndex >= _tabConfigs.Count) return;
if (tabIndex > -1 && tabData != null)
{
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<ITabContent>();
if (tabContent != null) await tabContent.OnCloseAsync();
}
}
// 모든 컨텐츠 비활성화
foreach (var content in _allContentInstances)
{
if (content != null)
content.SetActive(false);
}
if (tabIndex > -1 && tabData != null)
{
// 활성화된 컨텐츠만 활성화
GameObject? activeContent = _allContentInstances[tabIndex];
if (activeContent != null)
{
activeContent.SetActive(true);
// 데이터 업데이트
ITabContent? tabContent = activeContent.GetComponent<ITabContent>();
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);
}
}
/// <summary>
/// 특정 탭의 컨텐츠 데이터를 업데이트합니다.
/// </summary>
/// <param name="tabIndex">업데이트할 탭 인덱스</param>
/// <param name="data">새로운 데이터</param>
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<ITabContent>();
tabContent?.SetContentData(data);
}
// 지연 로딩 탭인 경우 ContentLoader에도 업데이트
if (tabIndex < _tabConfigs.Count)
{
var config = _tabConfigs[tabIndex];
if (config.useLazyLoading && contentLoader != null)
{
contentLoader.SetTabContentData(config.tabID, data);
}
}
}
/// <summary>
/// 특정 탭의 컨텐츠 인스턴스를 가져옵니다.
/// </summary>
/// <param name="tabID">탭 ID</param>
/// <param name="autoLoad">자동 로드 여부</param>
/// <returns>컨텐츠 인스턴스</returns>
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;
}
/// <summary>
/// 특정 탭의 컴포넌트를 가져옵니다.
/// </summary>
public T? GetTabComponent<T>(string tabID, bool autoLoad = false) where T : Component
{
GameObject? instance = GetTabInstance(tabID, autoLoad);
return instance?.GetComponent<T>();
}
/// <summary>
/// 탭 ID로 인덱스를 찾습니다.
/// </summary>
private int GetTabIndexByID(string tabID)
{
for (int i = 0; i < _tabConfigs.Count; i++)
{
if (_tabConfigs[i].tabID == tabID)
{
return i;
}
}
return -1;
}
/// <summary>
/// 특정 탭이 로드되었는지 확인합니다.
/// </summary>
public bool IsTabLoaded(string tabID)
{
int tabIndex = GetTabIndexByID(tabID);
if (tabIndex < 0) return false;
return _allContentInstances[tabIndex] != null;
}
/// <summary>
/// ContentLoader 관련 메서드들 (있는 경우에만 동작)
/// </summary>
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;
}
}
}
}
}
/// <summary>
/// 모든 탭과 관련 게임오브젝트를 정리합니다.
/// </summary>
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;
}
}
}
}