Files
XRLib/Assets/Scripts/UVC/UI/List/ComponentList/ComponentListItem.cs

252 lines
11 KiB
C#
Raw Normal View History

2025-08-08 18:33:29 +09:00
#nullable enable
using DG.Tweening;
using Gpm.Ui;
using UnityEngine;
using UnityEngine.UI;
2025-08-11 18:30:13 +09:00
using UVC.Factory;
2025-08-08 18:33:29 +09:00
using UVC.Factory.Component;
2025-08-11 14:58:18 +09:00
using UVC.Locale;
2025-08-08 18:33:29 +09:00
using UVC.UI.Modal;
2025-08-11 18:30:13 +09:00
namespace UVC.UI.List.ComponentList
2025-08-08 18:33:29 +09:00
{
2025-08-11 14:58:18 +09:00
/// <summary>
/// InfiniteScroll에 표시될 개별 아이템을 정의하는 클래스입니다.
/// 이 아이템은 '카테고리' 또는 '일반 항목' 두 가지 상태를 가질 수 있으며,
/// ComponentListItemData의 isCategory 값에 따라 UI가 동적으로 변경됩니다.
/// </summary>
2025-08-08 18:33:29 +09:00
public class ComponentListItem : InfiniteScrollItem
{
2025-08-11 14:58:18 +09:00
// [카테고리 UI 요소]
[Header("Category UI")]
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected GameObject categoryRoot; // 카테리 UI의 최상위 GameObject
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected TMPro.TextMeshProUGUI categoryText; // 카테고리 이름을 표시할 텍스트
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected TMPro.TextMeshProUGUI categoryBageText; // 카테고리에 속한 항목 수를 표시할 텍스트
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected Button categoryExtendButton; // 카테고리 확장/축소 버튼
2025-08-08 18:33:29 +09:00
2025-08-11 14:58:18 +09:00
// [일반 항목 UI 요소]
[Header("General Item UI")]
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected GameObject generalRoot; // 일반 항목 UI의 최상위 GameObject
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected TMPro.TMP_InputField generalText; // 일반 항목의 이름을 표시할 입력 필드
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected TMPro.TextMeshProUGUI generalOptionText; // 일반 항목의 옵션을 표시할 텍스트
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected Button showButton; // 항목을 씬에 표시하는 버튼
2025-08-08 18:33:29 +09:00
[SerializeField]
2025-08-11 18:30:13 +09:00
protected Button hideButton; // 항목을 씬에서 숨기는 버튼
2025-08-08 18:33:29 +09:00
2025-08-11 18:30:13 +09:00
// 애니메이션 중복 실행을 방지하기 위한 플래그
protected bool isAnimating = false;
2025-08-08 18:33:29 +09:00
/// <summary>
2025-08-11 14:58:18 +09:00
/// InfiniteScroll에 의해 데이터가 이 아이템에 할당될 때 호출되는 핵심 메서드입니다.
/// 스크롤 시 아이템이 재활용될 때마다 새로운 데이터로 이 메서드가 호출되어 UI를 갱신합니다.
2025-08-08 18:33:29 +09:00
/// </summary>
2025-08-11 14:58:18 +09:00
/// <param name="scrollData">이 아이템에 표시할 데이터입니다. `InfiniteScrollData`를 상속받은 `ComponentListItemData` 객체입니다.</param>
/// <example>
/// <code>
/// // 1. 표시할 데이터를 생성합니다.
/// var categoryData = new ComponentListItemData
/// {
/// isCategory = true,
/// categoryName = "전기 설비",
/// categoryBadgeCount = 10
/// };
///
/// var generalData = new ComponentListItemData
/// {
/// isCategory = false,
/// generalName = "분전반",
/// generalOption = "옵션 A",
/// factoryObjectInfo = new FactoryObjectInfo { Id = "some-unique-id", Name = "분전반" }
/// };
///
/// // 2. 생성한 데이터를 InfiniteScroll에 추가합니다.
/// // infiniteScroll은 InfiniteScroll 컴포넌트의 인스턴스입니다.
/// infiniteScroll.InsertData(categoryData);
/// infiniteScroll.InsertData(generalData);
///
/// // 위 코드가 실행되면, InfiniteScroll은 각 데이터에 맞는 ComponentListItem을 생성(또는 재활용)하고,
/// // 이 UpdateData 메서드를 호출하여 UI를 데이터에 맞게 설정합니다.
/// </code>
/// </example>
2025-08-08 18:33:29 +09:00
public override void UpdateData(InfiniteScrollData scrollData)
{
// 1. 부모 클래스의 UpdateData를 호출하여 기본 초기화 작업을 수행합니다.
2025-08-11 14:58:18 +09:00
// 이 과정에서 this.scrollData에 매개변수로 받은 scrollData가 할당됩니다.
2025-08-08 18:33:29 +09:00
base.UpdateData(scrollData);
// 2. 매개변수로 받은 scrollData를 실제 사용할 데이터 타입인 ComponentListItemData로 형변환(casting)합니다.
2025-08-11 14:58:18 +09:00
// 이렇게 해야 ComponentListItemData에만 정의된 속성(isCategory, categoryName 등)에 접근할 수 있습니다.
2025-08-08 18:33:29 +09:00
ComponentListItemData itemData = (ComponentListItemData)scrollData;
2025-08-11 14:58:18 +09:00
// 3. 데이터의 isCategory 값에 따라 카테고리 UI 또는 일반 항목 UI를 활성화/비활성화합니다.
2025-08-08 18:33:29 +09:00
categoryRoot.SetActive(itemData.isCategory);
generalRoot.SetActive(!itemData.isCategory);
2025-08-11 14:58:18 +09:00
// 4. 활성화된 UI에 데이터를 채워 넣습니다.
2025-08-08 18:33:29 +09:00
if (itemData.isCategory)
{
2025-08-11 14:58:18 +09:00
// 카테고리 아이템일 경우
2025-08-08 18:33:29 +09:00
categoryText.text = itemData.categoryName;
categoryBageText.text = itemData.categoryBadgeCount.ToString();
}
else
{
2025-08-11 14:58:18 +09:00
// 일반 아이템일 경우
2025-08-08 18:33:29 +09:00
generalText.text = itemData.generalName;
generalOptionText.text = itemData.generalOption;
2025-08-11 14:58:18 +09:00
// 이 아이템과 연결된 실제 씬(Scene) 상의 FactoryObject가 있는지 확인합니다.
2025-08-08 18:33:29 +09:00
if (itemData.factoryObjectInfo != null)
{
2025-08-11 14:58:18 +09:00
// FactoryObjectManager를 통해 ID로 객체를 찾습니다.
2025-08-08 18:33:29 +09:00
FactoryObject? obj = FactoryObjectManager.Instance.FindById(itemData.factoryObjectInfo.Id);
if (obj != null)
{
2025-08-11 14:58:18 +09:00
// 찾은 객체의 활성화 상태에 따라 '보이기'/'숨기기' 버튼의 상태를 결정합니다.
2025-08-08 18:33:29 +09:00
showButton.gameObject.SetActive(!obj.gameObject.activeSelf);
hideButton.gameObject.SetActive(obj.gameObject.activeSelf);
}
}
}
}
2025-08-11 14:58:18 +09:00
/// <summary>
/// 카테고리 아이템의 '확장/축소' 버튼을 클릭했을 때 호출됩니다.
/// </summary>
2025-08-11 18:30:13 +09:00
public virtual void OnExpendButtonClick()
2025-08-08 18:33:29 +09:00
{
2025-08-11 18:30:13 +09:00
// 애니메이션이 진행 중이면 중복 호출을 방지합니다.
if (isAnimating)
{
return;
}
isAnimating = true;
ComponentListItemData itemData = (ComponentListItemData)scrollData;
// 애니메이션을 위해 현재 각도에서 목표 각도로 회전시킵니다.
float targetAngle = itemData.isExpanded ? 90 : 0;
categoryExtendButton.transform.DORotate(new Vector3(0, 0, targetAngle), 0.3f)
.OnComplete(() =>
{
// 애니메이션이 완료되면 플래그를 초기화합니다.
isAnimating = false;
});
// 데이터에 할당된 Action을 호출하여 상태 변경을 상위 리스트에 알립니다.
itemData.OnCategoryExpendAction?.Invoke(itemData);
2025-08-08 18:33:29 +09:00
}
2025-08-11 14:58:18 +09:00
/// <summary>
/// 카테고리 아이템의 '설정' 버튼을 클릭했을 때 호출됩니다.
/// </summary>
2025-08-11 18:30:13 +09:00
public virtual async void OnSettingButtonClick()
2025-08-08 18:33:29 +09:00
{
2025-08-11 14:58:18 +09:00
// 1. 이 아이템에 바인딩된 데이터를 가져옵니다.
2025-08-08 18:33:29 +09:00
ComponentListItemData itemData = (ComponentListItemData)scrollData;
2025-08-11 14:58:18 +09:00
// 2. 모달이 열려있는 동안 카메라 조작을 막기 위해 컨트롤러를 비활성화합니다.
2025-08-08 18:33:29 +09:00
FactoryCameraController.Instance.Enable = false; // 카메라 컨트롤러 비활성화
2025-08-11 14:58:18 +09:00
// 3. 모달에 표시할 콘텐츠를 설정합니다.
2025-08-08 18:33:29 +09:00
var modalContent = new ModalContent("Prefabs/UI/Modal/SettingModal")
{
Title = "설정 카테고리",
Message = $"shortcut:DisplaySetting>{itemData.categoryName}"
2025-08-08 18:33:29 +09:00
};
2025-08-11 14:58:18 +09:00
// 4. 설정 모달을 엽니다. await 키워드를 사용하여 모달이 닫힐 때까지 기다립니다.
2025-08-08 18:33:29 +09:00
await UVC.UI.Modal.Modal.Open<object>(modalContent);
2025-08-11 14:58:18 +09:00
// 5. 모달이 닫히면 카메라 컨트롤러를 다시 활성화합니다.
2025-08-08 18:33:29 +09:00
FactoryCameraController.Instance.Enable = true;
}
2025-08-11 14:58:18 +09:00
/// <summary>
/// 일반 아이템의 '보이기' 버튼을 클릭했을 때 호출됩니다.
/// </summary>
2025-08-11 18:30:13 +09:00
public virtual void OnShowButtonClick()
2025-08-08 18:33:29 +09:00
{
ComponentListItemData itemData = (ComponentListItemData)scrollData;
if (itemData.factoryObjectInfo != null)
{
2025-08-11 14:58:18 +09:00
// ID를 사용하여 씬에서 실제 객체를 찾습니다.
2025-08-08 18:33:29 +09:00
FactoryObject? obj = FactoryObjectManager.Instance.FindById(itemData.factoryObjectInfo.Id);
if (obj != null)
{
2025-08-11 14:58:18 +09:00
// 객체를 활성화(보이게)합니다.
2025-08-08 18:33:29 +09:00
obj.gameObject.SetActive(true);
2025-08-11 14:58:18 +09:00
// '보이기' 버튼은 숨기고 '숨기기' 버튼을 표시합니다.
2025-08-08 18:33:29 +09:00
showButton.gameObject.SetActive(false);
hideButton.gameObject.SetActive(true);
}
}
}
2025-08-11 14:58:18 +09:00
/// <summary>
/// 일반 아이템의 '숨기기' 버튼을 클릭했을 때 호출됩니다.
/// </summary>
2025-08-11 18:30:13 +09:00
public virtual void OnHideButtonClick()
2025-08-08 18:33:29 +09:00
{
ComponentListItemData itemData = (ComponentListItemData)scrollData;
if (itemData.factoryObjectInfo != null)
{
2025-08-11 14:58:18 +09:00
// ID를 사용하여 씬에서 실제 객체를 찾습니다.
2025-08-08 18:33:29 +09:00
FactoryObject? obj = FactoryObjectManager.Instance.FindById(itemData.factoryObjectInfo.Id);
if (obj != null)
{
FactoryObjectSelectionManager.Instance.Deselect(obj);
2025-08-11 14:58:18 +09:00
// 객체를 비활성화(숨기기)합니다.
2025-08-08 18:33:29 +09:00
obj.gameObject.SetActive(false);
2025-08-11 14:58:18 +09:00
// '숨기기' 버튼은 숨기고 '보이기' 버튼을 표시합니다.
2025-08-08 18:33:29 +09:00
showButton.gameObject.SetActive(true);
hideButton.gameObject.SetActive(false);
}
}
}
2025-08-11 14:58:18 +09:00
/// <summary>
/// 일반 아이템의 '찾기/이동' 버튼을 클릭했을 때 호출됩니다.
/// </summary>
2025-08-11 18:30:13 +09:00
public virtual void OnSearchButtonClick()
2025-08-08 18:33:29 +09:00
{
ComponentListItemData itemData = (ComponentListItemData)scrollData;
if (itemData.factoryObjectInfo != null)
{
FactoryObject? obj = FactoryObjectManager.Instance.FindById(itemData.factoryObjectInfo.Id);
if(obj == null)
{
2025-08-11 14:58:18 +09:00
// 객체를 찾지 못한 경우 사용자에게 알림 메시지를 표시합니다.
Toast.Show(LocalizationManager.Instance.GetString($"{itemData.factoryObjectInfo.Name} 객체를 찾을 수 없습니다."), 2f);
return;
}
2025-08-11 14:58:18 +09:00
// 객체가 존재하고 활성화 상태일 때만 카메라를 이동시킵니다.
2025-08-08 18:33:29 +09:00
if (obj != null && obj.gameObject.activeSelf)
{
2025-08-11 14:58:18 +09:00
// FactoryCameraController를 사용하여 해당 객체의 위치로 카메라를 부드럽게 이동시킵니다.
2025-08-12 18:13:19 +09:00
FactoryObjectSelectionManager.Instance.Select(obj);
2025-08-08 18:33:29 +09:00
FactoryCameraController.Instance.FocusOnTarget(obj.transform.position, 10f);
}
}
}
}
}