#nullable enable using DG.Tweening; using Gpm.Ui; using UnityEngine; using UnityEngine.UI; namespace UVC.UI.List.ComponentList { /// /// InfiniteScroll에 표시될 개별 아이템을 정의하는 클래스입니다. /// 이 아이템은 '카테고리' 또는 '일반 항목' 두 가지 상태를 가질 수 있으며, /// ComponentListItemData의 isCategory 값에 따라 UI가 동적으로 변경됩니다. /// public class ComponentListItem : InfiniteScrollItem { // ComponentList 참조 private ComponentList? _componentList; // [카테고리 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; private void Awake() { // ComponentList 참조 캐싱 if (_componentList == null) { _componentList = GetComponentInParent(); } } /// /// InfiniteScroll에 의해 데이터가 이 아이템에 할당될 때 호출되는 핵심 메서드입니다. /// 스크롤 시 아이템이 재활용될 때마다 새로운 데이터로 이 메서드가 호출되어 UI를 갱신합니다. /// /// 이 아이템에 표시할 데이터입니다. `InfiniteScrollData`를 상속받은 `ComponentListItemData` 객체입니다. /// /// /// // 1. 표시할 데이터를 생성합니다. /// var categoryData = new ComponentListItemData /// { /// isCategory = true, /// categoryName = "전기 설비", /// categoryBadgeCount = 10 /// }; /// /// var generalData = new ComponentListItemData /// { /// isCategory = false, /// generalName = "분전반", /// generalOption = "옵션 A", /// Id = "some-unique-id" /// }; /// /// // 2. 생성한 데이터를 InfiniteScroll에 추가합니다. /// infiniteScroll.InsertData(categoryData); /// infiniteScroll.InsertData(generalData); /// /// public override void UpdateData(InfiniteScrollData scrollData) { // 1. 부모 클래스의 UpdateData를 호출하여 기본 초기화 작업을 수행합니다. // 이 과정에서 this.scrollData에 매개변수로 받은 scrollData가 할당됩니다. base.UpdateData(scrollData); // InfiniteScroll 아이템 재활용 시 애니메이션 상태 리셋 isAnimating = false; // 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(); // 애니메이션을 위해 현재 각도에서 목표 각도로 회전시킵니다. categoryExtendButton.transform.DOKill(); // 이전 애니메이션이 있으면 종료 categoryExtendButton.transform.DORotate(new Vector3(0, 0, itemData.IsExpanded ? 0 : 90), 0.0f); } else { // 일반 아이템일 경우 generalText.text = itemData.Name; generalOptionText.text = itemData.Option; // 버튼 상태는 외부 이벤트 핸들러에서 처리 // 기본값: 숨기기 버튼 표시 (객체가 보이는 상태로 가정) showButton.gameObject.SetActive(false); hideButton.gameObject.SetActive(true); } } public virtual void OnClickItem() { ComponentListItemData itemData = (ComponentListItemData)scrollData; if (_componentList != null) { _componentList.HandleClickItem(itemData); } } public virtual void OnRightClickItem() { ComponentListItemData itemData = (ComponentListItemData)scrollData; if (_componentList != null) { _componentList.HandleRightClickItem(itemData); } } /// /// 카테고리 아이템의 '확장/축소' 버튼을 클릭했을 때 호출됩니다. /// public virtual void OnExpendButtonClick() { // 애니메이션이 진행 중이면 중복 호출을 방지합니다. if (isAnimating) return; isAnimating = true; ComponentListItemData itemData = (ComponentListItemData)scrollData; // 토글 후의 상태를 기준으로 목표 각도 계산 // 현재 isExpanded=true(펼쳐진 상태)이면 접을 것이므로 90도로 (화살표 우측 방향) // 현재 isExpanded=false(접힌 상태)이면 펼칠 것이므로 0도로 (화살표 아래 방향) float targetAngle = itemData.IsExpanded ? 90 : 0; // 이전 애니메이션이 있으면 종료 categoryExtendButton.transform.DOKill(); categoryExtendButton.transform.DORotate(new Vector3(0, 0, targetAngle), 0.3f) .SetUpdate(true) // TimeScale 영향 받지 않도록 설정 .OnComplete(() => { isAnimating = false; }); // ComponentList의 이벤트를 통해 확장/축소 처리 if (_componentList != null) { _componentList.HandleCategoryExpand(itemData); } } /// /// 카테고리 아이템의 '설정' 버튼을 클릭했을 때 호출됩니다. /// public virtual void OnSettingButtonClick() { ComponentListItemData itemData = (ComponentListItemData)scrollData; if (_componentList != null) { _componentList.HandleSettingButtonClick(itemData); } } /// /// 일반 아이템의 '보이기' 버튼을 클릭했을 때 호출됩니다. /// public virtual void OnShowButtonClick() { showButton.gameObject.SetActive(false); hideButton.gameObject.SetActive(true); ComponentListItemData itemData = (ComponentListItemData)scrollData; if (_componentList != null) { _componentList.HandleShowButtonClick(itemData); } } /// /// 일반 아이템의 '숨기기' 버튼을 클릭했을 때 호출됩니다. /// public virtual void OnHideButtonClick() { showButton.gameObject.SetActive(true); hideButton.gameObject.SetActive(false); ComponentListItemData itemData = (ComponentListItemData)scrollData; if (_componentList != null) { _componentList.HandleHideButtonClick(itemData); } } /// /// 일반 아이템의 '찾기/이동' 버튼을 클릭했을 때 호출됩니다. /// public virtual void OnSearchButtonClick() { ComponentListItemData itemData = (ComponentListItemData)scrollData; if (_componentList != null) { _componentList.HandleSearchButtonClick(itemData); } } protected virtual void OnDestroy() { // 애니메이션 정리 if (categoryExtendButton != null) { categoryExtendButton.transform.DOKill(); } // 참조 정리 _componentList = null; // UI 참조 정리 categoryRoot = null!; categoryText = null!; categoryBageText = null!; categoryExtendButton = null!; generalRoot = null!; generalText = null!; generalOptionText = null!; showButton = null!; hideButton = null!; } } }