173 lines
8.0 KiB
C#
173 lines
8.0 KiB
C#
#nullable enable
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
using UnityEngine.UI;
|
|
|
|
namespace SHI.modal
|
|
{
|
|
/// <summary>
|
|
/// 수평 레이아웃에서 두 패널의 가중치(LayoutElement.flexibleWidth)를 드래그 핸들로 조절하는 간단한 분할기입니다.
|
|
/// </summary>
|
|
public class HorizontalSplitDrag : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
|
|
{
|
|
[SerializeField, Range(0.0f, 0.5f)] private float minLeftWeight = 0.1f;
|
|
[SerializeField, Range(0.5f, 1.0f)] private float maxLeftWeight = 0.9f;
|
|
[SerializeField] private bool useActualWidthForHandle = true; // 실제 패널 폭 기준 위치 보정
|
|
|
|
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;
|
|
|
|
/// <summary>
|
|
/// 좌/우 패널을 지정하고(필요 시 좌측 고정 패널 포함) 분할기를 초기화합니다.
|
|
/// </summary>
|
|
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 != null ? _left.GetComponent<LayoutElement>() : null;
|
|
if (_left != null && _leftLayout == null) _leftLayout = _left.gameObject.AddComponent<LayoutElement>();
|
|
_rightLayout = _right != null ? _right.GetComponent<LayoutElement>() : null;
|
|
if (_right != null && _rightLayout == null) _rightLayout = _right.gameObject.AddComponent<LayoutElement>();
|
|
|
|
_lastLeftPanelActive = _leftFixedPanel != null && _leftFixedPanel.gameObject.activeInHierarchy;
|
|
_lastParentWidth = _parent != null ? _parent.rect.width : 0f;
|
|
RefreshPosition();
|
|
}
|
|
|
|
|
|
/// <inheritdoc />
|
|
public void OnBeginDrag(PointerEventData eventData)
|
|
{
|
|
if (_parent != null) _parentWidth = _parent.rect.width;
|
|
if (_handleRect != null) _handleY = _handleRect.anchoredPosition.y;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 부모 RectTransform pivot을 고려한 작업 영역 좌/우 경계 계산.
|
|
/// </summary>
|
|
private void GetWorkArea(out float minX, out float maxX, out float leftOffset)
|
|
{
|
|
minX = maxX = leftOffset = 0f;
|
|
if (_parent == null) return;
|
|
float width = _parent.rect.width;
|
|
var pivot = _parent.pivot; // (0..1)
|
|
float pivotOrigin = -width * pivot.x; // local 좌표계에서 좌측 경계
|
|
leftOffset = 0f;
|
|
if (_leftFixedPanel != null && _leftFixedPanel.gameObject.activeSelf)
|
|
leftOffset = _leftFixedPanel.rect.width; // 고정 패널 폭만큼 이동
|
|
minX = pivotOrigin + leftOffset;
|
|
maxX = pivotOrigin + width; // 우측 경계 (handle 중심 좌표)
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void OnDrag(PointerEventData eventData)
|
|
{
|
|
if (_parent == null || _leftLayout == null || _rightLayout == 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 minX, maxX, leftOffset;
|
|
GetWorkArea(out minX, out maxX, out leftOffset);
|
|
|
|
// 드래그 포인터를 작업 영역으로 정규화하여 [0..1] 비율 t 산출
|
|
float t = Mathf.InverseLerp(minX, maxX, local.x);
|
|
t = Mathf.Clamp01(t);
|
|
|
|
// 가변 비율 계산 (minLeftWeight~maxLeftWeight 범위)
|
|
float leftWeight = Mathf.Lerp(minLeftWeight, maxLeftWeight, t);
|
|
float rightWeight = Mathf.Max(0.0001f, 1f - leftWeight);
|
|
_leftLayout.flexibleWidth = leftWeight;
|
|
_rightLayout.flexibleWidth = rightWeight;
|
|
|
|
// 레이아웃 즉시 반영 후 실제 폭 기준 위치 재계산
|
|
Canvas.ForceUpdateCanvases();
|
|
UpdateHandlePositionFromActualWidths(minX, maxX);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 실제 패널 폭을 사용해 핸들 위치를 경계선(왼쪽 패널 오른쪽 끝)에 정렬.
|
|
/// 레이아웃 그룹 padding/spacing, 고정 패널 폭을 모두 반영.
|
|
/// </summary>
|
|
private void UpdateHandlePositionFromActualWidths(float minX, float maxX)
|
|
{
|
|
if (_handleRect == null || _parent == null || _left == null) return;
|
|
if (!useActualWidthForHandle)
|
|
{
|
|
// 기존 비율 방식 (보정 없이)
|
|
float totalFlex = Mathf.Max(0.0001f, _leftLayout.flexibleWidth + _rightLayout.flexibleWidth);
|
|
float leftWeight = Mathf.Clamp01(_leftLayout.flexibleWidth / totalFlex);
|
|
float normalized = Mathf.InverseLerp(minLeftWeight, maxLeftWeight, Mathf.Clamp(leftWeight, minLeftWeight, maxLeftWeight));
|
|
float xRatio = Mathf.Lerp(minX, maxX, normalized);
|
|
_handleRect.anchoredPosition = new Vector2(xRatio, _handleY);
|
|
return;
|
|
}
|
|
|
|
// 고정 패널 폭 + 가변 왼쪽 패널 실제 폭 = 경계 픽셀
|
|
float fixedWidth = (_leftFixedPanel != null && _leftFixedPanel.gameObject.activeSelf) ? _leftFixedPanel.rect.width : 0f;
|
|
float variableLeftWidth = _left.rect.width; // 레이아웃 적용 후 실제 계산된 폭
|
|
|
|
// 부모 pivot 기반 local 좌표 좌측 경계
|
|
float widthParent = _parent.rect.width;
|
|
float origin = -widthParent * _parent.pivot.x;
|
|
float boundaryX = origin + fixedWidth + variableLeftWidth;
|
|
|
|
// 핸들 자체 폭 중앙 정렬 (핸들 pivot이 중앙이라고 가정)
|
|
// 경계 근처에서 과도한 이동 방지 (clamp)
|
|
float halfHandle = _handleRect.rect.width * 0.5f;
|
|
float minCenter = origin + fixedWidth + halfHandle; // 최소 중앙: 고정 패널 끝 + 반 핸들
|
|
float maxCenter = origin + widthParent - halfHandle; // 최대 중앙: 부모 우측 - 반 핸들
|
|
float centerX = Mathf.Clamp(boundaryX, minCenter, maxCenter);
|
|
_handleRect.anchoredPosition = new Vector2(centerX, _handleY);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 현재 레이아웃 상태에 맞게 드래그 핸들의 위치를 동기화합니다.
|
|
/// </summary>
|
|
public void RefreshPosition()
|
|
{
|
|
if (_parent == null || _handleRect == null || _leftLayout == null || _rightLayout == null) return;
|
|
float width = _parent.rect.width;
|
|
if (width <= 0f) return;
|
|
|
|
float minX, maxX, leftOffset;
|
|
GetWorkArea(out minX, out maxX, out leftOffset);
|
|
|
|
Canvas.ForceUpdateCanvases();
|
|
UpdateHandlePositionFromActualWidths(minX, maxX);
|
|
}
|
|
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void OnEndDrag(PointerEventData eventData) { }
|
|
}
|
|
}
|