Files
XRLib/Assets/Scripts/UVC/UI/List/Tree/TreeListItemDragHandler.cs

805 lines
31 KiB
C#
Raw Normal View History

2025-10-28 20:10:51 +09:00
#nullable enable
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace UVC.UI.List.Tree
{
/// <summary>
/// TreeListItem의 드래그 & 드롭 입력을 처리하는 컴포넌트입니다.
///
/// 역할:
/// 1) 마우스 입력 처리: 드래그 시작/진행/종료
/// 2) 드래그 시각 피드백: 알파 변경, 드롭 위치 표시기 갱신
/// 3) 마우스 위치 기반 드롭 대상 탐색 및 드롭 위치 판정
/// 4) 드래그 매니저(TreeListDragDropManager)와 이벤트 연동
2025-10-28 20:10:51 +09:00
///
/// 이벤트 흐름(IBeginDragHandler/IDragHandler/IEndDragHandler):
/// - OnBeginDrag: 드래그 시작 준비(안전성 검증, 오프셋 계산)
/// - OnDrag: 최초 드래그 프레임에 StartDrag, DropIndicator 생성/갱신
/// - OnEndDrag: 알파/레이캐스트 복구, DropIndicator 숨김, TryDrop 수행
///
/// 시각 피드백:
/// - CanvasGroup.alpha를 일시적으로 낮춰 드래그 중임을 표시
/// - DropIndicator(얇은 선 또는 블록)로 삽입 위치/자식 투입 위치를 표시
2025-10-28 20:10:51 +09:00
/// </summary>
public class TreeListItemDragHandler : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
/// <summary>부모 TreeListItem.</summary>
2025-10-28 20:10:51 +09:00
private TreeListItem? treeListItem;
/// <summary>부모 TreeList.</summary>
2025-10-28 20:10:51 +09:00
private TreeList? treeList;
/// <summary>드래그 & 드롭 매니저.</summary>
2025-10-28 20:10:51 +09:00
private TreeListDragDropManager? dragDropManager;
private RectTransform? rectTransform;
/// <summary>드래그 시각 피드백용 CanvasGroup.</summary>
2025-10-28 20:10:51 +09:00
private CanvasGroup? canvasGroup;
/// <summary>드래그 시작 전 원본 알파값.</summary>
2025-10-28 20:10:51 +09:00
private float originalAlpha = 1f;
private Vector2 dragOffset = Vector2.zero;
/// <summary>드래그 중 적용할 알파값.</summary>
2025-10-28 20:10:51 +09:00
[SerializeField]
private float dragAlpha = 0.5f;
/// <summary>드래그 활성화 여부.</summary>
2025-10-28 20:10:51 +09:00
[SerializeField]
private bool enableDragDrop = true;
/// <summary>드롭 위치 표시 라인/블록.</summary>
2025-10-28 20:10:51 +09:00
private Image? dropIndicator;
private RectTransform? dropIndicatorRect;
/// <summary>드롭 표시기의 부모(RectTransform).</summary>
2025-10-28 20:10:51 +09:00
private RectTransform? dropIndicatorParent;
/// <summary>드래그 시작 시의 원본 부모/순서.</summary>
2025-10-28 20:10:51 +09:00
private Transform? originalParent;
private int originalSiblingIndex;
2025-10-29 20:12:11 +09:00
private RectTransform? treeListRootParent;
2025-10-28 20:10:51 +09:00
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
// CanvasGroup 가져오기 (없으면 생성)
canvasGroup = GetComponent<CanvasGroup>();
if (canvasGroup == null)
{
canvasGroup = gameObject.AddComponent<CanvasGroup>();
}
originalAlpha = canvasGroup.alpha;
}
/// <summary>
/// 외부에서 드래그 매니저 및 상위 컨텍스트를 주입합니다.
/// </summary>
/// <param name="item">이 핸들러가 속한 아이템.</param>
/// <param name="list">아이템 컨테이너(TreeList).</param>
/// <param name="manager">드래그 & 드롭 매니저.</param>
2025-10-28 20:10:51 +09:00
public void SetDragDropManager(TreeListItem item, TreeList list, TreeListDragDropManager manager)
{
treeListItem = item;
treeList = list;
dragDropManager = manager;
2025-10-29 20:12:11 +09:00
treeListRootParent = list.Root.parent as RectTransform;
2025-10-28 20:10:51 +09:00
Debug.Log($"[TreeListItemDragHandler] 드래그 핸들러 설정: {item.Data?.Name ?? "Unknown"}");
}
/// <summary>
/// 드래그가 시작될 때 호출됩니다.
/// OnPointerDown 이후 마우스가 임계치 이상 이동하면 자동 호출됩니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="eventData">포인터 이벤트 데이터.</param>
2025-10-28 20:10:51 +09:00
public void OnBeginDrag(PointerEventData eventData)
{
Debug.Log($"[OnPointerDown]");
if (!enableDragDrop || treeListItem?.Data == null || dragDropManager == null)
{
return;
}
// 마우스 버튼이 왼쪽이 아니면 무시
if (eventData.button != PointerEventData.InputButton.Left)
{
return;
}
// 마우스 위치의 로컬 좌표 저장(필요 시 Y 오프셋 계산에 사용)
2025-10-28 20:10:51 +09:00
RectTransformUtility.ScreenPointToLocalPointInRectangle(
treeListRootParent,
2025-10-28 20:10:51 +09:00
eventData.position,
null,
out var localPoint);
dragOffset = localPoint;
Debug.Log($"[OnPointerDown] {treeListItem.Data.Name}에 포인터 다운, offset: {dragOffset}");
}
/// <summary>
/// 드래그 중 프레임마다 호출됩니다.
/// 최초 1프레임에 드래그 상태 진입 및 표시기 생성, 이후 hover 대상/표시기 갱신을 수행합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="eventData">포인터 이벤트 데이터.</param>
2025-10-28 20:10:51 +09:00
public void OnDrag(PointerEventData eventData)
{
if (!enableDragDrop || dragDropManager == null || treeListItem?.Data == null || treeList == null || rectTransform == null)
{
Debug.LogWarning("[OnDrag] 필수 컴포넌트 누락 또는 비활성화됨");
return;
}
if (eventData.button != PointerEventData.InputButton.Left)
{
return;
}
Debug.Log($"[OnDrag] {treeListItem.Data.Name} 드래그 중");
// 최초 드래그 프레임: 드래그 상태 진입 및 시각 피드백/표시기 준비
2025-10-28 20:10:51 +09:00
if (!dragDropManager.IsDragging)
{
dragDropManager.StartDrag(treeListItem.Data);
// 원본 부모/순서 저장
2025-10-28 20:10:51 +09:00
originalParent = rectTransform.parent;
originalSiblingIndex = rectTransform.GetSiblingIndex();
// 시각 피드백(투명도/레이캐스트)
2025-10-28 20:10:51 +09:00
if (canvasGroup != null)
{
canvasGroup.alpha = dragAlpha;
canvasGroup.blocksRaycasts = false;
}
// 드롭 위치 표시 막대 생성
CreateDropIndicator();
Debug.Log($"[OnDrag] {treeListItem.Data.Name} 드래그 시작");
}
// 필요 시 실제 UI를 마우스를 따라 이동시키려면 아래 호출을 활성화
2025-10-29 20:12:11 +09:00
//UpdateDragPosition(eventData);
2025-10-28 20:10:51 +09:00
// 마우스 위의 드롭 대상 찾기
var targetItem = GetItemAtMousePosition(eventData.position);
dragDropManager.OnDragOver(targetItem?.Data);
// 드롭 위치 표시 갱신(위/아래/자식)
2025-10-28 20:10:51 +09:00
UpdateDropIndicator(targetItem);
}
/// <summary>
/// 드래그가 종료될 때 호출됩니다(마우스 버튼 해제).
/// 상태/시각 피드백 복구 후 TryDrop을 수행합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="eventData">포인터 이벤트 데이터.</param>
2025-10-28 20:10:51 +09:00
public void OnEndDrag(PointerEventData eventData)
{
Debug.Log($"[OnPointerUp]");
if (!enableDragDrop || dragDropManager == null)
{
return;
}
Debug.Log($"[OnPointerUp] 드래그 완료");
// 원본 알파값으로 복구
if (canvasGroup != null)
{
canvasGroup.alpha = originalAlpha;
canvasGroup.blocksRaycasts = true;
}
// 원본 부모/순서 복구(드래그 중 시각 이동을 되돌림)
2025-10-28 20:10:51 +09:00
if (originalParent != null)
{
rectTransform?.SetParent(originalParent);
if (rectTransform != null)
{
rectTransform.SetSiblingIndex(originalSiblingIndex);
}
}
// 드롭 위치 표시 막대 숨김
HideDropIndicator();
// 드래그가 시작되지 않았으면 무시
if (!dragDropManager.IsDragging)
{
dragDropManager.EndDrag();
return;
}
// 마우스 위치의 대상 아이템 찾기
var targetItem = GetItemAtMousePosition(eventData.position);
// 드롭 시도 및 성공 시 데이터 동기화
2025-10-28 20:10:51 +09:00
if (treeListItem?.Data != null)
{
var result = dragDropManager.TryDrop(targetItem?.Data);
Debug.Log($"[OnPointerUp] 드롭 결과: {(result ? "" : "")}");
if (result) HandleDropSuccess(treeListItem.Data, targetItem);
2025-10-28 20:10:51 +09:00
}
dragDropManager.EndDrag();
}
/// <summary>
/// 드래그 중 아이템이 마우스를 따라다니도록 위치를 업데이트합니다.
/// Y축만 이동(X축 고정).
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="eventData">포인터 이벤트 데이터.</param>
2025-10-28 20:10:51 +09:00
private void UpdateDragPosition(PointerEventData eventData)
{
if (rectTransform == null || treeList == null)
{
return;
}
var canvasRect = treeList.GetComponent<RectTransform>();
if (canvasRect == null)
{
return;
}
// 스크린 좌표를 캔버스 로컬 좌표로 변환
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
2025-10-29 20:12:11 +09:00
treeListRootParent, //canvasRect,
2025-10-28 20:10:51 +09:00
eventData.position,
null,
out var canvasLocalPoint))
{
// ✅ Y축만 업데이트 (X는 고정)
// 드래그 오프셋을 고려한 Y 위치만 계산
var currentPosition = rectTransform.anchoredPosition;
rectTransform.anchoredPosition = new Vector2(
currentPosition.x, // ✅ X는 그대로 유지
canvasLocalPoint.y - dragOffset.y // ✅ Y만 마우스를 따라 이동
);
}
}
/// <summary>
/// 드롭 위치 표시기(DropIndicator)를 생성하거나 재사용합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
private void CreateDropIndicator()
{
2025-10-29 20:12:11 +09:00
Debug.Log($"[CreateDropIndicator] dropIndicator != null:{dropIndicator != null}");
2025-10-28 20:10:51 +09:00
if (dropIndicator != null)
{
return;
}
2025-10-29 20:12:11 +09:00
//기존에 생성된 DropIndicator가 있는지 확인
var existingDropIndicator = FindDropIndicatorInRoot();
if (existingDropIndicator != null)
{
// 기존 DropIndicator 사용
dropIndicator = existingDropIndicator.GetComponent<Image>();
dropIndicatorRect = existingDropIndicator.GetComponent<RectTransform>();
dropIndicatorParent = treeListRootParent;
Debug.Log("[CreateDropIndicator] 기존 DropIndicator를 재사용합니다");
return;
}
2025-10-28 20:10:51 +09:00
// ✅ Root(root)를 부모로 설정
// 계층 구조: TreeList > ScrollView > Viewport > Content > Root > TreeListItem
2025-10-29 20:12:11 +09:00
dropIndicatorParent = treeListRootParent;// rectTransform?.parent as RectTransform;
2025-10-28 20:10:51 +09:00
if (dropIndicatorParent == null)
{
Debug.LogError("[CreateDropIndicator] EntryRoot/Content를 찾을 수 없습니다");
return;
}
2025-10-29 20:12:11 +09:00
2025-10-28 20:10:51 +09:00
// 새로운 GameObject 생성
var indicatorGo = new GameObject("DropIndicator");
indicatorGo.transform.SetParent(dropIndicatorParent, false);
// Image 컴포넌트 추가
dropIndicator = indicatorGo.AddComponent<Image>();
dropIndicator.color = new Color(0.2f, 0.8f, 1f, 0.5f); // 반투명 파란색
// RectTransform 설정
dropIndicatorRect = indicatorGo.GetComponent<RectTransform>();
if (dropIndicatorRect != null)
{
2025-10-29 20:12:11 +09:00
dropIndicatorRect.anchorMin = new Vector2(0, 1f);
dropIndicatorRect.anchorMax = new Vector2(1, 1f);
dropIndicatorRect.pivot = new Vector2(0, 1f);
dropIndicatorRect.sizeDelta = new Vector2(0, 2f); // 높이 3
2025-10-28 20:10:51 +09:00
}
dropIndicator.raycastTarget = false;
indicatorGo.SetActive(false);
Debug.Log("[CreateDropIndicator] 드롭 위치 표시 막대 생성됨");
}
/// <summary>
/// 드롭 위치 표시기를 갱신합니다.
/// 복잡도: 대상 RectTransform의 월드 코너를 부모 기준 로컬 좌표로 환산하여,
/// 위/아래/자식(블록) 위치를 정확히 계산합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="targetItem">현재 마우스 아래의 대상 아이템(null이면 숨김).</param>
2025-10-28 20:10:51 +09:00
private void UpdateDropIndicator(TreeListItem? targetItem)
{
if (dropIndicator == null || dropIndicatorRect == null || dropIndicatorParent == null)
{
return;
}
if (targetItem == null)
{
dropIndicator.gameObject.SetActive(false);
return;
}
// 드롭 위치 판단
var targetRect = targetItem.GetComponent<RectTransform>();
if (targetRect == null)
{
dropIndicator.gameObject.SetActive(false);
return;
}
var dropPosition = GetDropPosition(targetRect);
dropIndicator.gameObject.SetActive(true);
// ✅ 핵심: 월드 좌표로 변환해서 부모 기준 로컬 좌표 계산
Vector3[] targetCorners = new Vector3[4];
targetRect.GetWorldCorners(targetCorners);
2025-10-29 20:12:11 +09:00
// 🎯 아이템의 월드 X 좌표 (왼쪽 끝)
float targetWorldX = targetCorners[0].x;
2025-10-28 20:10:51 +09:00
// 아이템의 월드 Y 좌표
float targetWorldY = targetCorners[0].y;
2025-10-29 20:12:11 +09:00
// 부모(Root)의 월드 좌표
2025-10-28 20:10:51 +09:00
Vector3[] parentCorners = new Vector3[4];
dropIndicatorParent.GetWorldCorners(parentCorners);
float parentWorldY = parentCorners[0].y;
2025-10-29 20:12:11 +09:00
float parentWorldX = parentCorners[0].x;
2025-10-28 20:10:51 +09:00
float parentHeight = parentCorners[1].y - parentCorners[0].y;
2025-10-29 20:12:11 +09:00
float parentWidth = parentCorners[3].x - parentCorners[0].x;
// 🎯 월드 X를 부모 기준 로컬 X로 변환
float relativeX = targetWorldX - parentWorldX;
2025-10-28 20:10:51 +09:00
// 월드 Y를 부모 기준 로컬 Y로 변환
float relativeY = targetWorldY - parentWorldY;
2025-10-29 20:12:11 +09:00
// 🎯 부모의 pivot을 고려한 로컬 X 계산
float pivotAdjustedX = relativeX - (parentWidth * dropIndicatorParent.pivot.x);
2025-10-28 20:10:51 +09:00
// 부모의 pivot을 고려한 로컬 Y 계산
2025-10-29 20:12:11 +09:00
float pivotAdjustedY = relativeY - (parentHeight * dropIndicatorParent.pivot.y) + targetRect.rect.height / 2;
2025-10-28 20:10:51 +09:00
2025-10-29 20:12:11 +09:00
float indicatorX = 0;
2025-10-28 20:10:51 +09:00
float indicatorY = 0;
2025-10-29 20:12:11 +09:00
float indicatorHeight = 3f; // 기본 높이
2025-10-28 20:10:51 +09:00
switch (dropPosition)
{
case DropPosition.Above:
// 대상 아이템 위 (아이템 높이의 절반)
2025-10-29 20:12:11 +09:00
indicatorX = pivotAdjustedX;
2025-10-28 20:10:51 +09:00
indicatorY = pivotAdjustedY + (targetRect.rect.height / 2);
2025-10-29 20:12:11 +09:00
indicatorHeight = 2f; // 얇은 선
2025-10-28 20:10:51 +09:00
break;
case DropPosition.Below:
// 대상 아이템 아래
2025-10-29 20:12:11 +09:00
indicatorX = pivotAdjustedX;
2025-10-28 20:10:51 +09:00
indicatorY = pivotAdjustedY - (targetRect.rect.height / 2);
2025-10-29 20:12:11 +09:00
indicatorHeight = 2f; // 얇은 선
2025-10-28 20:10:51 +09:00
break;
case DropPosition.InsideAsChild:
// 대상 아이템 중앙
2025-10-29 20:12:11 +09:00
indicatorX = pivotAdjustedX;
indicatorY = pivotAdjustedY + (targetRect.rect.height / 2); //indicatorHeight 크기 때문에 반영
// 🎯 targetRect의 높이만큼 설정
indicatorHeight = targetRect.rect.height;
2025-10-28 20:10:51 +09:00
break;
}
// ✅ DropIndicator 위치 설정
2025-10-29 20:12:11 +09:00
dropIndicatorRect.anchoredPosition = new Vector2(indicatorX, indicatorY);
// 🎯 DropIndicator 높이 설정
dropIndicatorRect.sizeDelta = new Vector2(dropIndicatorRect.sizeDelta.x, indicatorHeight);
//dropIndicatorRect의 x 시작 위치를 targetRect.rect의 위치와 맞춤
2025-10-28 20:10:51 +09:00
2025-10-29 20:12:11 +09:00
Debug.Log($"[UpdateDropIndicator] {targetItem?.Data?.Name} 위치: {dropPosition}, targetY: {targetWorldY}, parentY: {parentWorldY}, indicatorY: {indicatorY}");
2025-10-28 20:10:51 +09:00
}
/// <summary>
/// 드롭 위치 표시기를 숨깁니다.
2025-10-28 20:10:51 +09:00
/// </summary>
private void HideDropIndicator()
{
if (dropIndicator != null)
{
dropIndicator.gameObject.SetActive(false);
}
}
2025-10-29 20:12:11 +09:00
/// <summary>
/// treeListRootParent의 직접 자식 중 "DropIndicator" GameObject를 찾습니다.
2025-10-29 20:12:11 +09:00
/// </summary>
/// <returns>찾은 GameObject. 없으면 null.</returns>
2025-10-29 20:12:11 +09:00
private GameObject? FindDropIndicatorInRoot()
{
if (treeListRootParent == null)
{
Debug.LogWarning("[FindDropIndicatorInRoot] treeListRootParent가 null입니다");
return null;
}
// 1. treeListRootParent의 모든 직접 자식을 순회
for (int i = 0; i < treeListRootParent.childCount; i++)
{
Transform child = treeListRootParent.GetChild(i);
// 2. 자식의 이름이 "DropIndicator"인지 확인
if (child.name == "DropIndicator")
{
Debug.Log("[FindDropIndicatorInRoot] DropIndicator를 찾았습니다");
return child.gameObject;
}
}
Debug.Log("[FindDropIndicatorInRoot] DropIndicator를 찾지 못했습니다");
return null;
}
2025-10-28 20:10:51 +09:00
/// <summary>
/// 스크린 좌표에 위치한 TreeListItem을 찾습니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="screenPosition">스크린 좌표.</param>
/// <returns>찾은 TreeListItem. 없으면 null.</returns>
2025-10-28 20:10:51 +09:00
private TreeListItem? GetItemAtMousePosition(Vector2 screenPosition)
{
if (treeList == null)
{
return null;
}
// Raycast로 UI 요소 찾기
var results = new System.Collections.Generic.List<RaycastResult>();
var eventData = new PointerEventData(EventSystem.current)
{
position = screenPosition
};
EventSystem.current.RaycastAll(eventData, results);
Debug.Log($"[GetItemAtMousePosition] Raycast 결과: {results.Count}개");
foreach (var result in results)
{
var item = result.gameObject.GetComponentInParent<TreeListItem>();
2025-10-29 20:12:11 +09:00
if (item != null)// && item != treeListItem)
2025-10-28 20:10:51 +09:00
{
Debug.Log($"[GetItemAtMousePosition] 찾은 아이템: {item.Data?.Name ?? "Unknown"}");
return item;
}
}
return null;
}
/// <summary>
/// 드롭 성공 후 데이터 구조를 갱신합니다.
/// 루트 드롭/형제 간 이동/자식으로 이동 등을 처리합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="draggedData">드래그한 데이터.</param>
/// <param name="targetItem">드롭 대상 UI 아이템(null: 루트로 드롭).</param>
2025-10-28 20:10:51 +09:00
private void HandleDropSuccess(TreeListItemData? draggedData, TreeListItem? targetItem)
{
if (draggedData == null || treeList == null)
{
return;
}
// 드롭 대상이 없으면 (빈 공간에 드롭) 루트로 이동
if (targetItem == null)
{
Debug.Log($"[HandleDropSuccess] 루트로 이동");
MoveToRoot(draggedData);
2025-10-30 18:36:26 +09:00
treeList.UpdateFlattenedItemDataList();
2025-10-28 20:10:51 +09:00
return;
}
var targetData = targetItem.Data;
if (targetData == null)
{
return;
}
// 드롭 위치 판단: 대상의 위/아래 또는 자식으로
var dropPosition = GetDropPosition(targetItem.GetComponent<RectTransform>());
2025-10-29 20:12:11 +09:00
Debug.Log($"[HandleDropSuccess] 드롭 위치: {targetItem?.Data?.Name} {dropPosition}");
2025-10-28 20:10:51 +09:00
switch (dropPosition)
{
case DropPosition.InsideAsChild:
// 대상의 자식으로 이동
MoveAsChild(draggedData, targetData);
break;
case DropPosition.Above:
// 대상 위에 위치 (같은 부모 내에서)
MoveBefore(draggedData, targetData);
break;
case DropPosition.Below:
// 대상 아래에 위치 (같은 부모 내에서)
MoveAfter(draggedData, targetData);
break;
}
2025-10-30 18:36:26 +09:00
treeList.UpdateFlattenedItemDataList();
2025-10-28 20:10:51 +09:00
}
/// <summary>
/// 대상 RectTransform 내 마우스 Y 위치로 위/아래/자식 드롭 위치를 판정합니다.
/// 상/하 30% → 위/아래, 중간 40% → 자식.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="targetRect">대상 아이템 RectTransform.</param>
/// <returns>드롭 위치.</returns>
2025-10-28 20:10:51 +09:00
private DropPosition GetDropPosition(RectTransform targetRect)
{
// 목표: 마우스 Y 위치를 기반으로 위/아래/안쪽 판단
// 상위 1/3: 위
// 중간 1/3: 자식
// 하위 1/3: 아래
var height = targetRect.rect.height;
2025-10-30 18:36:26 +09:00
var thresholdUpper = height * 0.3f;
var thresholdLower = height * 0.7f;
2025-10-28 20:10:51 +09:00
// 월드 좌표에서 로컬 좌표로 변환
RectTransformUtility.ScreenPointToLocalPointInRectangle(
targetRect,
Input.mousePosition,
null,
out var localMousePos);
// 로컬 Y 좌표로 판단 (RectTransform의 피벗이 중앙이면 -height/2 ~ height/2)
float relativeY = localMousePos.y;
2025-10-30 18:36:26 +09:00
//Debug.Log($"GetDropPosition height:{height}, relativeY:{relativeY}, thresholdUpper:{thresholdUpper}, thresholdLower:{thresholdLower}");
if (relativeY > -thresholdUpper)
2025-10-28 20:10:51 +09:00
{
return DropPosition.Above;
}
else if (relativeY < -thresholdLower)
{
return DropPosition.Below;
}
else
{
return DropPosition.InsideAsChild;
}
}
/// <summary>
/// 아이템을 루트 레벨로 이동합니다.
/// 이미 루트면 순서만 변경합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="draggedData">드래그된 데이터.</param>
2025-10-28 20:10:51 +09:00
private void MoveToRoot(TreeListItemData draggedData)
{
Debug.Log($"[MoveToRoot] {draggedData.Name}을(를) 루트로 이동");
2025-10-29 20:12:11 +09:00
if (treeList == null)
{
return;
}
var root = treeList.Root;
if (root == null)
{
return;
}
if (draggedData.Parent == null)
2025-10-29 20:12:11 +09:00
{
// 이미 루트 레벨인 경우 순서만 변경
treeList.Items.Remove(draggedData);
treeList.SwapItem(draggedData, treeList.Items.Count);
}
else
{
// 루트 레벨에 아이템이 없거나 유효한 위치를 찾지 못한 경우 끝에 추가
treeList.AddCloneItem(draggedData);
2025-10-29 20:12:11 +09:00
}
Debug.Log($"[MoveToRoot] {draggedData.Name}을(를) 루트 레벨의 끝에 추가");
2025-10-28 20:10:51 +09:00
}
/// <summary>
/// 아이템을 대상의 자식으로 이동합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="draggedData">이동할 데이터.</param>
/// <param name="targetData">대상 데이터.</param>
2025-10-28 20:10:51 +09:00
private void MoveAsChild(TreeListItemData draggedData, TreeListItemData targetData)
{
targetData.AddCloneChild(draggedData);
2025-10-28 20:10:51 +09:00
Debug.Log($"[MoveAsChild] {draggedData.Name}을(를) {targetData.Name}의 자식으로 이동");
}
/// <summary>
/// 아이템을 대상 앞(위)으로 이동합니다.
/// 같은 부모면 재정렬, 다르면 부모 교체 후 삽입합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="draggedData">이동할 데이터.</param>
/// <param name="targetData">기준 데이터.</param>
2025-10-28 20:10:51 +09:00
private void MoveBefore(TreeListItemData draggedData, TreeListItemData targetData)
{
//부모가 같은 경우 순서만 변경
if (draggedData.Parent == targetData.Parent)
2025-10-28 20:10:51 +09:00
{
if (draggedData.Parent != null)
2025-10-28 20:10:51 +09:00
{
var draggedIndex = draggedData.Parent.Children.IndexOf(draggedData);
draggedData.Parent.Children.Remove(draggedData);
var targetIndex = draggedData.Parent.Children.IndexOf(targetData);
if (targetIndex >= 0)
{
draggedData.Parent.SwapChild(draggedData, targetIndex);
Debug.Log($"[MoveBefore] {draggedData.Name}을(를) {targetData.Name} 앞으로 {targetIndex} 순서 변경");
}
}
else
{
var treeListItems = treeList?.Items;
if (treeListItems != null)
{
treeListItems.Remove(draggedData);
var targetIndex = treeListItems.IndexOf(targetData);
if (targetIndex >= 0)
{
treeList?.SwapItem(draggedData, targetIndex);
Debug.Log($"[MoveBefore] {draggedData.Name}을(를) {targetData.Name} 앞으로 {targetIndex} 순서 변경(루트레벨)");
}
}
2025-10-28 20:10:51 +09:00
}
}
2025-10-29 20:12:11 +09:00
else
{
var parentData = targetData.Parent;// FindParentOfItem(targetData);
//RemoveFromParent(draggedData);
if (parentData != null)
2025-10-29 20:12:11 +09:00
{
var targetIndex = parentData.Children.IndexOf(targetData);
2025-10-29 20:12:11 +09:00
if (targetIndex >= 0)
{
parentData.AddCloneAtChild(draggedData, targetIndex); // 자식으로 추가
Debug.Log($"[MoveBefore] {draggedData.Name}을(를) {targetData.Name} 앞으로 {targetIndex} 이동");
2025-10-29 20:12:11 +09:00
}
}
else
{
// 루트 레벨인 경우
var treeListItems = treeList?.Items;
if (treeListItems != null)
{
var targetIndex = treeListItems.IndexOf(targetData);
if (targetIndex >= 0)
{
treeList?.AddCloneItemAt(draggedData, targetIndex);
Debug.Log($"[MoveBefore] {draggedData.Name}을(를) {targetData.Name} 앞으로 {targetIndex} 이동 (루트 레벨)");
}
}
2025-10-29 20:12:11 +09:00
}
2025-10-29 20:12:11 +09:00
}
2025-10-28 20:10:51 +09:00
}
/// <summary>
/// 아이템을 대상 뒤(아래)로 이동합니다.
/// 같은 부모면 재정렬, 다르면 부모 교체 후 삽입합니다.
2025-10-28 20:10:51 +09:00
/// </summary>
/// <param name="draggedData">이동할 데이터.</param>
/// <param name="targetData">기준 데이터.</param>
2025-10-28 20:10:51 +09:00
private void MoveAfter(TreeListItemData draggedData, TreeListItemData targetData)
{
//부모가 같은 경우 순서만 변경
if (draggedData.Parent == targetData.Parent)
2025-10-28 20:10:51 +09:00
{
if (draggedData.Parent != null)
2025-10-28 20:10:51 +09:00
{
var draggedIndex = draggedData.Parent.Children.IndexOf(draggedData);
draggedData.Parent.Children.Remove(draggedData);
var targetIndex = draggedData.Parent.Children.IndexOf(targetData);
if (targetIndex >= 0)
{
draggedData.Parent.SwapChild(draggedData, targetIndex + 1);
Debug.Log($"[MoveBefore] {draggedData.Name}을(를) {targetData.Name} 앞으로 {targetIndex} 순서 변경");
}
2025-10-28 20:10:51 +09:00
}
else
2025-10-29 20:12:11 +09:00
{
var treeListItems = treeList?.Items;
if (treeListItems != null)
2025-10-29 20:12:11 +09:00
{
treeListItems.Remove(draggedData);
var targetIndex = treeListItems.IndexOf(targetData);
if (targetIndex >= 0)
{
treeList?.SwapItem(draggedData, targetIndex + 1);
Debug.Log($"[MoveBefore] {draggedData.Name}을(를) {targetData.Name} 앞으로 {targetIndex} 순서 변경(루트레벨)");
}
2025-10-29 20:12:11 +09:00
}
}
}
else
{
var parentData = targetData.Parent;
2025-10-28 20:10:51 +09:00
if (parentData != null)
{
var targetIndex = parentData.Children.IndexOf(targetData);
if (targetIndex >= 0)
{
parentData.AddCloneAtChild(draggedData, targetIndex + 1);
Debug.Log($"[MoveAfter] {draggedData.Name}을(를) {targetData.Name} 뒤로 {targetIndex} 이동");
}
}
else
2025-10-28 20:10:51 +09:00
{
// 루트 레벨인 경우
var treeListItems = treeList?.Items;
if (treeListItems != null)
{
var targetIndex = treeListItems.IndexOf(targetData);
if (targetIndex >= 0)
{
treeList?.AddCloneItemAt(draggedData, targetIndex + 1);
Debug.Log($"[MoveAfter] {draggedData.Name}을(를) {targetData.Name} 뒤로 {targetIndex} 이동 (루트 레벨)");
}
}
2025-10-28 20:10:51 +09:00
}
}
}
2025-10-29 20:12:11 +09:00
2025-10-28 20:10:51 +09:00
/// <summary>
/// 드롭 위치를 나타냅니다.
2025-10-28 20:10:51 +09:00
/// </summary>
private enum DropPosition
{
/// <summary>위 (형제 아이템으로 앞쪽)</summary>
Above,
/// <summary>안쪽 (자식으로)</summary>
InsideAsChild,
/// <summary>아래 (형제 아이템으로 뒤쪽)</summary>
Below
}
}
}