사용자 정보 모달, 드래그 리스트 개발
This commit is contained in:
8
Assets/Scripts/UVC/UI/List/Draggable.meta
Normal file
8
Assets/Scripts/UVC/UI/List/Draggable.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 521099f40c2c22441b4506477f820698
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
162
Assets/Scripts/UVC/UI/List/Draggable/DragBehavior.cs
Normal file
162
Assets/Scripts/UVC/UI/List/Draggable/DragBehavior.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UVC.UI.List.Draggable
|
||||
{
|
||||
/// <summary>
|
||||
/// 드래그 동작만을 처리하는 컴포넌트입니다.
|
||||
/// UI 요소를 드래그할 수 있게 만들어주는 기본 기능을 제공합니다.
|
||||
///
|
||||
/// 사용 예시:
|
||||
/// 1. 드래그하고 싶은 UI GameObject에 이 컴포넌트를 추가합니다.
|
||||
/// 2. 드래그 이벤트를 받고 싶다면 아래와 같이 구독합니다:
|
||||
///
|
||||
/// void Start()
|
||||
/// {
|
||||
/// var dragBehavior = GetComponent<DragBehavior>();
|
||||
/// dragBehavior.OnDragStarted += (eventData) => Debug.Log("드래그 시작!");
|
||||
/// dragBehavior.OnDragging += (eventData) => Debug.Log("드래그 중...");
|
||||
/// dragBehavior.OnDragEnded += (eventData) => Debug.Log("드래그 끝!");
|
||||
/// }
|
||||
/// </summary>
|
||||
public class DragBehavior : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
[Tooltip("드래그 대상 RectTransform. 이 RectTransform이 드래그 중에 따라다닙니다.")]
|
||||
[SerializeField]
|
||||
private RectTransform dragTarget;
|
||||
|
||||
[SerializeField, Tooltip("수직 드래그 사용 여부")]
|
||||
private bool verticalDrag = true;
|
||||
|
||||
[SerializeField, Tooltip("수평 드래그 사용 여부")]
|
||||
private bool horizontalDrag = false;
|
||||
|
||||
/// <summary>
|
||||
/// 드래그가 시작될 때 발생하는 이벤트
|
||||
/// </summary>
|
||||
public Action<PointerEventData>? OnDragStarted;
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 중일 때 계속 발생하는 이벤트
|
||||
/// </summary>
|
||||
public Action<PointerEventData>? OnDragging;
|
||||
|
||||
/// <summary>
|
||||
/// 드래그가 끝날 때 발생하는 이벤트
|
||||
/// </summary>
|
||||
public Action<PointerEventData>? OnDragEnded;
|
||||
|
||||
// 드래그 시작 전의 원래 위치를 저장
|
||||
private Vector2 originalPosition;
|
||||
|
||||
// 드래그 시작 전의 원래 부모 Transform을 저장
|
||||
private Transform? originalParent;
|
||||
|
||||
// 드래그 시작 전의 원래 순서(인덱스)를 저장
|
||||
private int originalIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 원래 순서를 가져옵니다
|
||||
/// </summary>
|
||||
public int OriginalIndex => originalIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 원래 부모 Transform을 가져옵니다
|
||||
/// </summary>
|
||||
public Transform? OriginalParent => originalParent;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// dragTarget이 설정되지 않은 경우 현재 GameObject의 RectTransform을 사용
|
||||
if (dragTarget == null)
|
||||
{
|
||||
dragTarget = GetComponent<RectTransform>();
|
||||
}
|
||||
if (dragTarget == null)
|
||||
{
|
||||
Debug.LogError($"[DragBehavior] 드래그 대상 RectTransform이 설정되지 않았습니다! - {gameObject.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unity가 드래그 시작을 감지했을 때 자동으로 호출됩니다
|
||||
/// </summary>
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
// 현재 상태를 저장합니다
|
||||
originalPosition = dragTarget.position;
|
||||
originalParent = dragTarget.parent;
|
||||
originalIndex = dragTarget.GetSiblingIndex();
|
||||
|
||||
// 구독자들에게 드래그 시작을 알립니다
|
||||
OnDragStarted?.Invoke(eventData);
|
||||
|
||||
// 디버그 로그 (개발 중 확인용)
|
||||
Debug.Log($"[DragBehavior] 드래그 시작 - 아이템: {dragTarget.name}, 원래 인덱스: {originalIndex}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unity가 드래그 중임을 감지했을 때 매 프레임 호출됩니다
|
||||
/// </summary>
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
// 오브젝트를 마우스 위치로 이동시킵니다
|
||||
if (verticalDrag)
|
||||
dragTarget.anchoredPosition += new Vector2(0, eventData.delta.y);
|
||||
else if (horizontalDrag)
|
||||
dragTarget.anchoredPosition += new Vector2(eventData.delta.x, 0);
|
||||
else
|
||||
dragTarget.anchoredPosition += eventData.delta;
|
||||
|
||||
|
||||
// 구독자들에게 드래그 중임을 알립니다
|
||||
OnDragging?.Invoke(eventData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unity가 드래그 종료를 감지했을 때 자동으로 호출됩니다
|
||||
/// </summary>
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
// 구독자들에게 드래그 종료를 알립니다
|
||||
OnDragEnded?.Invoke(eventData);
|
||||
|
||||
Debug.Log($"[DragBehavior] 드래그 종료 - 아이템: {dragTarget.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 종료 후 새로운 위치 정보를 업데이트합니다
|
||||
/// </summary>
|
||||
public void UpdatePositionInfo()
|
||||
{
|
||||
originalParent = dragTarget.parent;
|
||||
originalIndex = dragTarget.GetSiblingIndex();
|
||||
originalPosition = dragTarget.position;
|
||||
|
||||
Debug.Log($"[DragBehavior] 위치 정보 업데이트 - 새 인덱스: {originalIndex}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 오브젝트를 원래 위치로 되돌립니다
|
||||
///
|
||||
/// 사용 예시:
|
||||
/// if (드래그가_유효하지_않음)
|
||||
/// {
|
||||
/// dragBehavior.ResetPosition();
|
||||
/// }
|
||||
/// </summary>
|
||||
public void ResetPosition()
|
||||
{
|
||||
if (originalParent != null)
|
||||
{
|
||||
dragTarget.SetParent(originalParent);
|
||||
dragTarget.SetSiblingIndex(originalIndex);
|
||||
dragTarget.position = originalPosition;
|
||||
|
||||
Debug.Log($"[DragBehavior] 위치 리셋 - 아이템: {dragTarget.name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 261f68435056bc44fbadb799304989f6
|
||||
413
Assets/Scripts/UVC/UI/List/Draggable/DraggableList.cs
Normal file
413
Assets/Scripts/UVC/UI/List/Draggable/DraggableList.cs
Normal file
@@ -0,0 +1,413 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.List.Draggable
|
||||
{
|
||||
/// <summary>
|
||||
/// 드래그 가능한 리스트의 전체 기능을 관리하는 메인 컴포넌트입니다.
|
||||
/// 다른 모든 컴포넌트들을 조합하여 완전한 드래그 앤 드롭 리스트를 만듭니다.
|
||||
///
|
||||
/// 설정 방법:
|
||||
/// 1. ScrollView GameObject에 이 컴포넌트를 추가합니다.
|
||||
/// - ScrollRectHandler 추가
|
||||
/// 2. Inspector에서 Item Prefab과 Content를 연결합니다.
|
||||
/// 3. Item Prefab에는 다음 컴포넌트들이 필요합니다:
|
||||
/// - ListItemController
|
||||
/// - ListItemView
|
||||
/// - DragBehavior
|
||||
/// 4.ScrollView > Viewport > Content
|
||||
/// - Vertical Layout Group 추가(Control Child Size: width, Child Force Expand: width)
|
||||
/// - Content Size Fitter 추가(Vertical Fit: Preferred Size)
|
||||
/// - ListReorderHandler 추가
|
||||
///
|
||||
/// 사용 예시:
|
||||
/// public class MyListData : IListItemData
|
||||
/// {
|
||||
/// public string Id { get; set; }
|
||||
/// public string DisplayName { get; set; }
|
||||
/// }
|
||||
///
|
||||
/// void Start()
|
||||
/// {
|
||||
/// var list = GetComponent<DraggableList>();
|
||||
///
|
||||
/// list.OnOrderChanged += (data, oldIndex, newIndex) => Debug.Log($"아이템 순서 변경: {data.DisplayName} ({oldIndex} -> {newIndex})");
|
||||
/// list.OnItemClicked += (data) => Debug.Log($"아이템 클릭: {data.DisplayName}");
|
||||
/// list.OnChangedItemData += (data) => Debug.Log($"아이템 데이터 변경: {data.DisplayName}");
|
||||
///
|
||||
/// var dataList = new List<MyListData>
|
||||
/// {
|
||||
/// new MyListData { Id = "1", DisplayName = "첫 번째 아이템" },
|
||||
/// new MyListData { Id = "2", DisplayName = "두 번째 아이템" }
|
||||
/// };
|
||||
///
|
||||
/// list.SetData(dataList);
|
||||
/// }
|
||||
/// </summary>
|
||||
public class DraggableList : MonoBehaviour
|
||||
{
|
||||
[Header("필수 설정")]
|
||||
[SerializeField]
|
||||
[Tooltip("리스트 아이템으로 사용할 프리팹. DragBehavior, ListItemController, ItemDataBinder가 있어야 합니다.")]
|
||||
private GameObject? itemPrefab;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("아이템들이 추가될 부모 Transform (보통 ScrollView > Viewport > Content)")]
|
||||
private Transform? content;
|
||||
|
||||
//[Header("옵션")]
|
||||
//[SerializeField]
|
||||
//[Tooltip("순서 변경을 자동으로 저장할지 여부")]
|
||||
//private bool autoSave = false;
|
||||
|
||||
// 컴포넌트 참조들
|
||||
private ListReorderHandler? reorderHandler;
|
||||
private ScrollRectHandler? scrollHandler;
|
||||
|
||||
// 현재 데이터 리스트
|
||||
private List<ListItemData> dataList = new List<ListItemData>();
|
||||
public IReadOnlyList<ListItemData> DataList => dataList;
|
||||
|
||||
// 아이템 GameObject와 데이터의 매핑
|
||||
private Dictionary<GameObject, ListItemData> itemDataMap = new Dictionary<GameObject, ListItemData>();
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 순서가 변경될 때 발생하는 이벤트, oldIndex, newIndex
|
||||
/// </summary>
|
||||
public event Action<ListItemData, int, int>? OnOrderChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템이 클릭될 때 발생하는 이벤트
|
||||
/// </summary>
|
||||
public event Action<ListItemData>? OnItemClicked;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 데이터가 변경 됐을 때 발생하는 이벤트
|
||||
/// </summary>
|
||||
public event Action<ListItemData>? OnChangedItemData;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트 초기화
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
ValidateSetup();
|
||||
SetupComponents();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 필수 설정이 올바른지 검증합니다
|
||||
/// </summary>
|
||||
private void ValidateSetup()
|
||||
{
|
||||
if (itemPrefab == null)
|
||||
{
|
||||
Debug.LogError($"[DraggableList] Item Prefab이 설정되지 않았습니다! - {gameObject.name}");
|
||||
}
|
||||
|
||||
if (content == null)
|
||||
{
|
||||
Debug.LogError($"[DraggableList] Content Transform이 설정되지 않았습니다! - {gameObject.name}");
|
||||
}
|
||||
|
||||
// 프리팹에 필수 컴포넌트가 있는지 확인
|
||||
if (itemPrefab != null)
|
||||
{
|
||||
if (itemPrefab.GetComponent<DragBehavior>() == null)
|
||||
if (itemPrefab.GetComponentInChildren<DragBehavior>() == null)
|
||||
Debug.LogError($"[DraggableList] Item Prefab에 DragBehavior가 없습니다!");
|
||||
|
||||
if (itemPrefab.GetComponent<ListItemController>() == null)
|
||||
Debug.LogError($"[DraggableList] Item Prefab에 ListItemController가 없습니다!");
|
||||
|
||||
if (itemPrefab.GetComponent<ListItemView>() == null)
|
||||
Debug.LogError($"[DraggableList] Item Prefab에 ItemDataBinder가 없습니다!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 필요한 컴포넌트들을 설정합니다
|
||||
/// </summary>
|
||||
private void SetupComponents()
|
||||
{
|
||||
if (content == null) return;
|
||||
|
||||
// ListReorderHandler 추가 또는 가져오기
|
||||
reorderHandler = content.GetComponent<ListReorderHandler>();
|
||||
if (reorderHandler == null)
|
||||
{
|
||||
reorderHandler = content.gameObject.AddComponent<ListReorderHandler>();
|
||||
Debug.Log("[DraggableList] ListReorderHandler를 자동으로 추가했습니다.");
|
||||
}
|
||||
|
||||
// ScrollRectHandler 추가 또는 가져오기
|
||||
var scrollRect = GetComponentInChildren<ScrollRect>();
|
||||
if (scrollRect != null)
|
||||
{
|
||||
scrollHandler = scrollRect.GetComponent<ScrollRectHandler>();
|
||||
if (scrollHandler == null)
|
||||
{
|
||||
scrollHandler = scrollRect.gameObject.AddComponent<ScrollRectHandler>();
|
||||
Debug.Log("[DraggableList] ScrollRectHandler를 자동으로 추가했습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
// 이벤트 연결
|
||||
if (reorderHandler != null)
|
||||
{
|
||||
reorderHandler.OnOrderChanged += HandleOrderChanged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 리스트에 표시할 데이터를 설정합니다
|
||||
/// </summary>
|
||||
/// <typeparam name="T">IListItemData를 구현한 데이터 타입</typeparam>
|
||||
/// <param name="data">표시할 데이터 리스트</param>
|
||||
public void SetData<T>(List<T> data) where T : ListItemData
|
||||
{
|
||||
// 기존 데이터와 UI 정리
|
||||
ClearList();
|
||||
|
||||
// 새 데이터 추가
|
||||
dataList.Clear();
|
||||
foreach (var item in data)
|
||||
{
|
||||
dataList.Add(item);
|
||||
}
|
||||
|
||||
// UI 생성
|
||||
RefreshUI();
|
||||
|
||||
Debug.Log($"[DraggableList] 데이터 설정 완료 - {data.Count}개 아이템");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 데이터를 기반으로 UI를 다시 생성합니다
|
||||
/// </summary>
|
||||
private void RefreshUI()
|
||||
{
|
||||
if (content == null || itemPrefab == null) return;
|
||||
|
||||
// 기존 UI 제거
|
||||
foreach (Transform child in content)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
itemDataMap.Clear();
|
||||
|
||||
// 새 UI 생성
|
||||
foreach (var data in dataList)
|
||||
{
|
||||
CreateItemUI(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 단일 아이템의 UI를 생성합니다
|
||||
/// </summary>
|
||||
private void CreateItemUI(ListItemData data)
|
||||
{
|
||||
if (itemPrefab == null || content == null) return;
|
||||
|
||||
// 프리팹으로부터 새 아이템 생성
|
||||
GameObject item = Instantiate(itemPrefab, content);
|
||||
item.name = $"Item_{data.Id ?? "Unknown"}";
|
||||
|
||||
// 데이터 아이템 View 컴포넌트 가져오기
|
||||
var binder = item.GetComponent<ListItemView>();
|
||||
binder?.BindData(data);
|
||||
if(binder != null) binder.OnChangeData += (data) => OnChangedItemData?.Invoke(data);
|
||||
|
||||
// 클릭 이벤트 설정
|
||||
var button = item.GetComponent<Button>();
|
||||
if (button != null)
|
||||
{
|
||||
button.onClick.AddListener(() => HandleItemClick(data));
|
||||
}
|
||||
|
||||
// 매핑 저장
|
||||
itemDataMap[item] = data;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 순서 변경을 처리합니다
|
||||
/// </summary>
|
||||
private void HandleOrderChanged(int oldIndex, int newIndex)
|
||||
{
|
||||
if (oldIndex == newIndex) return;
|
||||
|
||||
// 데이터 리스트의 순서도 변경
|
||||
if (oldIndex >= 0 && oldIndex < dataList.Count &&
|
||||
newIndex >= 0 && newIndex < dataList.Count)
|
||||
{
|
||||
var item = dataList[oldIndex];
|
||||
dataList.RemoveAt(oldIndex);
|
||||
dataList.Insert(newIndex, item);
|
||||
|
||||
// 이벤트 발생
|
||||
OnOrderChanged?.Invoke((item as ListItemData)!, oldIndex, newIndex);
|
||||
|
||||
// 자동 저장
|
||||
//if (autoSave)
|
||||
//{
|
||||
// SaveOrder();
|
||||
//}
|
||||
|
||||
Debug.Log($"[DraggableList] 순서 변경: {oldIndex} -> {newIndex}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 클릭을 처리합니다
|
||||
/// </summary>
|
||||
private void HandleItemClick(object data)
|
||||
{
|
||||
OnItemClicked?.Invoke((data as ListItemData)!);
|
||||
Debug.Log($"[DraggableList] 아이템 클릭: {(data as ListItemData)?.DisplayName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 순서를 저장합니다
|
||||
/// </summary>
|
||||
private void SaveOrder()
|
||||
{
|
||||
// 간단한 예시: ID 순서를 PlayerPrefs에 저장
|
||||
var ids = new List<string>();
|
||||
foreach (var item in dataList)
|
||||
{
|
||||
if (item is ListItemData listItem)
|
||||
{
|
||||
ids.Add(listItem.Id);
|
||||
}
|
||||
}
|
||||
|
||||
string orderString = string.Join(",", ids);
|
||||
PlayerPrefs.SetString("ListOrder", orderString);
|
||||
PlayerPrefs.Save();
|
||||
|
||||
Debug.Log($"[DraggableList] 순서 저장됨: {orderString}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 저장된 순서를 불러옵니다
|
||||
/// </summary>
|
||||
public void LoadOrder()
|
||||
{
|
||||
string orderString = PlayerPrefs.GetString("ListOrder", "");
|
||||
if (string.IsNullOrEmpty(orderString)) return;
|
||||
|
||||
// TODO: 저장된 순서에 따라 dataList를 재정렬하는 로직 구현
|
||||
Debug.Log($"[DraggableList] 순서 불러옴: {orderString}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 리스트를 완전히 비웁니다
|
||||
/// </summary>
|
||||
public void ClearList()
|
||||
{
|
||||
if (content != null)
|
||||
{
|
||||
foreach (Transform child in content)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
dataList.Clear();
|
||||
itemDataMap.Clear();
|
||||
|
||||
Debug.Log("[DraggableList] 리스트 초기화됨");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 아이템을 추가합니다
|
||||
/// </summary>
|
||||
public void AddItem<T>(T item) where T : ListItemData
|
||||
{
|
||||
dataList.Add(item);
|
||||
CreateItemUI(item);
|
||||
|
||||
//if (autoSave)
|
||||
//{
|
||||
// SaveOrder();
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 ID의 아이템을 제거합니다
|
||||
/// </summary>
|
||||
public void RemoveItem(string id)
|
||||
{
|
||||
// 데이터에서 찾기
|
||||
ListItemData? toRemove = null;
|
||||
foreach (var item in dataList)
|
||||
{
|
||||
if (item is ListItemData listItem && listItem.Id == id)
|
||||
{
|
||||
toRemove = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove != null)
|
||||
{
|
||||
dataList.Remove(toRemove);
|
||||
|
||||
// UI에서 제거
|
||||
GameObject? uiToRemove = null;
|
||||
foreach (var kvp in itemDataMap)
|
||||
{
|
||||
if (kvp.Value == toRemove)
|
||||
{
|
||||
uiToRemove = kvp.Key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uiToRemove != null)
|
||||
{
|
||||
itemDataMap.Remove(uiToRemove);
|
||||
Destroy(uiToRemove);
|
||||
}
|
||||
|
||||
//if (autoSave)
|
||||
//{
|
||||
// SaveOrder();
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 데이터 리스트를 가져옵니다
|
||||
/// </summary>
|
||||
public List<T> GetData<T>() where T : class
|
||||
{
|
||||
var result = new List<T>();
|
||||
foreach (var item in dataList)
|
||||
{
|
||||
if (item is T typedItem)
|
||||
{
|
||||
result.Add(typedItem);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 파괴될 때 이벤트 구독을 해제합니다
|
||||
/// </summary>
|
||||
void OnDestroy()
|
||||
{
|
||||
if (reorderHandler != null)
|
||||
{
|
||||
reorderHandler.OnOrderChanged -= HandleOrderChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b8727d0aa73f3da458d5425f3097273f
|
||||
210
Assets/Scripts/UVC/UI/List/Draggable/ListItemController.cs
Normal file
210
Assets/Scripts/UVC/UI/List/Draggable/ListItemController.cs
Normal file
@@ -0,0 +1,210 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.List.Draggable
|
||||
{
|
||||
/// <summary>
|
||||
/// 리스트 아이템의 드래그와 비주얼을 관리하는 컴포넌트입니다.
|
||||
/// DragBehavior와 함께 사용되어 드래그 시 시각적 효과를 처리합니다.
|
||||
///
|
||||
/// 필수 설정:
|
||||
/// 1. 이 컴포넌트가 있는 GameObject에 또는 자식에 DragBehavior를 함께 추가해야 합니다.
|
||||
/// 2. CanvasGroup이 없으면 자동으로 추가됩니다.
|
||||
///
|
||||
/// Inspector 설정:
|
||||
/// - Drag Alpha: 드래그 중 투명도 (0~1, 기본값 0.6)
|
||||
///
|
||||
/// 사용 예시:
|
||||
/// GameObject listItem = Instantiate(itemPrefab);
|
||||
/// listItem.AddComponent<DragBehavior>();
|
||||
/// listItem.AddComponent<ListItemController>();
|
||||
/// </summary>
|
||||
public class ListItemController : MonoBehaviour
|
||||
{
|
||||
// 컴포넌트 참조들
|
||||
private CanvasGroup? canvasGroup;
|
||||
private ListReorderHandler? reorderHandler;
|
||||
private GameObject? placeholder;
|
||||
|
||||
[Header("DragBehavior 설정")]
|
||||
[SerializeField]
|
||||
private DragBehavior? dragBehavior;
|
||||
|
||||
[Header("비주얼 설정")]
|
||||
[SerializeField, Range(0f, 1f)]
|
||||
[Tooltip("드래그 중일 때의 투명도 값입니다. 0은 완전 투명, 1은 완전 불투명")]
|
||||
private float dragAlpha = 0.6f;
|
||||
|
||||
/// <summary>
|
||||
/// 현재 생성된 플레이스홀더를 가져옵니다
|
||||
/// </summary>
|
||||
public GameObject? Placeholder => placeholder;
|
||||
|
||||
/// <summary>
|
||||
/// 원래 순서(인덱스)를 가져옵니다
|
||||
/// </summary>
|
||||
public int OriginalIndex => dragBehavior?.OriginalIndex ?? -1;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트 초기화 - Unity가 자동으로 호출
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
// 필수 컴포넌트들을 가져옵니다
|
||||
if(dragBehavior == null)
|
||||
dragBehavior = GetComponentInChildren<DragBehavior>();
|
||||
|
||||
// CanvasGroup이 없으면 추가합니다 (알파값 조절을 위해 필요)
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
if (canvasGroup == null)
|
||||
{
|
||||
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||
Debug.Log($"[ListItemController] CanvasGroup을 자동으로 추가했습니다 - {gameObject.name}");
|
||||
}
|
||||
|
||||
// 드래그 이벤트에 핸들러 메서드들을 연결합니다
|
||||
if (dragBehavior != null)
|
||||
{
|
||||
dragBehavior.OnDragStarted += HandleDragStart;
|
||||
dragBehavior.OnDragging += HandleDragging;
|
||||
dragBehavior.OnDragEnded += HandleDragEnd;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"[ListItemController] DragBehavior를 찾을 수 없습니다! - {gameObject.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start는 Awake 이후에 호출되며, 다른 오브젝트의 컴포넌트를 찾을 때 사용
|
||||
/// </summary>
|
||||
void Start()
|
||||
{
|
||||
// 부모에서 ListReorderHandler를 찾습니다
|
||||
reorderHandler = GetComponentInParent<ListReorderHandler>();
|
||||
|
||||
if (reorderHandler == null)
|
||||
{
|
||||
Debug.LogWarning($"[ListItemController] ListReorderHandler를 찾을 수 없습니다. 재정렬 기능이 작동하지 않을 수 있습니다. - {gameObject.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그가 시작될 때 호출되는 핸들러
|
||||
/// </summary>
|
||||
private void HandleDragStart(PointerEventData eventData)
|
||||
{
|
||||
Debug.Log($"[ListItemController] 드래그 시작 처리 - {gameObject.name}");
|
||||
|
||||
// 1. 플레이스홀더(빈 공간 표시)를 생성합니다
|
||||
CreatePlaceholder();
|
||||
|
||||
// 2. 드래그 중인 아이템을 반투명하게 만듭니다
|
||||
if (canvasGroup != null)
|
||||
{
|
||||
canvasGroup.alpha = dragAlpha;
|
||||
canvasGroup.blocksRaycasts = false; // 드래그 중에는 클릭을 받지 않습니다
|
||||
}
|
||||
|
||||
// 3. 아이템을 최상위 Canvas로 이동시켜 다른 UI 위에 표시되도록 합니다
|
||||
Canvas? rootCanvas = GetComponentInParent<Canvas>();
|
||||
if (rootCanvas != null)
|
||||
{
|
||||
transform.SetParent(rootCanvas.transform);
|
||||
transform.SetAsLastSibling(); // 가장 위에 표시
|
||||
}
|
||||
|
||||
// 4. 리오더 핸들러에 드래그 시작을 알립니다
|
||||
reorderHandler?.StartReorder(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 중일 때 계속 호출되는 핸들러
|
||||
/// </summary>
|
||||
private void HandleDragging(PointerEventData eventData)
|
||||
{
|
||||
// 리오더 핸들러가 현재 마우스 위치에서 적절한 드롭 위치를 계산하도록 합니다
|
||||
reorderHandler?.UpdateReorderPosition(this, eventData.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그가 끝날 때 호출되는 핸들러
|
||||
/// </summary>
|
||||
private void HandleDragEnd(PointerEventData eventData)
|
||||
{
|
||||
Debug.Log($"[ListItemController] 드래그 종료 처리 - {gameObject.name}");
|
||||
|
||||
// 1. 아이템을 원래 투명도로 복원합니다
|
||||
if (canvasGroup != null)
|
||||
{
|
||||
canvasGroup.alpha = 1f;
|
||||
canvasGroup.blocksRaycasts = true; // 다시 클릭을 받을 수 있도록
|
||||
}
|
||||
|
||||
// 2. 리오더 핸들러가 최종 위치를 결정하도록 합니다
|
||||
reorderHandler?.EndReorder(this);
|
||||
|
||||
// 3. 플레이스홀더를 제거합니다
|
||||
if (placeholder != null)
|
||||
{
|
||||
Destroy(placeholder);
|
||||
placeholder = null;
|
||||
}
|
||||
|
||||
// 중요: 드래그 종료 후 새로운 위치 정보를 업데이트
|
||||
dragBehavior?.UpdatePositionInfo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 플레이스홀더(빈 공간 표시용 오브젝트)를 생성합니다
|
||||
/// 드래그 중인 아이템이 원래 있던 자리를 표시하는 용도입니다
|
||||
/// </summary>
|
||||
private void CreatePlaceholder()
|
||||
{
|
||||
// 이미 플레이스홀더가 있다면 생성하지 않습니다
|
||||
if (placeholder != null) return;
|
||||
|
||||
// 새 GameObject를 만들어 플레이스홀더로 사용합니다
|
||||
placeholder = new GameObject("Placeholder");
|
||||
|
||||
// RectTransform 컴포넌트를 추가하고 설정합니다
|
||||
var rect = placeholder.AddComponent<RectTransform>();
|
||||
|
||||
if (dragBehavior?.OriginalParent != null)
|
||||
{
|
||||
rect.SetParent(dragBehavior.OriginalParent);
|
||||
rect.SetSiblingIndex(dragBehavior.OriginalIndex);
|
||||
|
||||
// 원본과 같은 크기로 설정합니다
|
||||
var originalRect = GetComponent<RectTransform>();
|
||||
if (originalRect != null)
|
||||
{
|
||||
rect.sizeDelta = originalRect.sizeDelta;
|
||||
}
|
||||
}
|
||||
|
||||
// 시각적으로 구분할 수 있도록 반투명한 회색 이미지를 추가합니다
|
||||
var image = placeholder.AddComponent<Image>();
|
||||
image.color = new Color(0.5f, 0.5f, 0.5f, 0.3f);
|
||||
|
||||
Debug.Log($"[ListItemController] 플레이스홀더 생성됨 - {gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 파괴될 때 이벤트 구독을 해제합니다
|
||||
/// </summary>
|
||||
void OnDestroy()
|
||||
{
|
||||
// 메모리 누수를 방지하기 위해 이벤트 구독을 해제합니다
|
||||
if (dragBehavior != null)
|
||||
{
|
||||
dragBehavior.OnDragStarted -= HandleDragStart;
|
||||
dragBehavior.OnDragging -= HandleDragging;
|
||||
dragBehavior.OnDragEnded -= HandleDragEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98e103c1fce908c4baee1f553081f2f1
|
||||
44
Assets/Scripts/UVC/UI/List/Draggable/ListItemData.cs
Normal file
44
Assets/Scripts/UVC/UI/List/Draggable/ListItemData.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#nullable enable
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UI.List.Draggable
|
||||
{
|
||||
/// <summary>
|
||||
/// 리스트 아이템 데이터의 기본 클래스 입니다.
|
||||
/// 모든 리스트 아이템 데이터는 이 클래스를 상속해야 합니다
|
||||
///
|
||||
/// 구현 예시:
|
||||
/// public class MyItemData : ListItemData
|
||||
/// {
|
||||
/// public string Title;
|
||||
/// public MyItemData(string title, string id, string displayName, Sprite? icon = null) : base(id, displayName, icon)
|
||||
/// {
|
||||
/// Title = title;
|
||||
/// }
|
||||
/// }
|
||||
/// </summary>
|
||||
public class ListItemData
|
||||
{
|
||||
/// <summary>
|
||||
/// 아이템의 고유 식별자
|
||||
/// </summary>
|
||||
public string Id;
|
||||
|
||||
/// <summary>
|
||||
/// UI에 표시될 이름
|
||||
/// </summary>
|
||||
public string DisplayName;
|
||||
|
||||
/// <summary>
|
||||
/// 아이템의 아이콘 스프라이트
|
||||
/// </summary>
|
||||
public Sprite? Icon;
|
||||
|
||||
public ListItemData(string id, string displayName, Sprite? icon = null)
|
||||
{
|
||||
Id = id;
|
||||
DisplayName = displayName;
|
||||
Icon = icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 909d02b315354e748b0ed02efa4133c6
|
||||
137
Assets/Scripts/UVC/UI/List/Draggable/ListItemView.cs
Normal file
137
Assets/Scripts/UVC/UI/List/Draggable/ListItemView.cs
Normal file
@@ -0,0 +1,137 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using static RTG.GizmoTransform;
|
||||
|
||||
namespace UVC.UI.List.Draggable
|
||||
{
|
||||
/// <summary>
|
||||
/// UI 요소와 데이터를 연결하는 컴포넌트입니다.
|
||||
/// 데이터 변경 시 UI를 자동으로 업데이트합니다.
|
||||
///
|
||||
/// 사용 방법:
|
||||
/// 1. 리스트 아이템 프리팹에 이 컴포넌트를 추가합니다.
|
||||
/// 2. Inspector에서 Text와 Image 컴포넌트를 연결합니다.
|
||||
/// 3. BindData 메서드로 데이터를 설정합니다.
|
||||
///
|
||||
/// 예시:
|
||||
/// var binder = listItem.GetComponent<ListItemView>();
|
||||
/// binder.BindData(new MyItemData { DisplayName = "아이템 1" });
|
||||
///
|
||||
/// // 나중에 데이터 가져오기
|
||||
/// var data = binder.GetData<MyItemData>();
|
||||
/// </summary>
|
||||
public class ListItemView : MonoBehaviour
|
||||
{
|
||||
[Header("UI 기본 요소 연결")]
|
||||
[SerializeField]
|
||||
[Tooltip("아이템의 제목을 표시할 TextMeshProUGUI 컴포넌트")]
|
||||
protected TextMeshProUGUI? titleText;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("아이템의 아이콘을 표시할 Image 컴포넌트 (선택사항)")]
|
||||
protected Image? iconImage;
|
||||
|
||||
// 현재 바인딩된 데이터를 저장
|
||||
protected ListItemData? boundData;
|
||||
|
||||
public Action<ListItemData>? OnChangeData;
|
||||
|
||||
/// <summary>
|
||||
/// 데이터를 UI에 바인딩합니다
|
||||
/// </summary>
|
||||
/// <param name="data">바인딩할 데이터 객체</param>
|
||||
public virtual void BindData(ListItemData data)
|
||||
{
|
||||
boundData = data;
|
||||
UpdateUI();
|
||||
|
||||
Debug.Log($"[ListItemView] 데이터 바인딩 완료 - {gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 바인딩된 데이터를 기반으로 UI를 업데이트합니다
|
||||
/// </summary>
|
||||
protected virtual void UpdateUI()
|
||||
{
|
||||
// IListItemData 인터페이스를 구현한 경우
|
||||
if (boundData != null)
|
||||
{
|
||||
// 제목 텍스트 업데이트
|
||||
if (titleText != null)
|
||||
{
|
||||
titleText.text = boundData.DisplayName;
|
||||
}
|
||||
|
||||
// IHasIcon 인터페이스도 구현한 경우 아이콘 업데이트
|
||||
if (iconImage != null && boundData.Icon != null)
|
||||
{
|
||||
iconImage.sprite = boundData.Icon;
|
||||
iconImage.gameObject.SetActive(true);
|
||||
}
|
||||
else if (iconImage != null)
|
||||
{
|
||||
// 아이콘이 없으면 숨깁니다
|
||||
iconImage.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 바인딩된 데이터를 특정 타입으로 가져옵니다
|
||||
/// </summary>
|
||||
/// <typeparam name="T">가져올 데이터의 타입</typeparam>
|
||||
/// <returns>바인딩된 데이터, 타입이 맞지 않거나 없으면 null</returns>
|
||||
public T? GetData<T>() where T : ListItemData
|
||||
{
|
||||
return boundData as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 바인딩된 데이터가 있는지 확인합니다
|
||||
/// </summary>
|
||||
public bool HasData => boundData != null;
|
||||
|
||||
/// <summary>
|
||||
/// UI를 수동으로 새로고침합니다
|
||||
/// </summary>
|
||||
public void RefreshUI()
|
||||
{
|
||||
if (boundData != null)
|
||||
{
|
||||
UpdateUI();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 바인딩을 해제하고 UI를 초기화합니다
|
||||
/// </summary>
|
||||
public virtual void ClearBinding()
|
||||
{
|
||||
boundData = null;
|
||||
|
||||
if (titleText != null)
|
||||
{
|
||||
titleText.text = "";
|
||||
}
|
||||
|
||||
if (iconImage != null)
|
||||
{
|
||||
iconImage.sprite = null;
|
||||
iconImage.gameObject.SetActive(false);
|
||||
}
|
||||
//Debug.Log($"[ListItemView] 바인딩 해제됨 - {gameObject.name}");
|
||||
}
|
||||
|
||||
protected virtual void OnDestroy()
|
||||
{
|
||||
// 바인딩 해제 시 추가 작업이 필요하면 여기에 작성
|
||||
ClearBinding();
|
||||
OnChangeData = null;
|
||||
//Debug.Log($"[ListItemView] 컴포넌트 파괴됨 - {gameObject.name}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 056b568ab6ec8dc47b22943eea32fe61
|
||||
181
Assets/Scripts/UVC/UI/List/Draggable/ListReorderHandler.cs
Normal file
181
Assets/Scripts/UVC/UI/List/Draggable/ListReorderHandler.cs
Normal file
@@ -0,0 +1,181 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.VisualScripting;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Extension;
|
||||
|
||||
namespace UVC.UI.List.Draggable
|
||||
{
|
||||
/// <summary>
|
||||
/// 리스트 아이템들의 순서 변경을 관리하는 컴포넌트입니다.
|
||||
/// 드래그 중인 아이템의 위치를 계산하고 순서를 재정렬합니다.
|
||||
///
|
||||
/// 사용 방법:
|
||||
/// 1. ScrollView의 Content GameObject에 이 컴포넌트를 추가합니다.
|
||||
/// 2. OnOrderChanged 이벤트를 구독하여 순서 변경을 감지합니다.
|
||||
///
|
||||
/// 예시:
|
||||
/// void Start()
|
||||
/// {
|
||||
/// var reorderHandler = content.GetComponent<ListReorderHandler>();
|
||||
/// reorderHandler.OnOrderChanged += (oldIndex, newIndex) =>
|
||||
/// {
|
||||
/// Debug.Log($"아이템이 {oldIndex}에서 {newIndex}로 이동했습니다");
|
||||
/// };
|
||||
/// }
|
||||
/// </summary>
|
||||
public class ListReorderHandler : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// 아이템 순서가 변경될 때 발생하는 이벤트
|
||||
/// 첫 번째 매개변수: 이전 인덱스, 두 번째 매개변수: 새 인덱스
|
||||
/// </summary>
|
||||
public Action<int, int>? OnOrderChanged;
|
||||
|
||||
// 현재 드래그 중인 아이템을 추적합니다
|
||||
private ListItemController? currentDragging;
|
||||
|
||||
// 자동 스크롤을 관리하는 핸들러
|
||||
private ScrollRectHandler? scrollHandler;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트 초기화
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
// 부모 GameObject에 있는 ScrollRectHandler를 찾습니다
|
||||
scrollHandler = GetComponentInParent<ScrollRectHandler>();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 드래그가 시작될 때 호출됩니다
|
||||
/// </summary>
|
||||
/// <param name="item">드래그를 시작한 아이템</param>
|
||||
public void StartReorder(ListItemController item)
|
||||
{
|
||||
currentDragging = item;
|
||||
|
||||
// 자동 스크롤을 활성화합니다 (있는 경우)
|
||||
scrollHandler?.EnableAutoScroll(true);
|
||||
|
||||
Debug.Log($"[ListReorderHandler] 재정렬 시작 - {item.gameObject.name}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 중일 때 호출되어 플레이스홀더의 위치를 업데이트합니다
|
||||
/// </summary>
|
||||
/// <param name="item">드래그 중인 아이템</param>
|
||||
/// <param name="position">현재 마우스/터치 위치 (스크린 좌표)</param>
|
||||
public void UpdateReorderPosition(ListItemController item, Vector3 position)
|
||||
{
|
||||
// 플레이스홀더가 없으면 처리하지 않습니다
|
||||
if (item.Placeholder == null) return;
|
||||
|
||||
// 마우스 위치에서 가장 가까운 인덱스를 계산합니다
|
||||
int targetIndex = CalculateTargetIndex(position);
|
||||
Debug.Log($"[ListReorderHandler] 드래그 위치 업데이트 - 타겟 인덱스: {targetIndex}");
|
||||
if (targetIndex >= 0)
|
||||
{
|
||||
// 현재 플레이스홀더의 인덱스와 다를 때만 이동
|
||||
int currentPlaceholderIndex = item.Placeholder.transform.GetSiblingIndex();
|
||||
if (targetIndex != currentPlaceholderIndex)
|
||||
{
|
||||
item.Placeholder.transform.SetSiblingIndex(targetIndex);
|
||||
|
||||
// 레이아웃 즉시 업데이트
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(transform as RectTransform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그가 끝날 때 호출되어 최종 위치를 확정합니다
|
||||
/// </summary>
|
||||
/// <param name="item">드래그가 끝난 아이템</param>
|
||||
public void EndReorder(ListItemController item)
|
||||
{
|
||||
// 자동 스크롤을 비활성화합니다
|
||||
scrollHandler?.EnableAutoScroll(false);
|
||||
|
||||
if (item.Placeholder != null)
|
||||
{
|
||||
// 플레이스홀더의 현재 위치가 최종 위치입니다
|
||||
int newIndex = item.Placeholder.transform.GetSiblingIndex();
|
||||
|
||||
// 아이템을 원래 부모로 되돌리고 새 위치에 배치합니다
|
||||
item.transform.SetParent(transform);
|
||||
item.transform.SetSiblingIndex(newIndex);
|
||||
|
||||
// Layout 재정렬을 위해 강제 업데이트
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(transform as RectTransform);
|
||||
|
||||
// 순서가 실제로 변경되었다면 이벤트를 발생시킵니다
|
||||
if (item.OriginalIndex != newIndex)
|
||||
{
|
||||
OnOrderChanged?.Invoke(item.OriginalIndex, newIndex);
|
||||
Debug.Log($"[ListReorderHandler] 순서 변경됨: {item.OriginalIndex} -> {newIndex}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 플레이스홀더가 없는 경우 원래 위치로 복귀
|
||||
item.transform.SetParent(transform);
|
||||
}
|
||||
|
||||
currentDragging = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 주어진 스크린 좌표에서 가장 가까운 아이템의 인덱스를 계산합니다
|
||||
/// </summary>
|
||||
/// <param name="worldPosition">스크린 좌표</param>
|
||||
/// <returns>가장 가까운 인덱스, 찾지 못하면 -1</returns>
|
||||
private int CalculateTargetIndex(Vector3 worldPosition)
|
||||
{
|
||||
// 스크린 좌표를 로컬 좌표로 변환합니다
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
transform as RectTransform,
|
||||
worldPosition,
|
||||
null, // 오버레이 캔버스의 경우 카메라가 null
|
||||
out Vector2 localPos
|
||||
);
|
||||
|
||||
float closestDistance = float.MaxValue;
|
||||
int closestIndex = -1;
|
||||
|
||||
// 모든 아이템과의 거리를 계산하여 가장 가까운 것을 찾습니다
|
||||
var children = transform.GetChildren<RectTransform>();
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
// Y 좌표 차이만 계산합니다 (세로 리스트 기준)
|
||||
// 가로 리스트의 경우 localPos.x - children[i].anchoredPosition.x 를 사용하세요
|
||||
float distance = Mathf.Abs(localPos.y - (children[i].anchoredPosition.y + children[i].rect.height / 2));
|
||||
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
return closestIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 특정 인덱스의 아이템을 가져옵니다
|
||||
/// </summary>
|
||||
/// <param name="index">가져올 아이템의 인덱스</param>
|
||||
/// <returns>해당 인덱스의 Transform, 없으면 null</returns>
|
||||
public Transform? GetItemAtIndex(int index)
|
||||
{
|
||||
if (index >= 0 && index < transform.childCount)
|
||||
{
|
||||
return transform.GetChild(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 156b4800a1dadfb4c821381a5d6220d0
|
||||
172
Assets/Scripts/UVC/UI/List/Draggable/ScrollRectHandler.cs
Normal file
172
Assets/Scripts/UVC/UI/List/Draggable/ScrollRectHandler.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UVC.UI.List.Draggable
|
||||
{
|
||||
/// <summary>
|
||||
/// 드래그 중 화면 가장자리에서 자동으로 스크롤하는 기능을 제공합니다.
|
||||
/// ScrollRect와 함께 사용되어 긴 리스트에서도 드래그 앤 드롭이 가능하게 합니다.
|
||||
///
|
||||
/// 필수 설정:
|
||||
/// 1. ScrollRect 컴포넌트가 있는 GameObject에 추가합니다.
|
||||
///
|
||||
/// Inspector 설정:
|
||||
/// - Scroll Zone Height: 자동 스크롤이 시작되는 영역의 높이 (픽셀)
|
||||
/// - Scroll Speed: 스크롤 속도
|
||||
///
|
||||
/// 사용 예시:
|
||||
/// scrollHandler.EnableAutoScroll(true); // 자동 스크롤 시작
|
||||
/// scrollHandler.EnableAutoScroll(false); // 자동 스크롤 중지
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(ScrollRect))]
|
||||
public class ScrollRectHandler : MonoBehaviour
|
||||
{
|
||||
[Header("자동 스크롤 설정")]
|
||||
[SerializeField]
|
||||
[Tooltip("화면 가장자리에서 이 거리(픽셀) 안에 마우스가 있으면 자동 스크롤이 시작됩니다")]
|
||||
private float scrollZoneHeight = 50f;
|
||||
|
||||
[SerializeField]
|
||||
[Tooltip("자동 스크롤 속도 (픽셀/초)")]
|
||||
private float scrollSpeed = 600f;
|
||||
|
||||
// ScrollRect 컴포넌트 참조
|
||||
private ScrollRect? scrollRect;
|
||||
|
||||
// 자동 스크롤 코루틴 참조
|
||||
private Coroutine? autoScrollCoroutine;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트 초기화
|
||||
/// </summary>
|
||||
void Awake()
|
||||
{
|
||||
scrollRect = GetComponent<ScrollRect>();
|
||||
|
||||
if (scrollRect == null)
|
||||
{
|
||||
Debug.LogError($"[ScrollRectHandler] ScrollRect 컴포넌트를 찾을 수 없습니다! - {gameObject.name}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 자동 스크롤을 활성화하거나 비활성화합니다
|
||||
/// </summary>
|
||||
/// <param name="enable">true: 활성화, false: 비활성화</param>
|
||||
public void EnableAutoScroll(bool enable)
|
||||
{
|
||||
if (scrollRect == null) return;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
// 드래그 중에는 ScrollRect의 기본 드래그를 비활성화합니다
|
||||
scrollRect.enabled = false;
|
||||
|
||||
// 자동 스크롤 코루틴을 시작합니다
|
||||
if (autoScrollCoroutine == null)
|
||||
{
|
||||
autoScrollCoroutine = StartCoroutine(AutoScrollRoutine());
|
||||
Debug.Log("[ScrollRectHandler] 자동 스크롤 시작");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// ScrollRect를 다시 활성화합니다
|
||||
scrollRect.enabled = true;
|
||||
|
||||
// 자동 스크롤 코루틴을 중지합니다
|
||||
if (autoScrollCoroutine != null)
|
||||
{
|
||||
StopCoroutine(autoScrollCoroutine);
|
||||
autoScrollCoroutine = null;
|
||||
Debug.Log("[ScrollRectHandler] 자동 스크롤 중지");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 자동 스크롤을 처리하는 코루틴
|
||||
/// 마우스가 화면 가장자리에 있을 때 스크롤을 자동으로 움직입니다
|
||||
/// </summary>
|
||||
private IEnumerator AutoScrollRoutine()
|
||||
{
|
||||
if (scrollRect == null) yield break;
|
||||
|
||||
var scrollTransform = scrollRect.GetComponent<RectTransform>();
|
||||
if (scrollTransform == null) yield break;
|
||||
|
||||
// 스크롤 영역의 모서리 좌표를 저장할 배열
|
||||
Vector3[] corners = new Vector3[4];
|
||||
|
||||
while (true)
|
||||
{
|
||||
// ScrollRect의 월드 좌표 모서리를 가져옵니다
|
||||
// corners[0]: 왼쪽 아래, corners[1]: 왼쪽 위, corners[2]: 오른쪽 위, corners[3]: 오른쪽 아래
|
||||
scrollTransform.GetWorldCorners(corners);
|
||||
|
||||
float mouseY = Input.mousePosition.y;
|
||||
float topY = corners[1].y; // 위쪽 가장자리 Y 좌표
|
||||
float bottomY = corners[0].y; // 아래쪽 가장자리 Y 좌표
|
||||
float scrollDelta = 0f;
|
||||
|
||||
// 마우스가 위쪽 가장자리 근처에 있으면 위로 스크롤
|
||||
if (mouseY > topY - scrollZoneHeight && mouseY <= topY)
|
||||
{
|
||||
// 가장자리에 가까울수록 빠르게 스크롤
|
||||
float normalizedDistance = (topY - mouseY) / scrollZoneHeight;
|
||||
scrollDelta = scrollSpeed * (1f - normalizedDistance) * Time.deltaTime;
|
||||
|
||||
Debug.Log($"[ScrollRectHandler] 위로 스크롤 중... 속도: {scrollDelta}");
|
||||
}
|
||||
// 마우스가 아래쪽 가장자리 근처에 있으면 아래로 스크롤
|
||||
else if (mouseY < bottomY + scrollZoneHeight && mouseY >= bottomY)
|
||||
{
|
||||
// 가장자리에 가까울수록 빠르게 스크롤
|
||||
float normalizedDistance = (mouseY - bottomY) / scrollZoneHeight;
|
||||
scrollDelta = -scrollSpeed * (1f - normalizedDistance) * Time.deltaTime;
|
||||
|
||||
Debug.Log($"[ScrollRectHandler] 아래로 스크롤 중... 속도: {scrollDelta}");
|
||||
}
|
||||
|
||||
// 스크롤 적용
|
||||
if (Mathf.Abs(scrollDelta) > 0.01f)
|
||||
{
|
||||
// 스크롤 델타를 정규화된 값으로 변환 (0~1 범위)
|
||||
float normalized = scrollDelta / scrollRect.content.rect.height;
|
||||
|
||||
// 새로운 스크롤 위치 계산 (0~1 범위로 제한)
|
||||
scrollRect.verticalNormalizedPosition = Mathf.Clamp01(
|
||||
scrollRect.verticalNormalizedPosition + normalized
|
||||
);
|
||||
}
|
||||
|
||||
// 다음 프레임까지 대기
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 자동 스크롤이 활성화되어 있는지 확인합니다
|
||||
/// </summary>
|
||||
/// <returns>자동 스크롤이 활성화되어 있으면 true</returns>
|
||||
public bool IsAutoScrolling()
|
||||
{
|
||||
return autoScrollCoroutine != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트가 비활성화될 때 자동 스크롤을 중지합니다
|
||||
/// </summary>
|
||||
void OnDisable()
|
||||
{
|
||||
EnableAutoScroll(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3349b75020336074d9c7d2b5bf6a7796
|
||||
@@ -1,53 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UVC.UI.List
|
||||
{
|
||||
/// <summary>
|
||||
/// 개별 드래그 가능한 아이템의 UI 컴포넌트
|
||||
/// 드래그 동작과 시각적 피드백을 담당
|
||||
/// </summary>
|
||||
public class DraggableItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
||||
{
|
||||
// 이벤트
|
||||
public Action<PointerEventData>? OnBeginDragEvent;
|
||||
public Action<PointerEventData>? OnDragEvent;
|
||||
public Action<PointerEventData>? OnEndDragEvent;
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 시작 시 호출
|
||||
/// </summary>
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
OnBeginDragEvent?.Invoke(eventData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 중 호출
|
||||
/// </summary>
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
// 이벤트 발생
|
||||
OnDragEvent?.Invoke(eventData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 종료 시 호출
|
||||
/// </summary>
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
// 이벤트 발생
|
||||
OnEndDragEvent?.Invoke(eventData);
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
// 이벤트 구독 해제
|
||||
OnBeginDragEvent = null;
|
||||
OnDragEvent = null;
|
||||
OnEndDragEvent = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d93b757e3738184492e84c051530130
|
||||
@@ -1,42 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UVC.UI.List
|
||||
{
|
||||
/// <summary>
|
||||
/// 드래그 가능한 목록 아이템의 데이터 모델
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class DraggableItemData
|
||||
{
|
||||
[SerializeField] protected string id;
|
||||
[SerializeField] protected int sortOrder;
|
||||
|
||||
public string Id => id;
|
||||
public int SortOrder { get => sortOrder; set => sortOrder = value; }
|
||||
|
||||
public DraggableItemData(string id, int sortOrder = 0)
|
||||
{
|
||||
this.id = id ?? throw new ArgumentNullException(nameof(id));
|
||||
this.sortOrder = sortOrder;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 작업의 결과를 나타내는 이벤트 인자
|
||||
/// </summary>
|
||||
public class DraggableItemReorderEventArgs : EventArgs
|
||||
{
|
||||
public string ItemId { get; }
|
||||
public int OldIndex { get; }
|
||||
public int NewIndex { get; }
|
||||
|
||||
public DraggableItemReorderEventArgs(string itemId, int oldIndex, int newIndex)
|
||||
{
|
||||
ItemId = itemId;
|
||||
OldIndex = oldIndex;
|
||||
NewIndex = newIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a576b71abcd5ff41a1c3d0adf21a45c
|
||||
@@ -1,191 +0,0 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UVC.UI.List
|
||||
{
|
||||
/// <summary>
|
||||
/// 개별 드래그 가능한 아이템의 UI 컴포넌트
|
||||
/// 드래그 동작과 시각적 피드백을 담당
|
||||
/// </summary>
|
||||
public class DraggableListItem : MonoBehaviour
|
||||
{
|
||||
[Header("UI 컴포넌트")]
|
||||
[SerializeField] protected CanvasGroup? canvasGroup;
|
||||
[SerializeField] protected RectTransform? rectTransform;
|
||||
[SerializeField] protected DraggableItem? dragAnchor;
|
||||
[SerializeField] protected TMP_InputField? inputField;
|
||||
|
||||
[Header("드래그 설정")]
|
||||
[SerializeField] protected float dragAlpha = 0.6f;
|
||||
[SerializeField] protected bool blockRaycastsWhileDragging = false;
|
||||
|
||||
// 프로퍼티
|
||||
public DraggableItemData? Data { get; private set; }
|
||||
public RectTransform? RectTransform => rectTransform;
|
||||
public bool IsDragging { get; private set; }
|
||||
|
||||
public event Action<DraggableListItem>? OnBeginDragEvent;
|
||||
public event Action<DraggableListItem, Vector2>? OnDragEvent;
|
||||
public event Action<DraggableListItem>? OnEndDragEvent;
|
||||
|
||||
private Vector2 originalPosition;
|
||||
private Transform? originalParent;
|
||||
private int originalSiblingIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 컴포넌트 초기화
|
||||
/// </summary>
|
||||
private void Awake()
|
||||
{
|
||||
// null 체크 및 자동 할당
|
||||
if (rectTransform == null)
|
||||
rectTransform = GetComponent<RectTransform>();
|
||||
|
||||
if (canvasGroup == null)
|
||||
canvasGroup = GetComponent<CanvasGroup>();
|
||||
|
||||
// CanvasGroup이 없으면 추가
|
||||
if (canvasGroup == null)
|
||||
canvasGroup = gameObject.AddComponent<CanvasGroup>();
|
||||
|
||||
if (dragAnchor == null)
|
||||
{
|
||||
Debug.LogError("Drag Anchor is not assigned. Please assign it in the inspector.");
|
||||
return;
|
||||
}
|
||||
|
||||
dragAnchor.OnBeginDragEvent += OnBeginDrag;
|
||||
dragAnchor.OnDragEvent += OnDrag;
|
||||
dragAnchor.OnEndDragEvent += OnEndDrag;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 아이템 데이터로 UI 업데이트
|
||||
/// </summary>
|
||||
/// <param name="data">표시할 데이터</param>
|
||||
public void SetData(DraggableItemData? data)
|
||||
{
|
||||
if (data == null) return;
|
||||
|
||||
Data = data;
|
||||
UpdateUI();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI 요소들을 데이터에 맞게 업데이트
|
||||
/// </summary>
|
||||
protected virtual void UpdateUI()
|
||||
{
|
||||
if (Data == null) return;
|
||||
if(inputField != null)
|
||||
{
|
||||
inputField.text = Data.Id;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 시작 시 호출
|
||||
/// </summary>
|
||||
public void OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (rectTransform == null) return;
|
||||
|
||||
IsDragging = true;
|
||||
|
||||
// 원래 위치와 부모 저장
|
||||
originalPosition = rectTransform.anchoredPosition;
|
||||
originalParent = transform.parent;
|
||||
originalSiblingIndex = transform.GetSiblingIndex();
|
||||
|
||||
// 시각적 피드백 적용
|
||||
ApplyDragVisuals(true);
|
||||
|
||||
// 이벤트 발생
|
||||
OnBeginDragEvent?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 중 호출
|
||||
/// </summary>
|
||||
public void OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (rectTransform == null) return;
|
||||
|
||||
// 마우스 위치로 아이템 이동
|
||||
rectTransform.anchoredPosition += new Vector2(0, eventData.delta.y);//eventData.delta
|
||||
|
||||
// 이벤트 발생
|
||||
OnDragEvent?.Invoke(this, rectTransform.anchoredPosition);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 종료 시 호출
|
||||
/// </summary>
|
||||
public void OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
IsDragging = false;
|
||||
|
||||
// 시각적 피드백 복원
|
||||
ApplyDragVisuals(false);
|
||||
|
||||
// 이벤트 발생
|
||||
OnEndDragEvent?.Invoke(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 드래그 시각적 효과 적용/해제
|
||||
/// </summary>
|
||||
/// <param name="isDragging">드래그 중인지 여부</param>
|
||||
private void ApplyDragVisuals(bool isDragging)
|
||||
{
|
||||
if (canvasGroup == null) return;
|
||||
|
||||
if (isDragging)
|
||||
{
|
||||
canvasGroup.alpha = dragAlpha;
|
||||
canvasGroup.blocksRaycasts = blockRaycastsWhileDragging;
|
||||
}
|
||||
else
|
||||
{
|
||||
canvasGroup.alpha = 1f;
|
||||
canvasGroup.blocksRaycasts = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 원래 위치로 되돌리기 (드래그 취소 시 사용)
|
||||
/// </summary>
|
||||
public void ResetToOriginalPosition()
|
||||
{
|
||||
if (rectTransform == null || originalParent == null) return;
|
||||
|
||||
transform.SetParent(originalParent);
|
||||
transform.SetSiblingIndex(originalSiblingIndex);
|
||||
rectTransform.anchoredPosition = originalPosition;
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
// 이벤트 구독 해제
|
||||
if (dragAnchor != null)
|
||||
{
|
||||
dragAnchor.OnBeginDragEvent -= OnBeginDrag;
|
||||
dragAnchor.OnDragEvent -= OnDrag;
|
||||
dragAnchor.OnEndDragEvent -= OnEndDrag;
|
||||
}
|
||||
|
||||
OnBeginDragEvent = null;
|
||||
OnDragEvent = null;
|
||||
OnEndDragEvent = null;
|
||||
|
||||
// 리소스 정리
|
||||
canvasGroup = null;
|
||||
rectTransform = null;
|
||||
dragAnchor = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cdfe032ad5874e4cbc571f344516b93
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 71e6121c6103b0a4c9aeadc24c891b86
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UVC.Data.Core;
|
||||
using UVC.Factory.Component;
|
||||
using UVC.Factory.Modal.Config;
|
||||
using UVC.Factory.Playback;
|
||||
using UVC.Locale;
|
||||
using UVC.Log;
|
||||
@@ -241,16 +242,10 @@ namespace UVC.UI.Menu
|
||||
ULog.Debug($"사용자가 네트워크 오류 알림을 확인했나요? {result}");
|
||||
}))
|
||||
}));
|
||||
model.MenuItems.Add(new MenuItemData("language", "menu_language", subMenuItems: new List<MenuItemData>
|
||||
|
||||
model.MenuItems.Add(new MenuItemData("Settings", "Settings", subMenuItems: new List<MenuItemData>
|
||||
{
|
||||
// 각 언어 메뉴 아이템에 ChangeLanguageCommand를 연결하여 언어 변경 기능 수행
|
||||
new MenuItemData("lang_ko", "menu_lang_korean", new ChangeLanguageCommand("ko"), commandParameter: "ko"),
|
||||
new MenuItemData("lang_en", "menu_lang_english", new ChangeLanguageCommand("en")),
|
||||
new MenuItemData("file_save", "menu_file_save", command: new DebugLogCommand("저장 선택됨 (Command 실행)") , subMenuItems: new List<MenuItemData>
|
||||
{
|
||||
new MenuItemData("file_save_as", "menu_file_save_as", new DebugLogCommand("다른 이름으로 저장 선택됨 (Command 실행)"))
|
||||
}),
|
||||
// 필요에 따라 다른 언어들도 추가 가능
|
||||
new MenuItemData("info", "info", new ConfigDataOrderCommand()),
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -372,12 +372,12 @@ namespace UVC.UI.Modal
|
||||
}
|
||||
else // bool이 아닌 다른 타입의 경우 ModalView.GetResult() 사용
|
||||
{
|
||||
object resultFromView = null;
|
||||
object? resultFromView = null;
|
||||
// modalViewToClose는 파괴되었을 수도 있으므로, modalInstanceToDestroy에서 다시 가져오거나 null 체크 강화
|
||||
ModalView viewForGetResult = modalInstanceToDestroy?.GetComponent<ModalView>() ?? modalViewToClose;
|
||||
if (viewForGetResult != null)
|
||||
{
|
||||
resultFromView = viewForGetResult.GetResult();
|
||||
resultFromView = await viewForGetResult.GetResult();
|
||||
}
|
||||
|
||||
// 📜 이야기: 이제 가져온 결과를 '약속 증서'에 적어야 하는데, 타입이 맞는지 잘 확인해야 해요.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UVC.Locale;
|
||||
using UVC.Log; // LocalizationManager 사용을 위해 추가
|
||||
|
||||
namespace UVC.UI.Modal
|
||||
{
|
||||
@@ -124,7 +123,7 @@ namespace UVC.UI.Modal
|
||||
PrefabPath = prefabPath;
|
||||
// Title, Message 등 다른 텍스트 속성도 필요한 경우 여기서 기본 다국어 키를 사용하여 초기화할 수 있습니다.
|
||||
// 예: Title = LocalizationManager.Instance.GetString("default_modal_title");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 🚀 모달 창이 화면에 나타나기 *직전*에 이 레시피가 할 일을 정해요. (비동기 작업 가능)
|
||||
|
||||
@@ -71,32 +71,41 @@ namespace UVC.UI.Modal
|
||||
/// 🏷️ 모달 창의 제목을 보여줄 글상자(TextMeshProUGUI)예요.
|
||||
/// Unity 에디터의 인스펙터 창에서 실제 UI 요소를 끌어다 연결해줘야 해요.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public TextMeshProUGUI titleText;
|
||||
/// <summary>
|
||||
/// 💬 모달 창의 주요 메시지를 보여줄 글상자예요. 이것도 연결해주세요!
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public TextMeshProUGUI messageText;
|
||||
/// <summary>
|
||||
/// ✅ '확인' 버튼이에요. 연결 필수!
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public Button confirmButton;
|
||||
/// <summary>
|
||||
/// 확인 버튼 안에 있는 글상자예요. 확인 버튼 글자를 바꿀 때 사용돼요.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public TextMeshProUGUI confirmButtonText;
|
||||
/// <summary>
|
||||
/// ❌ '취소' 버튼이에요. 이것도 연결해주세요!
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public Button cancelButton;
|
||||
/// <summary>
|
||||
/// 취소 버튼 안에 있는 글상자예요. 취소 버튼 글자를 바꿀 때 사용돼요.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public TextMeshProUGUI cancelButtonText;
|
||||
/// <summary>
|
||||
/// ✖️ 모달 창을 닫는 (보통 오른쪽 위에 있는 X 모양) 버튼이에요.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public Button closeButton; // 닫기 버튼
|
||||
|
||||
protected bool autoPositionButtons = true; // 버튼 위치 자동 조정 여부 (기본값: true)
|
||||
|
||||
// 필요에 따라 다른 UI 요소들을 추가할 수 있습니다.
|
||||
// 예: public Image backgroundImage;
|
||||
// 예: public InputField inputField;
|
||||
@@ -149,7 +158,7 @@ namespace UVC.UI.Modal
|
||||
|
||||
|
||||
// 버튼 위치를 예쁘게 조정해요 (예: 버튼이 하나만 있으면 가운데로).
|
||||
AdjustButtonPositions();
|
||||
if(autoPositionButtons) AdjustButtonPositions();
|
||||
await UniTask.CompletedTask; // 비동기 메서드라서 마지막에 이걸 붙여줘요.
|
||||
}
|
||||
|
||||
@@ -253,7 +262,7 @@ namespace UVC.UI.Modal
|
||||
/// public TMP_InputField inputField;
|
||||
/// // (OnOpen에서 inputField 초기화 및 이벤트 연결)
|
||||
///
|
||||
/// public override object GetResult()
|
||||
/// public override UniTask<object?> GetResult()
|
||||
/// {
|
||||
/// return inputField != null ? inputField.text : string.Empty;
|
||||
/// }
|
||||
@@ -261,9 +270,9 @@ namespace UVC.UI.Modal
|
||||
/// // Modal.Open<string>(...) 이렇게 호출하면, 입력된 문자열을 받을 수 있어요.
|
||||
/// </code>
|
||||
/// </example>
|
||||
public virtual object? GetResult()
|
||||
public virtual UniTask<object?> GetResult()
|
||||
{
|
||||
return null;
|
||||
return UniTask.FromResult<object?>(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user