228 lines
8.6 KiB
C#
228 lines
8.6 KiB
C#
#nullable enable
|
|
using Gpm.Ui;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
using UVC.Util;
|
|
|
|
namespace UVC.UI.List
|
|
{
|
|
public class PrefabGridItem : InfiniteScrollItem, IPointerEnterHandler, IPointerExitHandler, IBeginDragHandler, IDragHandler, IEndDragHandler
|
|
{
|
|
[SerializeField]
|
|
private Image image;
|
|
|
|
[SerializeField]
|
|
private TextMeshProUGUI text;
|
|
|
|
// Drag state
|
|
private Canvas _canvas = default!;
|
|
private Image? _dragGhost;
|
|
private PrefabGridItemData? _boundData;
|
|
private PrefabGrid? _prefabGrid;
|
|
|
|
private void Awake()
|
|
{
|
|
// cache canvas for drag visuals
|
|
if (_canvas == null)
|
|
{
|
|
_canvas = GetComponentInParent<Canvas>();
|
|
if (_canvas == null)
|
|
{
|
|
// 최상위 Canvas가 없다면 씬에서 하나를 찾아 사용
|
|
_canvas = FindFirstObjectByType<Canvas>();
|
|
}
|
|
}
|
|
|
|
// PrefabGrid 참조 캐싱
|
|
if (_prefabGrid == null)
|
|
{
|
|
_prefabGrid = GetComponentInParent<PrefabGrid>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// InfiniteScroll에 의해 데이터가 이 아이템에 할당될 때 호출되는 핵심 메서드입니다.
|
|
/// 스크롤 시 아이템이 재활용될 때마다 새로운 데이터로 이 메서드가 호출되어 UI를 갱신합니다.
|
|
/// </summary>
|
|
/// <param name="scrollData">이 아이템에 표시할 데이터입니다. `InfiniteScrollData`를 상속받은 `ComponentListItemData` 객체입니다.</param>
|
|
/// <example>
|
|
/// <code>
|
|
/// // 1. 표시할 데이터를 생성합니다.
|
|
/// var categoryData = new ComponentListItemData
|
|
/// {
|
|
/// isCategory = true,
|
|
/// categoryName = "전기 설비",
|
|
/// categoryBadgeCount = 10
|
|
/// };
|
|
///
|
|
/// var generalData = new ComponentListItemData
|
|
/// {
|
|
/// isCategory = false,
|
|
/// Name = "분전반",
|
|
/// Option = "옵션 A",
|
|
/// factoryObjectInfo = new FactoryObjectInfo { Id = "some-unique-Id", Name = "분전반" }
|
|
/// };
|
|
///
|
|
/// // 2. 생성한 데이터를 InfiniteScroll에 추가합니다.
|
|
/// // infiniteScroll은 InfiniteScroll 컴포넌트의 인스턴스입니다.
|
|
/// infiniteScroll.InsertData(categoryData);
|
|
/// infiniteScroll.InsertData(generalData);
|
|
///
|
|
/// // 위 코드가 실행되면, InfiniteScroll은 각 데이터에 맞는 ComponentListItem을 생성(또는 재활용)하고,
|
|
/// // 이 UpdateData 메서드를 호출하여 UI를 데이터에 맞게 설정합니다.
|
|
/// </code>
|
|
/// </example>
|
|
public override async void UpdateData(InfiniteScrollData scrollData)
|
|
{
|
|
// 1. 부모 클래스의 UpdateData를 호출하여 기본 초기화 작업을 수행합니다.
|
|
// 이 과정에서 this.scrollData에 매개변수로 받은 scrollData가 할당됩니다.
|
|
base.UpdateData(scrollData);
|
|
|
|
PrefabGridItemData data = (PrefabGridItemData)scrollData;
|
|
|
|
text.text = data.ItemName;
|
|
image.sprite = await ResourceManager.LoadSpriteAsync(data.ImagePrefabPath);
|
|
|
|
_boundData = data;
|
|
}
|
|
|
|
public void OnClick()
|
|
{
|
|
if (_boundData == null || _prefabGrid == null) return;
|
|
_prefabGrid.HandleItemClick(_boundData);
|
|
}
|
|
|
|
public void OnPointerEnter(PointerEventData eventData)
|
|
{
|
|
CursorManager.Instance.SetCursor(CursorType.HandPoint);
|
|
}
|
|
|
|
public void OnPointerExit(PointerEventData eventData)
|
|
{
|
|
CursorManager.Instance.SetCursor(CursorType.Default);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 시작 시 호출됩니다. 드래그 고스트 이미지를 생성하고 초기 위치를 설정합니다.
|
|
/// </summary>
|
|
/// <param name="eventData">포인터 이벤트 데이터.</param>
|
|
public void OnBeginDrag(PointerEventData eventData)
|
|
{
|
|
if (image == null || image.sprite == null || _canvas == null) return;
|
|
|
|
// PrefabGrid의 이벤트를 통해 드래그 시작 처리
|
|
if (_boundData != null && _prefabGrid != null)
|
|
{
|
|
_prefabGrid.HandleItemBeginDrag(_boundData, eventData.position);
|
|
}
|
|
|
|
// DragImageFollowCursor가 true일 때만 고스트 이미지 생성
|
|
if (_prefabGrid != null && _prefabGrid.DragImageFollowCursor)
|
|
{
|
|
// 고스트 생성 (이미지 스프라이트 복사)
|
|
var go = new GameObject("DragGhost", typeof(RectTransform), typeof(CanvasGroup), typeof(Image));
|
|
go.transform.SetParent(_canvas.transform, false);
|
|
go.transform.SetAsLastSibling();
|
|
var ghostImg = go.GetComponent<Image>();
|
|
ghostImg.sprite = image.sprite;
|
|
ghostImg.preserveAspect = true;
|
|
ghostImg.raycastTarget = false; // 드래그 중에는 이벤트 차단하지 않도록
|
|
ghostImg.color = new Color(1f, 1f, 1f, 0.6f);
|
|
|
|
// 크기/위치 초기화
|
|
var srcRT = image.rectTransform;
|
|
var dstRT = (RectTransform)go.transform;
|
|
dstRT.sizeDelta = srcRT.rect.size;
|
|
SetGhostPosition(eventData, dstRT);
|
|
|
|
_dragGhost = ghostImg;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 중에 호출됩니다. 고스트 이미지를 현재 포인터 위치로 이동합니다.
|
|
/// </summary>
|
|
/// <param name="eventData">포인터 이벤트 데이터.</param>
|
|
public void OnDrag(PointerEventData eventData)
|
|
{
|
|
// PrefabGrid의 이벤트를 통해 드래그 중 처리
|
|
if (_boundData != null && _prefabGrid != null)
|
|
{
|
|
_prefabGrid.HandleItemDrag(_boundData, eventData.position);
|
|
}
|
|
|
|
if (_dragGhost == null || _canvas == null) return;
|
|
SetGhostPosition(eventData, (RectTransform)_dragGhost.transform);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 드래그 종료 시 호출됩니다. 고스트를 제거하고 드랍 이벤트를 발생시킵니다.
|
|
/// </summary>
|
|
/// <param name="eventData">포인터 이벤트 데이터.</param>
|
|
public void OnEndDrag(PointerEventData eventData)
|
|
{
|
|
// 고스트 제거
|
|
if (_dragGhost != null)
|
|
{
|
|
Destroy(_dragGhost.gameObject);
|
|
_dragGhost = null;
|
|
}
|
|
|
|
// PrefabGrid의 이벤트를 통해 드롭 처리
|
|
if (_boundData != null && _prefabGrid != null)
|
|
{
|
|
_prefabGrid.HandleItemEndDrag(_boundData, eventData.position);
|
|
_prefabGrid.HandleItemDrop(_boundData);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 고스트 RectTransform을 현재 포인터 위치로 배치합니다.
|
|
/// Canvas의 렌더 모드에 따라 화면/월드 좌표를 변환합니다.
|
|
/// </summary>
|
|
/// <param name="eventData">포인터 이벤트 데이터.</param>
|
|
/// <param name="ghostRt">이동시킬 고스트 RectTransform.</param>
|
|
private void SetGhostPosition(PointerEventData eventData, RectTransform ghostRt)
|
|
{
|
|
if (_canvas == null)
|
|
return;
|
|
|
|
if (_canvas.renderMode == RenderMode.ScreenSpaceOverlay)
|
|
{
|
|
ghostRt.position = eventData.position;
|
|
}
|
|
else
|
|
{
|
|
var cam = _canvas.worldCamera != null ? _canvas.worldCamera : eventData.pressEventCamera;
|
|
if (RectTransformUtility.ScreenPointToWorldPointInRectangle(_canvas.transform as RectTransform, eventData.position, cam, out var worldPos))
|
|
{
|
|
ghostRt.position = worldPos;
|
|
}
|
|
else
|
|
{
|
|
ghostRt.position = eventData.position; // fallback
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 종료 시 등록된 콜백과 드래그 고스트를 정리합니다.
|
|
/// </summary>
|
|
private void OnDestroy()
|
|
{
|
|
// 드래그 고스트 정리
|
|
if (_dragGhost != null)
|
|
{
|
|
Destroy(_dragGhost.gameObject);
|
|
_dragGhost = null;
|
|
}
|
|
|
|
// 참조 정리
|
|
_boundData = null;
|
|
_prefabGrid = null;
|
|
}
|
|
}
|
|
}
|