#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 { /// /// 드래그 중 화면 가장자리에서 자동으로 스크롤하는 기능을 제공합니다. /// ScrollRect와 함께 사용되어 긴 리스트에서도 드래그 앤 드롭이 가능하게 합니다. /// /// 필수 설정: /// 1. ScrollRect 컴포넌트가 있는 GameObject에 추가합니다. /// /// Inspector 설정: /// - Scroll Zone Height: 자동 스크롤이 시작되는 영역의 높이 (픽셀) /// - Scroll Speed: 스크롤 속도 /// /// 사용 예시: /// scrollHandler.EnableAutoScroll(true); // 자동 스크롤 시작 /// scrollHandler.EnableAutoScroll(false); // 자동 스크롤 중지 /// [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; /// /// 컴포넌트 초기화 /// void Awake() { scrollRect = GetComponent(); if (scrollRect == null) { Debug.LogError($"[ScrollRectHandler] ScrollRect 컴포넌트를 찾을 수 없습니다! - {gameObject.name}"); } } /// /// 자동 스크롤을 활성화하거나 비활성화합니다 /// /// true: 활성화, false: 비활성화 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] 자동 스크롤 중지"); } } } /// /// 자동 스크롤을 처리하는 코루틴 /// 마우스가 화면 가장자리에 있을 때 스크롤을 자동으로 움직입니다 /// private IEnumerator AutoScrollRoutine() { if (scrollRect == null) yield break; var scrollTransform = scrollRect.GetComponent(); 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; } } /// /// 현재 자동 스크롤이 활성화되어 있는지 확인합니다 /// /// 자동 스크롤이 활성화되어 있으면 true public bool IsAutoScrolling() { return autoScrollCoroutine != null; } /// /// 컴포넌트가 비활성화될 때 자동 스크롤을 중지합니다 /// void OnDisable() { EnableAutoScroll(false); } } }