#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