Files
XRLib/Assets/Scripts/UVC/UI/List/Tree/TreeListDragDropManager.cs
2025-10-30 18:36:26 +09:00

168 lines
5.3 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
namespace UVC.UI.List.Tree
{
/// <summary>
/// 트리 리스트의 드래그 & 드롭 기능을 관리하는 클래스입니다.
///
/// 역할:
/// 1. 드래그 시작/진행/종료 상태 관리
/// 2. 유효한 드롭 대상 판단 (순환 참조 방지)
/// 3. 아이템 위치 변경 (형제 아이템 간 순서 변경)
/// 4. 아이템 계층 구조 변경 (부모-자식 관계 수정)
///
/// 기능:
/// - 드래그할 아이템과 드롭 대상을 추적
/// - 유효성 검사 (자기 자신에게 드롭 금지, 순환 참조 방지)
/// - 드롭 완료 후 데이터 동기화
/// </summary>
public class TreeListDragDropManager
{
/// <summary>
/// 드래그 중인 아이템의 데이터입니다.
/// </summary>
public TreeListItemData? DraggedItem { get; private set; }
/// <summary>
/// 현재 드래그 중인 상태인지 여부입니다.
/// </summary>
public bool IsDragging { get; private set; }
/// <summary>
/// 드래그 시작 시 발생하는 이벤트입니다.
/// </summary>
public Action<TreeListItemData>? OnDragStarted;
/// <summary>
/// 드래그 진행 중 발생하는 이벤트입니다.
/// </summary>
public Action<TreeListItemData, TreeListItemData?>? OnDragEntered;
/// <summary>
/// 드래그 종료 시 발생하는 이벤트입니다.
/// </summary>
public Action<TreeListItemData>? OnDragEnded;
/// <summary>
/// 드롭 완료 시 발생하는 이벤트입니다.
/// </summary>
public Action<TreeListItemData, TreeListItemData?>? OnDropped;
/// <summary>
/// 드래그를 시작합니다.
/// </summary>
/// <param name="draggedItem">드래그할 아이템</param>
public void StartDrag(TreeListItemData draggedItem)
{
if (IsDragging)
{
return;
}
DraggedItem = draggedItem;
IsDragging = true;
OnDragStarted?.Invoke(draggedItem);
}
/// <summary>
/// 드래그 중에 마우스가 다른 아이템 위에 있을 때 호출됩니다.
/// </summary>
/// <param name="targetItem">현재 마우스 위에 있는 아이템</param>
public void OnDragOver(TreeListItemData? targetItem)
{
if (!IsDragging || DraggedItem == null)
{
return;
}
OnDragEntered?.Invoke(DraggedItem, targetItem);
}
/// <summary>
/// 드래그를 종료합니다.
/// </summary>
public void EndDrag()
{
if (!IsDragging || DraggedItem == null)
{
return;
}
OnDragEnded?.Invoke(DraggedItem);
IsDragging = false;
DraggedItem = null;
}
/// <summary>
/// 드래그된 아이템을 대상 아이템에 드롭합니다.
/// </summary>
/// <param name="targetItem">드롭 대상 아이템 (null이면 루트 레벨)</param>
/// <param name="insertIndex">대상 부모 내에서의 삽입 위치 (-1이면 끝에 추가)</param>
/// <returns>드롭 성공 여부</returns>
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;
}
/// <summary>
/// 첫 번째 아이템이 두 번째 아이템의 조상인지 확인합니다.
/// 순환 참조를 방지하기 위해 사용됩니다.
/// </summary>
/// <param name="potentialAncestor">조상일 가능성이 있는 아이템</param>
/// <param name="potentialDescendant">후손일 가능성이 있는 아이템</param>
/// <returns>조상-후손 관계이면 true</returns>
public static bool IsAncestorOf(TreeListItemData potentialAncestor, TreeListItemData potentialDescendant)
{
var current = potentialDescendant.Parent;
while (current != null)
{
if (current == potentialAncestor)
{
return true;
}
current = current.Parent;
}
return false;
}
/// <summary>
/// 모든 드래그 & 드롭 상태를 리셋합니다.
/// </summary>
public void Reset()
{
IsDragging = false;
DraggedItem = null;
}
}
}