드래그앤 드랍 개발 중

This commit is contained in:
logonkhi
2025-10-29 20:12:11 +09:00
parent 47ca525718
commit e245ee9f96
8 changed files with 417 additions and 1266 deletions

View File

@@ -80,6 +80,7 @@ namespace UVC.UI.List.Tree
private Transform? originalParent;
private int originalSiblingIndex;
private RectTransform? treeListRootParent;
private void Awake()
{
@@ -99,6 +100,7 @@ namespace UVC.UI.List.Tree
treeListItem = item;
treeList = list;
dragDropManager = manager;
treeListRootParent = list.Root.parent as RectTransform;
Debug.Log($"[TreeListItemDragHandler] 드래그 핸들러 설정: {item.Data?.Name ?? "Unknown"}");
}
@@ -122,7 +124,7 @@ namespace UVC.UI.List.Tree
// 드래그 시작 준비: 마우스 위치와 아이템 위치의 오프셋 계산
RectTransformUtility.ScreenPointToLocalPointInRectangle(
rectTransform,
treeListRootParent,//rectTransform,
eventData.position,
null,
out var localPoint);
@@ -173,7 +175,7 @@ namespace UVC.UI.List.Tree
}
// 아이템이 마우스를 따라다니도록 위치 업데이트
UpdateDragPosition(eventData);
//UpdateDragPosition(eventData);
// 마우스 위의 드롭 대상 찾기
var targetItem = GetItemAtMousePosition(eventData.position);
@@ -268,7 +270,7 @@ namespace UVC.UI.List.Tree
// 스크린 좌표를 캔버스 로컬 좌표로 변환
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
canvasRect,
treeListRootParent, //canvasRect,
eventData.position,
null,
out var canvasLocalPoint))
@@ -288,14 +290,28 @@ namespace UVC.UI.List.Tree
/// </summary>
private void CreateDropIndicator()
{
Debug.Log($"[CreateDropIndicator] dropIndicator != null:{dropIndicator != null}");
if (dropIndicator != null)
{
return;
}
//기존에 생성된 DropIndicator가 있는지 확인
var existingDropIndicator = FindDropIndicatorInRoot();
if (existingDropIndicator != null)
{
// 기존 DropIndicator 사용
dropIndicator = existingDropIndicator.GetComponent<Image>();
dropIndicatorRect = existingDropIndicator.GetComponent<RectTransform>();
dropIndicatorParent = treeListRootParent;
Debug.Log("[CreateDropIndicator] 기존 DropIndicator를 재사용합니다");
return;
}
// ✅ Root(root)를 부모로 설정
// 계층 구조: TreeList > ScrollView > Viewport > Content > Root > TreeListItem
dropIndicatorParent = treeList!.Root.parent as RectTransform;// rectTransform?.parent as RectTransform;
dropIndicatorParent = treeListRootParent;// rectTransform?.parent as RectTransform;
if (dropIndicatorParent == null)
{
@@ -303,6 +319,7 @@ namespace UVC.UI.List.Tree
return;
}
// 새로운 GameObject 생성
var indicatorGo = new GameObject("DropIndicator");
indicatorGo.transform.SetParent(dropIndicatorParent, false);
@@ -315,10 +332,10 @@ namespace UVC.UI.List.Tree
dropIndicatorRect = indicatorGo.GetComponent<RectTransform>();
if (dropIndicatorRect != null)
{
dropIndicatorRect.anchorMin = new Vector2(0, 0.5f);
dropIndicatorRect.anchorMax = new Vector2(1, 0.5f);
dropIndicatorRect.pivot = new Vector2(0, 0.5f);
dropIndicatorRect.sizeDelta = new Vector2(0, 3f); // 높이 3
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
}
dropIndicator.raycastTarget = false;
@@ -360,45 +377,68 @@ namespace UVC.UI.List.Tree
Vector3[] targetCorners = new Vector3[4];
targetRect.GetWorldCorners(targetCorners);
// 🎯 아이템의 월드 X 좌표 (왼쪽 끝)
float targetWorldX = targetCorners[0].x;
// 아이템의 월드 Y 좌표
float targetWorldY = targetCorners[0].y;
// 부모(EntryRoot)의 월드 좌표
// 부모(Root)의 월드 좌표
Vector3[] parentCorners = new Vector3[4];
dropIndicatorParent.GetWorldCorners(parentCorners);
float parentWorldY = parentCorners[0].y;
float parentWorldX = parentCorners[0].x;
float parentHeight = parentCorners[1].y - parentCorners[0].y;
float parentWidth = parentCorners[3].x - parentCorners[0].x;
// 🎯 월드 X를 부모 기준 로컬 X로 변환
float relativeX = targetWorldX - parentWorldX;
// 월드 Y를 부모 기준 로컬 Y로 변환
float relativeY = targetWorldY - parentWorldY;
// 부모의 pivot을 고려한 로컬 Y 계산
float pivotAdjustedY = relativeY - (parentHeight * dropIndicatorParent.pivot.y);
// 🎯 부모의 pivot을 고려한 로컬 X 계산
float pivotAdjustedX = relativeX - (parentWidth * dropIndicatorParent.pivot.x);
// 부모의 pivot을 고려한 로컬 Y 계산
float pivotAdjustedY = relativeY - (parentHeight * dropIndicatorParent.pivot.y) + targetRect.rect.height / 2;
float indicatorX = 0;
float indicatorY = 0;
float indicatorHeight = 3f; // 기본 높이
switch (dropPosition)
{
case DropPosition.Above:
// 대상 아이템 위 (아이템 높이의 절반)
indicatorX = pivotAdjustedX;
indicatorY = pivotAdjustedY + (targetRect.rect.height / 2);
indicatorHeight = 2f; // 얇은 선
break;
case DropPosition.Below:
// 대상 아이템 아래
indicatorX = pivotAdjustedX;
indicatorY = pivotAdjustedY - (targetRect.rect.height / 2);
indicatorHeight = 2f; // 얇은 선
break;
case DropPosition.InsideAsChild:
// 대상 아이템 중앙
indicatorY = pivotAdjustedY;
indicatorX = pivotAdjustedX;
indicatorY = pivotAdjustedY + (targetRect.rect.height / 2); //indicatorHeight 크기 때문에 반영
// 🎯 targetRect의 높이만큼 설정
indicatorHeight = targetRect.rect.height;
break;
}
// ✅ DropIndicator 위치 설정
dropIndicatorRect.anchoredPosition = new Vector2(0, indicatorY);
dropIndicatorRect.anchoredPosition = new Vector2(indicatorX, indicatorY);
// 🎯 DropIndicator 높이 설정
dropIndicatorRect.sizeDelta = new Vector2(dropIndicatorRect.sizeDelta.x, indicatorHeight);
//dropIndicatorRect의 x 시작 위치를 targetRect.rect의 위치와 맞춤
Debug.Log($"[UpdateDropIndicator] 위치: {dropPosition}, targetY: {targetWorldY}, parentY: {parentWorldY}, indicatorY: {indicatorY}");
Debug.Log($"[UpdateDropIndicator] {targetItem?.Data?.Name} 위치: {dropPosition}, targetY: {targetWorldY}, parentY: {parentWorldY}, indicatorY: {indicatorY}");
}
/// <summary>
@@ -412,6 +452,35 @@ namespace UVC.UI.List.Tree
}
}
/// <summary>
/// treeListRootParent의 자식 중에서 "DropIndicator"라는 이름의 GameObject를 찾습니다.
/// </summary>
/// <returns>찾은 GameObject (없으면 null)</returns>
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;
}
/// <summary>
/// 주어진 스크린 좌표에 있는 TreeListItem을 찾습니다.
/// </summary>
@@ -438,7 +507,7 @@ namespace UVC.UI.List.Tree
foreach (var result in results)
{
var item = result.gameObject.GetComponentInParent<TreeListItem>();
if (item != null && item != treeListItem)
if (item != null)// && item != treeListItem)
{
Debug.Log($"[GetItemAtMousePosition] 찾은 아이템: {item.Data?.Name ?? "Unknown"}");
return item;
@@ -479,7 +548,7 @@ namespace UVC.UI.List.Tree
// 드롭 위치 판단: 대상의 위/아래 또는 자식으로
var dropPosition = GetDropPosition(targetItem.GetComponent<RectTransform>());
Debug.Log($"[HandleDropSuccess] 드롭 위치: {dropPosition}");
Debug.Log($"[HandleDropSuccess] 드롭 위치: {targetItem?.Data?.Name} {dropPosition}");
switch (dropPosition)
{
@@ -515,8 +584,8 @@ namespace UVC.UI.List.Tree
// 하위 1/3: 아래
var height = targetRect.rect.height;
var thresholdUpper = height * 0.33f;
var thresholdLower = height * 0.67f;
var thresholdUpper = height * 0.2f;
var thresholdLower = height * 0.8f;
// 월드 좌표에서 로컬 좌표로 변환
RectTransformUtility.ScreenPointToLocalPointInRectangle(
@@ -550,8 +619,69 @@ namespace UVC.UI.List.Tree
// 기존 부모에서 제거
RemoveFromParent(draggedData);
Debug.Log($"[MoveToRoot] {draggedData.Name}을(를) 루트로 이동");
// 루트 레벨에 추가 (트리 구조 데이터에만 적용)
// UI 업데이트는 이벤트 구독자가 처리
// ✅ 루트 레벨에 추가
// TreeList의 Root는 직접 자식들을 포함하는 컨테이너
// Root의 자식 TreeListItem들이 실제 루트 레벨 아이템
// 데이터 구조에서 루트 아이템을 찾기 위해 모든 루트 아이템들을 순회
//
if (treeList == null)
{
return;
}
var root = treeList.Root;
if (root == null)
{
return;
}
// 1. 현재 마우스 위치(드래그 대상)를 기반으로 루트 아이템의 인덱스 계산
// 2. AddItemAt()을 호출하여 정확한 위치에 아이템 추가
// 루트 아이템 중 마우스 위의 아이템 찾기
var targetItem = GetItemAtMousePosition(Input.mousePosition);
if (targetItem != null && targetItem.Data != null)
{
// 드롭 위치 판단
var dropPosition = GetDropPosition(targetItem.GetComponent<RectTransform>());
var targetData = targetItem.Data;
// 대상 아이템이 루트 레벨의 아이템인지 확인
if (targetData.Parent == null)
{
// 루트 레벨 아이템이라면 해당 위치에 삽입
int targetIndex = treeList.Items.IndexOf(targetData);
if (targetIndex >= 0)
{
// Above: 대상 아이템 앞에 삽입
if (dropPosition == DropPosition.Above)
{
treeList.AddItemAt(draggedData, targetIndex);
}
// Below: 대상 아이템 뒤에 삽입
else if (dropPosition == DropPosition.Below)
{
treeList.AddItemAt(draggedData, targetIndex + 1);
}
else
{
// InsideAsChild: 루트 레벨에서는 아래에 추가
treeList.AddItemAt(draggedData, targetIndex + 1);
}
Debug.Log($"[MoveToRoot] {draggedData.Name}을(를) 루트 레벨의 인덱스 {targetIndex}에 추가");
return;
}
}
}
// 루트 레벨에 아이템이 없거나 유효한 위치를 찾지 못한 경우 끝에 추가
treeList.AddItem(draggedData);
Debug.Log($"[MoveToRoot] {draggedData.Name}을(를) 루트 레벨의 끝에 추가");
}
/// <summary>
@@ -582,6 +712,21 @@ namespace UVC.UI.List.Tree
Debug.Log($"[MoveBefore] {draggedData.Name}을(를) {targetData.Name} 앞으로 이동");
}
}
else
{
// 루트 레벨인 경우
var treeListItems = treeList?.Items;
if (treeListItems != null)
{
var targetIndex = treeListItems.IndexOf(targetData);
if (targetIndex >= 0)
{
treeList?.AddItemAt(draggedData, targetIndex);
Debug.Log($"[MoveBefore] {draggedData.Name}을(를) {targetData.Name} 앞으로 이동 (루트 레벨)");
}
}
}
}
/// <summary>
@@ -602,18 +747,41 @@ namespace UVC.UI.List.Tree
Debug.Log($"[MoveAfter] {draggedData.Name}을(를) {targetData.Name} 뒤로 이동");
}
}
else
{
// 루트 레벨인 경우
var treeListItems = treeList?.Items;
if (treeListItems != null)
{
var targetIndex = treeListItems.IndexOf(targetData);
if (targetIndex >= 0)
{
treeList?.AddItemAt(draggedData, targetIndex + 1);
Debug.Log($"[MoveAfter] {draggedData.Name}을(를) {targetData.Name} 뒤로 이동 (루트 레벨)");
}
}
}
}
/// <summary>
/// 아이템을 현재 부모에서 제거합니다.
/// </summary>
private void RemoveFromParent(TreeListItemData item)
{
var parent = FindParentOfItem(item);
Debug.Log($"[RemoveFromParent] {item.Name}을(를) 부모 {parent == null}에서 제거");
if (parent != null)
{
parent.RemoveChild(item);
}
else
{
// 루트 레벨에서 제거
treeList?.RemoveItem(item);
}
treeList?.UpdateFlattenedItemList();
}
/// <summary>
@@ -626,62 +794,17 @@ namespace UVC.UI.List.Tree
return null;
}
// 루트 아이템들 확인
foreach (Transform child in treeList.GetComponent<RectTransform>())
foreach (TreeListItemData data in treeList!.AllItemsFlattened)
{
var childItem = child.GetComponent<TreeListItem>();
if (childItem?.Data == item)
if (data == item)
{
return null; // 루트 레벨
return data.Parent;
}
}
// 전체 트리 순회해서 부모 찾기
return FindParentRecursive(item, null);
}
private TreeListItemData? FindParentRecursive(TreeListItemData target, TreeListItemData? currentParent)
{
if (currentParent != null)
{
foreach (var child in currentParent.Children)
{
if (child == target)
{
return currentParent;
}
var found = FindParentRecursive(target, child);
if (found != null)
{
return found;
}
}
}
else
{
// 루트 레벨의 모든 아이템 확인
if (treeList != null)
{
var root = treeList.GetComponent<RectTransform>();
foreach (Transform child in root)
{
var childItem = child.GetComponent<TreeListItem>();
if (childItem?.Data != null)
{
var found = FindParentRecursive(target, childItem.Data);
if (found != null)
{
return found;
}
}
}
}
}
return null;
}
/// <summary>
/// 드롭 위치를 나타내는 열거형입니다.
/// </summary>