Files
XRLib/Assets/Scripts/UVC/UI/List/PrefabGridItem.cs

228 lines
8.6 KiB
C#
Raw Normal View History

#nullable enable
2025-09-26 18:08:07 +09:00
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
2025-09-26 18:08:07 +09:00
{
[SerializeField]
private Image image;
[SerializeField]
private TextMeshProUGUI text;
// Drag state
private Canvas _canvas = default!;
private Image? _dragGhost;
2025-12-08 21:06:05 +09:00
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>();
}
}
2025-12-08 21:06:05 +09:00
// PrefabGrid 참조 캐싱
if (_prefabGrid == null)
{
_prefabGrid = GetComponentInParent<PrefabGrid>();
}
}
2025-09-26 18:08:07 +09:00
/// <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,
2025-10-28 20:10:51 +09:00
/// Name = "분전반",
/// Option = "옵션 A",
2025-09-26 18:08:07 +09:00
/// 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;
2025-09-26 18:08:07 +09:00
}
public void OnClick()
{
2025-12-08 21:06:05 +09:00
if (_boundData == null || _prefabGrid == null) return;
_prefabGrid.HandleItemClick(_boundData);
2025-09-26 18:08:07 +09:00
}
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>
2025-12-08 21:06:05 +09:00
/// 드래그 종료 시 호출됩니다. 고스트를 제거하고 드랍 이벤트를 발생시킵니다.
/// </summary>
/// <param name="eventData">포인터 이벤트 데이터.</param>
public void OnEndDrag(PointerEventData eventData)
{
// 고스트 제거
if (_dragGhost != null)
{
Destroy(_dragGhost.gameObject);
_dragGhost = null;
}
2025-12-08 21:06:05 +09:00
// PrefabGrid의 이벤트를 통해 드롭 처리
if (_boundData != null && _prefabGrid != null)
{
_prefabGrid.HandleItemEndDrag(_boundData, eventData.position);
2025-12-08 21:06:05 +09:00
_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()
{
2025-12-08 21:06:05 +09:00
// 드래그 고스트 정리
if (_dragGhost != null)
{
Destroy(_dragGhost.gameObject);
_dragGhost = null;
}
2025-12-08 21:06:05 +09:00
// 참조 정리
_boundData = null;
_prefabGrid = null;
}
2025-09-26 18:08:07 +09:00
}
}