#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;
}
}
}