519 lines
18 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|