#nullable enable using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.UI; namespace UVC.UI.List.Draggable { /// /// 드래그 가능한 리스트의 전체 기능을 관리하는 메인 컴포넌트입니다. /// 다른 모든 컴포넌트들을 조합하여 완전한 드래그 앤 드롭 리스트를 만듭니다. /// /// 설정 방법: /// 1. ScrollView GameObject에 이 컴포넌트를 추가합니다. /// - ScrollRectHandler 추가 /// 2. Inspector에서 Item Prefab과 Content를 연결합니다. /// 3. Item Prefab에는 다음 컴포넌트들이 필요합니다: /// - ListItemController /// - ListItemView /// - DragBehavior /// 4.ScrollView > Viewport > Content /// - Vertical Layout Group 추가(Control Child Size: width, Child Force Expand: width) /// - Content Size Fitter 추가(Vertical Fit: Preferred Size) /// - ListReorderHandler 추가 /// /// 사용 예시: /// public class MyListData : IListItemData /// { /// public string Id { get; set; } /// public string DisplayName { get; set; } /// } /// /// void Start() /// { /// var list = GetComponent(); /// /// list.OnOrderChanged += (data, oldIndex, newIndex) => Debug.Log($"아이템 순서 변경: {data.DisplayName} ({oldIndex} -> {newIndex})"); /// list.OnItemClicked += (data) => Debug.Log($"아이템 클릭: {data.DisplayName}"); /// list.OnChangedItemData += (data) => Debug.Log($"아이템 데이터 변경: {data.DisplayName}"); /// /// var dataList = new List /// { /// new MyListData { Id = "1", DisplayName = "첫 번째 아이템" }, /// new MyListData { Id = "2", DisplayName = "두 번째 아이템" } /// }; /// /// list.SetData(dataList); /// } /// public class DraggableList : MonoBehaviour { [Header("필수 설정")] [SerializeField] [Tooltip("리스트 아이템으로 사용할 프리팹. DragBehavior, ListItemController, ItemDataBinder가 있어야 합니다.")] private GameObject? itemPrefab; [SerializeField] [Tooltip("아이템들이 추가될 부모 Transform (보통 ScrollView > Viewport > Content)")] private Transform? content; //[Header("옵션")] //[SerializeField] //[Tooltip("순서 변경을 자동으로 저장할지 여부")] //private bool autoSave = false; // 컴포넌트 참조들 private ListReorderHandler? reorderHandler; private ScrollRectHandler? scrollHandler; // 현재 데이터 리스트 private List dataList = new List(); public IReadOnlyList DataList => dataList; // 아이템 GameObject와 데이터의 매핑 private Dictionary itemDataMap = new Dictionary(); /// /// 아이템 순서가 변경될 때 발생하는 이벤트, oldIndex, newIndex /// public event Action? OnOrderChanged; /// /// 아이템이 클릭될 때 발생하는 이벤트 /// public event Action? OnItemClicked; /// /// 아이템 데이터가 변경 됐을 때 발생하는 이벤트 /// public event Action? OnChangedItemData; /// /// 컴포넌트 초기화 /// void Awake() { ValidateSetup(); SetupComponents(); } /// /// 필수 설정이 올바른지 검증합니다 /// private void ValidateSetup() { if (itemPrefab == null) { Debug.LogError($"[DraggableList] Item Prefab이 설정되지 않았습니다! - {gameObject.name}"); } if (content == null) { Debug.LogError($"[DraggableList] Content Transform이 설정되지 않았습니다! - {gameObject.name}"); } // 프리팹에 필수 컴포넌트가 있는지 확인 if (itemPrefab != null) { if (itemPrefab.GetComponent() == null) if (itemPrefab.GetComponentInChildren() == null) Debug.LogError($"[DraggableList] Item Prefab에 DragBehavior가 없습니다!"); if (itemPrefab.GetComponent() == null) Debug.LogError($"[DraggableList] Item Prefab에 ListItemController가 없습니다!"); if (itemPrefab.GetComponent() == null) Debug.LogError($"[DraggableList] Item Prefab에 ItemDataBinder가 없습니다!"); } } /// /// 필요한 컴포넌트들을 설정합니다 /// private void SetupComponents() { if (content == null) return; // ListReorderHandler 추가 또는 가져오기 reorderHandler = content.GetComponent(); if (reorderHandler == null) { reorderHandler = content.gameObject.AddComponent(); //Debug.Log("[DraggableList] ListReorderHandler를 자동으로 추가했습니다."); } // ScrollRectHandler 추가 또는 가져오기 var scrollRect = GetComponentInChildren(); if (scrollRect != null) { scrollHandler = scrollRect.GetComponent(); if (scrollHandler == null) { scrollHandler = scrollRect.gameObject.AddComponent(); //Debug.Log("[DraggableList] ScrollRectHandler를 자동으로 추가했습니다."); } } // 이벤트 연결 if (reorderHandler != null) { reorderHandler.OnOrderChanged += HandleOrderChanged; } } /// /// 리스트에 표시할 데이터를 설정합니다 /// /// IListItemData를 구현한 데이터 타입 /// 표시할 데이터 리스트 public void SetData(List data) where T : ListItemData { // 기존 데이터와 UI 정리 ClearList(); // 새 데이터 추가 dataList.Clear(); foreach (var item in data) { dataList.Add(item); } // UI 생성 RefreshUI(); Debug.Log($"[DraggableList] 데이터 설정 완료 - {data.Count}개 아이템"); } /// /// 현재 데이터를 기반으로 UI를 다시 생성합니다 /// private void RefreshUI() { if (content == null || itemPrefab == null) return; // 기존 UI 제거 foreach (Transform child in content) { Destroy(child.gameObject); } itemDataMap.Clear(); // 새 UI 생성 foreach (var data in dataList) { CreateItemUI(data); } } /// /// 단일 아이템의 UI를 생성합니다 /// private void CreateItemUI(ListItemData data) { if (itemPrefab == null || content == null) return; // 프리팹으로부터 새 아이템 생성 GameObject item = Instantiate(itemPrefab, content); item.name = $"Item_{data.Id ?? "Unknown"}"; // 데이터 아이템 View 컴포넌트 가져오기 var binder = item.GetComponent(); binder?.BindData(data); if(binder != null) binder.OnChangeData += (data) => OnChangedItemData?.Invoke(data); // 클릭 이벤트 설정 var button = item.GetComponent