#nullable enable using System; using System.Collections.Generic; using UnityEngine; namespace UVC.UI.List.Tree { /// /// 트리 리스트의 드래그 & 드롭 기능을 관리하는 클래스입니다. /// /// 역할: /// 1. 드래그 시작/진행/종료 상태 관리 /// 2. 유효한 드롭 대상 판단 (순환 참조 방지) /// 3. 아이템 위치 변경 (형제 아이템 간 순서 변경) /// 4. 아이템 계층 구조 변경 (부모-자식 관계 수정) /// /// 기능: /// - 드래그할 아이템과 드롭 대상을 추적 /// - 유효성 검사 (자기 자신에게 드롭 금지, 순환 참조 방지) /// - 드롭 완료 후 데이터 동기화 /// public class TreeListDragDropManager { /// /// 드래그 중인 아이템의 데이터입니다. /// public TreeListItemData? DraggedItem { get; private set; } /// /// 현재 드래그 중인 상태인지 여부입니다. /// public bool IsDragging { get; private set; } /// /// 드래그 시작 시 발생하는 이벤트입니다. /// public event Action? OnDragStarted; /// /// 드래그 진행 중 발생하는 이벤트입니다. /// public event Action? OnDragEntered; /// /// 드래그 종료 시 발생하는 이벤트입니다. /// public event Action? OnDragEnded; /// /// 드롭 완료 시 발생하는 이벤트입니다. /// public event Action? OnDropped; /// /// 드래그를 시작합니다. /// /// 드래그할 아이템 public void StartDrag(TreeListItemData draggedItem) { if (IsDragging) { return; } DraggedItem = draggedItem; IsDragging = true; OnDragStarted?.Invoke(draggedItem); } /// /// 드래그 중에 마우스가 다른 아이템 위에 있을 때 호출됩니다. /// /// 현재 마우스 위에 있는 아이템 public void OnDragOver(TreeListItemData? targetItem) { if (!IsDragging || DraggedItem == null) { return; } OnDragEntered?.Invoke(DraggedItem, targetItem); } /// /// 드래그를 종료합니다. /// public void EndDrag() { if (!IsDragging || DraggedItem == null) { return; } OnDragEnded?.Invoke(DraggedItem); IsDragging = false; DraggedItem = null; } /// /// 드래그된 아이템을 대상 아이템에 드롭합니다. /// /// 드롭 대상 아이템 (null이면 루트 레벨) /// 대상 부모 내에서의 삽입 위치 (-1이면 끝에 추가) /// 드롭 성공 여부 public bool TryDrop(TreeListItemData? targetItem, int insertIndex = -1) { if (!IsDragging || DraggedItem == null) { return false; } // 자기 자신에게 드롭하는 경우 무시 if (targetItem != null && targetItem == DraggedItem) { EndDrag(); return false; } // 순환 참조 검사 (드래그 아이템이 드롭 대상의 부모인 경우) if (targetItem != null && IsAncestorOf(DraggedItem, targetItem)) { EndDrag(); return false; } OnDropped?.Invoke(DraggedItem, targetItem); EndDrag(); return true; } /// /// 첫 번째 아이템이 두 번째 아이템의 조상인지 확인합니다. /// 순환 참조를 방지하기 위해 사용됩니다. /// /// 조상일 가능성이 있는 아이템 /// 후손일 가능성이 있는 아이템 /// 조상-후손 관계이면 true public static bool IsAncestorOf(TreeListItemData potentialAncestor, TreeListItemData potentialDescendant) { var current = potentialDescendant; while (current != null) { // 재귀적으로 부모를 찾는 로직 // TreeListItemData는 부모 참조가 없으므로, 전체 리스트에서 찾아야 함 // 여기서는 간단한 버전으로, 자식 리스트를 통해 확인 var parent = FindParent(current, potentialAncestor); if (parent == null) { break; } if (parent == potentialAncestor) { return true; } current = parent; } return false; } /// /// 주어진 아이템의 부모를 찾습니다. /// /// 자식 아이템 /// 검색 제한 (이 아이템이 나타나면 멈춤) /// 부모 아이템 (없으면 null) private static TreeListItemData? FindParent(TreeListItemData child, TreeListItemData searchLimit) { // 이 구현은 전체 트리를 순회하므로 성능이 낮음 // 실제 구현에서는 TreeListItemData에 부모 참조를 추가하는 것이 나음 // 여기서는 시연 목적으로 간단하게 구현 return null; } /// /// 모든 드래그 & 드롭 상태를 리셋합니다. /// public void Reset() { IsDragging = false; DraggedItem = null; } } }