namespace Gpm.Ui { using System; using UnityEngine; using DataContext = InfiniteScroll.DataContext; /// /// InfiniteScroll에 표시될 데이터의 기반 클래스입니다. /// 이 클래스를 상속하여 스크롤 아이템에 필요한 데이터를 정의할 수 있습니다. /// /// /// /// // 사용자 정의 데이터 클래스 예시 /// public class MyCustomData : InfiniteScrollData /// { /// public string title; /// public string description; /// public Sprite icon; /// } /// /// public class InfiniteScrollData {} /// /// InfiniteScroll의 각 아이템을 나타내는 기본 클래스입니다. /// 이 클래스를 상속하여 아이템의 UI와 동작을 구현합니다. /// public class InfiniteScrollItem : MonoBehaviour { /// /// 아이템의 크기가 변경될 때 자동으로 스크롤에 적용할지 여부를 결정합니다. /// 주로 UI 요소(예: Text)의 내용에 따라 크기가 동적으로 변할 때 유용합니다. /// public bool autoApplySize = false; protected RectTransform cachedRectTransform = null; protected bool activeItem; protected InfiniteScroll scroll = null; /// /// 이 아이템의 RectTransform 컴포넌트를 가져옵니다. /// 성능을 위해 처음 접근할 때 캐시됩니다. /// public RectTransform rectTransform { get { if (System.Object.ReferenceEquals(cachedRectTransform, null) == true) { cachedRectTransform = transform as RectTransform; } return cachedRectTransform; } } /// /// 이 아이템에 바인딩된 데이터를 가져옵니다. /// 데이터는 InfiniteScrollData를 상속한 사용자 정의 클래스의 인스턴스입니다. /// protected InfiniteScrollData scrollData { get { if (dataContext != null) { return dataContext.data; } else { return null; } } } protected DataContext dataContext = null; protected Action selectCallback = null; protected Action updateSizeCallback = null; protected int itemObjectIndex = -1; internal bool needUpdateItemSize = true; /// /// InfiniteScroll에 의해 아이템이 생성될 때 호출되는 초기화 메서드입니다. /// /// 부모 InfiniteScroll 인스턴스 /// 아이템 객체 풀 내에서의 인덱스 public void Initalize(InfiniteScroll scroll, int itemObjectIndex) { this.scroll = scroll; this.itemObjectIndex = itemObjectIndex; this.needUpdateItemSize = true; } /// /// 현재 화면에 보이는 아이템들 중 이 아이템의 인덱스를 반환합니다. /// /// 화면 내 아이템 인덱스. 데이터가 없으면 -1을 반환합니다. public int GetItemIndex() { if (dataContext != null) { return dataContext.itemIndex; } else { return -1; } } /// /// 전체 데이터 목록에서 이 아이템이 사용하는 데이터의 인덱스를 반환합니다. /// /// 전체 데이터 인덱스. 데이터가 없으면 -1을 반환합니다. public int GetDataIndex() { if (dataContext != null) { return dataContext.index; } else { return -1; } } /// /// 아이템이 현재 활성화 상태인지 확인합니다. /// /// 활성화 상태이면 true, 아니면 false를 반환합니다. public bool IsActive() { return activeItem; } /// /// 아이템이 선택되었을 때 호출될 콜백을 추가합니다. /// /// 실행할 콜백 함수 /// /// /// void OnEnable() /// { /// var item = GetComponent<InfiniteScrollItem>(); /// item.AddSelectCallback(OnItemSelected); /// } /// /// void OnItemSelected(InfiniteScrollData data) /// { /// MyCustomData myData = data as MyCustomData; /// if (myData != null) /// { /// Debug.Log("Selected: " + myData.title); /// } /// } /// /// public void AddSelectCallback(Action callback) { selectCallback += callback; } /// /// 등록된 선택 콜백을 제거합니다. /// /// 제거할 콜백 함수 public void RemoveSelectCallback(Action callback) { selectCallback -= callback; } /// /// 새로운 데이터로 아이템의 내용을 업데이트합니다. /// 이 메서드를 상속하여 아이템의 UI(텍스트, 이미지 등)를 데이터에 맞게 설정해야 합니다. /// /// 표시할 데이터 /// /// /// // 사용자 정의 아이템 클래스에서 UpdateData를 재정의하는 예시 /// public class MyScrollItem : InfiniteScrollItem /// { /// public Text titleText; /// public Image iconImage; /// /// public override void UpdateData(InfiniteScrollData scrollData) /// { /// base.UpdateData(scrollData); /// /// MyCustomData myData = scrollData as MyCustomData; /// if (myData != null) /// { /// titleText.text = myData.title; /// iconImage.sprite = myData.icon; /// } /// } /// } /// /// public virtual void UpdateData(InfiniteScrollData scrollData) { } /// /// 스크롤 방향에 따라 아이템의 RectTransform 앵커와 피벗을 설정합니다. (내부용) /// internal void SetAxis(Vector2 anchorMin, Vector2 anchorMax, Vector2 pivot) { bool autoApplySize = this.autoApplySize; this.autoApplySize = false; rectTransform.anchorMin = anchorMin; rectTransform.anchorMax = anchorMax; rectTransform.pivot = pivot; needUpdateItemSize = true; this.autoApplySize = autoApplySize; } /// /// 아이템이 선택되었을 때 호출됩니다. /// 주로 버튼의 OnClick 이벤트에 연결하여 사용합니다. /// protected void OnSelect() { if (selectCallback != null) { selectCallback(scrollData); } } /// /// 아이템의 활성 상태를 설정합니다. /// GameObject를 활성화/비활성화하고, 필요한 경우 부모 스크롤에 상태 변경을 알립니다. /// /// 활성화 여부 /// 부모 InfiniteScroll에 상태 변경 이벤트를 보낼지 여부 public virtual void SetActive(bool active, bool notifyEvent = true) { activeItem = active; gameObject.SetActive(activeItem); if (notifyEvent == true) { if (scroll != null) { scroll.OnChangeActiveItem(GetDataIndex(), activeItem); } } } /// /// 동적 아이템 크기 모드에서 아이템의 크기를 설정합니다. /// InfiniteScroll의 dynamicItemSize가 true일 때만 작동합니다. /// /// 새로운 아이템 크기 (주 축 기준) /// 크기 변경을 즉시 스크롤에 알릴지 여부 public void SetSize(float itemSize, bool notity = true) { if (scrollData == null) { return; } if (scroll != null && scroll.dynamicItemSize == false) { return; } if (dataContext != null && dataContext.GetItemSize() != itemSize) { dataContext.SetItemSize(itemSize); if (notity == true) { OnUpdateItemSize(); } this.needUpdateItemSize = true; } } /// /// 동적 아이템 크기 모드에서 아이템의 크기를 설정합니다. /// InfiniteScroll의 dynamicItemSize가 true일 때만 작동합니다. /// /// 새로운 아이템의 sizeDelta /// 크기 변경을 즉시 스크롤에 알릴지 여부 public void SetSize(Vector2 sizeDelta, bool notity = true) { if (scrollData == null) { return; } if (scroll != null && scroll.dynamicItemSize == false) { return; } float itemSize = scroll.layout.GetMainSize(sizeDelta); SetSize(itemSize, notity); } /// /// 아이템의 데이터를 초기화하고 비활성화합니다. (내부용) /// internal void ClearData(bool notifyEvent = true) { SetActive(false, activeItem && notifyEvent); if (dataContext != null) { dataContext.itemObject = null; dataContext = null; } } /// /// 아이템의 모든 데이터와 콜백을 초기화합니다. (내부용) /// internal void Clear() { ClearData(false); selectCallback = null; updateSizeCallback = null; } /// /// 새로운 데이터 컨텍스트로 아이템을 업데이트합니다. (내부용) /// internal void UpdateItem(DataContext context) { this.dataContext = context; this.dataContext.itemObject = this; UpdateData(this.dataContext.data); this.dataContext.needUpdateItemData = false; this.needUpdateItemSize = true; } /// /// 아이템의 크기가 업데이트될 때 호출될 콜백을 추가합니다. /// `dynamicItemSize`가 활성화된 경우에 유용합니다. /// /// 실행할 콜백 함수. `InfiniteScrollData`와 `RectTransform`을 인자로 받습니다. public void AddUpdateSizeCallback(Action callback) { updateSizeCallback += callback; } /// /// 등록된 크기 업데이트 콜백을 제거합니다. /// /// 제거할 콜백 함수. public void RemoveUpdateSizeCallback(Action callback) { updateSizeCallback -= callback; } /// /// 아이템의 크기가 변경되었을 때 호출됩니다. /// 부모 `InfiniteScroll`에 크기 변경을 알려 레이아웃을 다시 계산하도록 하고, 등록된 콜백을 실행합니다. /// protected void OnUpdateItemSize() { if (scroll != null && scroll.dynamicItemSize == false) { return; } if (scroll != null) { scroll.OnUpdateItemSize(dataContext); } if (updateSizeCallback != null) { updateSizeCallback(scrollData, rectTransform); } } /// /// `autoApplySize` 기능이 현재 실행될 수 있는 상태인지 확인합니다. /// 아이템이 활성화되어 있고, 스크롤이 처리 중이 아닐 때 true를 반환합니다. /// protected bool CanAutoSizeCheck() { if (autoApplySize == false || activeItem == false) { return false; } if(scroll == null) { return false; } if( scroll.processing == true || scroll.anchorUpdate == true) { return false; } if (rectTransform == null) { return false; } return true; } /// /// Unity의 UI 시스템에 의해 RectTransform의 크기가 변경될 때 호출됩니다. /// autoApplySize가 true일 때, 이 메서드는 변경된 크기를 InfiniteScroll에 자동으로 전달하여 /// 스크롤 레이아웃을 다시 계산하도록 합니다. /// protected void OnRectTransformDimensionsChange() { if (CanAutoSizeCheck() == true) { SetSize(rectTransform.sizeDelta); } } } }