Files
XRLib/Assets/Scripts/SHI/modal/NW/VerticalDragManipulator.cs
2025-11-27 19:26:01 +09:00

157 lines
6.2 KiB
C#

using UnityEngine;
using UnityEngine.UIElements;
namespace SHI.Modal.NW
{
/// <summary>
/// UI 요소를 수직(Y축)으로만 드래그할 수 있게 해주는 Manipulator입니다.
///
/// <para><b>개요:</b></para>
/// <para>
/// Unity UI Toolkit의 PointerManipulator를 상속하여 구현된 커스텀 드래그 핸들러입니다.
/// 좌클릭으로 드래그를 시작하며, Y축 방향으로만 요소를 이동시킵니다.
/// NWModal에서 모델 뷰와 차트 뷰 사이의 구분선(드래그 버튼)에 사용됩니다.
/// </para>
///
/// <para><b>동작 방식:</b></para>
/// <list type="number">
/// <item>드래그 시작 시 요소를 Absolute 포지션으로 변경</item>
/// <item>마우스 이동에 따라 Y 좌표만 업데이트</item>
/// <item>X 좌표는 고정 유지</item>
/// </list>
///
/// <para><b>사용 예시:</b></para>
/// <code>
/// var dragButton = root.Q&lt;Button&gt;("drag-btn");
/// dragButton.AddManipulator(new VerticalDragManipulator());
/// </code>
///
/// <para><b>관련 클래스:</b></para>
/// <list type="bullet">
/// <item>HorizontalDragManipulator - 수평 드래그용 (ISOP 모달에서 사용)</item>
/// </list>
/// </summary>
public class VerticalDragManipulator : PointerManipulator
{
#region (Drag State)
/// <summary>현재 드래그 중인지 여부</summary>
private bool _isActive;
/// <summary>드래그 시작 시점의 포인터 위치 (화면 좌표)</summary>
private Vector3 _startPointerPosition;
/// <summary>드래그 시작 시점의 요소 top 스타일 값</summary>
private float _startTopStyle;
#endregion
#region (Constructor)
/// <summary>
/// VerticalDragManipulator를 초기화합니다.
/// 좌클릭(MouseButton.LeftMouse)을 드래그 활성화 버튼으로 설정합니다.
/// </summary>
public VerticalDragManipulator()
{
_isActive = false;
// 좌클릭으로 드래그 활성화
activators.Add(new ManipulatorActivationFilter { button = MouseButton.LeftMouse });
}
#endregion
#region / (Event Registration)
/// <summary>
/// 타겟 요소에 포인터 이벤트 콜백을 등록합니다.
/// AddManipulator() 호출 시 자동으로 실행됩니다.
/// </summary>
protected override void RegisterCallbacksOnTarget()
{
// TrickleDown: 부모에서 자식 방향으로 이벤트 전파 (캡처 단계)
target.RegisterCallback<PointerDownEvent>(OnPointerDown, TrickleDown.TrickleDown);
target.RegisterCallback<PointerMoveEvent>(OnPointerMove, TrickleDown.TrickleDown);
target.RegisterCallback<PointerUpEvent>(OnPointerUp, TrickleDown.TrickleDown);
}
/// <summary>
/// 타겟 요소에서 포인터 이벤트 콜백을 해제합니다.
/// RemoveManipulator() 호출 시 자동으로 실행됩니다.
/// </summary>
protected override void UnregisterCallbacksFromTarget()
{
target.UnregisterCallback<PointerDownEvent>(OnPointerDown);
target.UnregisterCallback<PointerMoveEvent>(OnPointerMove);
target.UnregisterCallback<PointerUpEvent>(OnPointerUp);
}
#endregion
#region (Pointer Event Handlers)
/// <summary>
/// 포인터 다운(클릭) 이벤트를 처리합니다.
/// 드래그를 시작하고 포인터를 캡처합니다.
/// </summary>
/// <param name="evt">포인터 다운 이벤트</param>
private void OnPointerDown(PointerDownEvent evt)
{
// 이미 드래그 중이면 무시
if (_isActive) return;
// 요소를 Absolute 포지션으로 변경하여 레이아웃 흐름에서 분리
// 이렇게 해야 top 스타일로 위치를 직접 제어할 수 있음
if (target.style.position != Position.Absolute)
{
// 현재 레이아웃 위치를 Absolute 좌표로 변환 (점프 방지)
target.style.left = target.layout.x;
target.style.top = target.layout.y;
target.style.position = Position.Absolute;
}
_isActive = true;
// 포인터 캡처: 마우스가 요소 밖으로 나가도 이벤트 수신
target.CapturePointer(evt.pointerId);
// 시작 위치 저장
_startPointerPosition = evt.position;
_startTopStyle = target.layout.y;
// 이벤트 버블링 중단 (부모로 전파 방지)
evt.StopPropagation();
}
/// <summary>
/// 포인터 이동 이벤트를 처리합니다.
/// 드래그 중일 때 요소의 Y 좌표를 업데이트합니다.
/// </summary>
/// <param name="evt">포인터 이동 이벤트</param>
private void OnPointerMove(PointerMoveEvent evt)
{
// 드래그 중이 아니거나 포인터 캡처가 없으면 무시
if (!_isActive || !target.HasPointerCapture(evt.pointerId)) return;
// 이동 거리 계산 (시작 위치 기준)
Vector3 delta = evt.position - _startPointerPosition;
// 새 Y 좌표 계산 (시작 위치 + 이동량)
float newY = _startTopStyle + delta.y;
// Y 좌표만 업데이트 (X축은 고정)
target.style.top = newY;
}
/// <summary>
/// 포인터 업(릴리즈) 이벤트를 처리합니다.
/// 드래그를 종료하고 포인터 캡처를 해제합니다.
/// </summary>
/// <param name="evt">포인터 업 이벤트</param>
private void OnPointerUp(PointerUpEvent evt)
{
if (_isActive)
{
_isActive = false;
// 포인터 캡처 해제
target.ReleasePointer(evt.pointerId);
evt.StopPropagation();
}
}
#endregion
}
}