Files
XRLib/Assets/Scripts/SHI/modal/HorizontalSplitDrag.cs
2025-11-12 16:48:34 +09:00

129 lines
5.3 KiB
C#

#nullable enable
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace SHI.modal
{
/// <summary>
/// 두 개의 RectTransform 가로 분할을 드래그 버튼으로 조절하는 간단한 스플리터.
/// 레이아웃 그룹(수평) + LayoutElement.flexibleWidth 기반으로 동작합니다.
/// </summary>
public class HorizontalSplitDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
private RectTransform _left;
private RectTransform _right;
private LayoutElement _leftLayout;
private LayoutElement _rightLayout;
private RectTransform _parent;
private RectTransform _handleRect; // this splitter's rect
private float _parentWidth; // cached on begin drag
private float _handleY = 42; // keep original y
private RectTransform _leftFixedPanel; // e.g., ModelDetailListView root
private bool _lastLeftPanelActive;
private float _lastParentWidth;
public void Initialize(RectTransform left, RectTransform right, RectTransform? leftFixedPanel = null)
{
_left = left;
_right = right;
_leftFixedPanel = leftFixedPanel;
_parent = left != null ? left.parent as RectTransform : null;
_handleRect = transform as RectTransform;
_leftLayout = _left.GetComponent<LayoutElement>();
if (_leftLayout == null) _leftLayout = _left.gameObject.AddComponent<LayoutElement>();
_rightLayout = _right.GetComponent<LayoutElement>();
if (_rightLayout == null) _rightLayout = _right.gameObject.AddComponent<LayoutElement>();
_lastLeftPanelActive = _leftFixedPanel != null && _leftFixedPanel.gameObject.activeInHierarchy;
_lastParentWidth = _parent != null ? _parent.rect.width : 0f;
RefreshPosition();
}
public void OnBeginDrag(PointerEventData eventData)
{
if (_parent != null) _parentWidth = _parent.rect.width;
if (_handleRect != null) _handleY = _handleRect.anchoredPosition.y;
}
public void OnDrag(PointerEventData eventData)
{
if (_parent == null) return;
Vector2 local;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(_parent, eventData.position, eventData.pressEventCamera, out local))
return;
float width = _parent.rect.width;
if (width <= 0f) return;
// 계산 범위: 좌측 고정 패널(보이는 경우)의 폭만큼 좌측 경계를 오른쪽으로 이동
float leftOffset = 0f;
if (_leftFixedPanel != null && _leftFixedPanel.gameObject.activeSelf)
{
leftOffset = _leftFixedPanel.rect.width;
}
float minX = -width * 0.5f + leftOffset; // 사용 가능한 작업 영역의 좌측 경계
float maxX = width * 0.5f; // 우측 경계
// 현재 포인터 위치를 작업 영역 비율[0..1]로 변환 후 범위 제한
float t = Mathf.InverseLerp(minX, maxX, local.x);
t = Mathf.Clamp01(t);
// LayoutElement 비율 (양 끝 과도값 방지하여10%~90% 사이 유지)
float leftWeight = Mathf.Clamp(t, 0.1f, 0.9f);
float rightWeight = 1f - leftWeight;
_leftLayout.flexibleWidth = leftWeight;
_rightLayout.flexibleWidth = rightWeight;
// 스플리터 핸들도 같은 좌표계에서 이동
if (_handleRect != null)
{
float clampedX = Mathf.Lerp(minX, maxX, leftWeight);
_handleRect.anchoredPosition = new Vector2(clampedX, _handleY);
}
}
// 외부에서 강제로 현재 레이아웃 기준으로 핸들 위치를 동기화합니다.
public void RefreshPosition()
{
if (_parent == null || _handleRect == null || _leftLayout == null || _rightLayout == null)
return;
float width = _parent.rect.width;
if (width <= 0f) return;
float leftOffset = 0f;
if (_leftFixedPanel != null && _leftFixedPanel.gameObject.activeSelf)
leftOffset = _leftFixedPanel.rect.width;
float minX = -width * 0.5f + leftOffset;
float maxX = width * 0.5f;
float totalFlex = Mathf.Max(0.0001f, _leftLayout.flexibleWidth + _rightLayout.flexibleWidth);
float leftWeight = Mathf.Clamp01(_leftLayout.flexibleWidth / totalFlex);
leftWeight = Mathf.Clamp(leftWeight, 0.1f, 0.9f);
if (_handleRect != null)
{
_handleRect.anchoredPosition = new Vector2(Mathf.Lerp(minX, maxX, leftWeight), _handleY);
}
}
private void LateUpdate()
{
bool nowActive = _leftFixedPanel != null && _leftFixedPanel.gameObject.activeInHierarchy;
float nowWidth = _parent != null ? _parent.rect.width : 0f;
if (nowActive != _lastLeftPanelActive || !Mathf.Approximately(nowWidth, _lastParentWidth))
{
_lastLeftPanelActive = nowActive;
_lastParentWidth = nowWidth;
RefreshPosition();
}
}
public void OnEndDrag(PointerEventData eventData) { }
}
}