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