using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
// decompiled source for ScrollRect can be found here:
// https://github.com/jamesjlinden/unity-decompiled/blob/master/UnityEngine.UI/UI/ScrollRect.cs
public class PinchableScrollRect : ScrollRect, IPointerEnterHandler, IPointerExitHandler
{
public enum ZoomMode
{
LocalScale,
RectSize
}
public ZoomMode _zoomMode = ZoomMode.LocalScale;
[SerializeField] protected float _minZoom = .1f;
[SerializeField] protected float _maxZoom = 10;
[SerializeField] protected float _zoomLerpSpeed = 10f;
protected float _currentZoom = 1;
public float CurrentZoom => _currentZoom;
protected float _targetZoom = 1;
protected float _initialWidth;
protected float _initialHeight;
protected float _horizMargin = 0f;
protected float _vertMargin = 0f;
bool _isPinching = false;
float _startPinchDist;
float _startPinchZoom;
Vector2 _startPinchCenterPosition;
Vector2 _startPinchScreenPosition;
float _mouseWheelSensitivity = 1;
Vector2 posFromBottomLeft;
bool blockPan = false;
bool mouseIsInside = false;
protected override void Awake()
{
base.Awake();
Input.multiTouchEnabled = true;
}
protected override void Start()
{
base.Start();
if (_zoomMode == ZoomMode.RectSize)
{
_initialWidth = content.rect.width;
_initialHeight = content.rect.height;
//Debug.Log($"ScrollRect content _initialWidth: {_initialWidth}, _initialHeight: {_initialHeight}");
}
}
public void OnPointerEnter(PointerEventData eventData)
{
//Debug.Log(" MOUSE ENTER ");
mouseIsInside = true;
}
public void OnPointerExit(PointerEventData eventData)
{
//Debug.Log(" MOUSE EXIT ");
mouseIsInside = false;
}
protected virtual void Update()
{
if (Input.touchCount == 2)
{
if (!_isPinching)
_isPinching = OnPinchStart();
if (_isPinching)
OnPinch();
}
else
{
_isPinching = false;
if (Input.touchCount == 0)
{
blockPan = false;
}
}
//pc input
if (mouseIsInside)
{
float scrollWheelInput = Input.GetAxis("Mouse ScrollWheel");
if (Mathf.Abs(scrollWheelInput) > float.Epsilon)
{
_targetZoom *= 1 + scrollWheelInput * _mouseWheelSensitivity;
_targetZoom = Mathf.Clamp(_targetZoom, _minZoom, _maxZoom);
_startPinchScreenPosition = (Vector2)Input.mousePosition;
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, _startPinchScreenPosition, null, out _startPinchCenterPosition);
Vector2 pivotPosition = new Vector2(content.pivot.x * content.rect.size.x, content.pivot.y * content.rect.size.y);
posFromBottomLeft = pivotPosition + _startPinchCenterPosition;
// Debug.Log($"posFromBottomLeft: {posFromBottomLeft} as fraction: {posFromBottomLeft.x / content.rect.width},{posFromBottomLeft.y / content.rect.height}");
//SetPivot(content, new Vector2(posFromBottomLeft.x / content.rect.width, posFromBottomLeft.y / content.rect.height));
}
}
//pc input end
if (Mathf.Abs(_currentZoom - _targetZoom) > 0.001f)
SetZoom(_targetZoom);
//SetZoom(Mathf.Lerp(_currentZoom, _targetZoom, _zoomLerpSpeed * Time.deltaTime));
}
public virtual void SetZoom(float z)
{
if (_zoomMode == ZoomMode.LocalScale)
{
SetPivot(content, new Vector2(posFromBottomLeft.x / content.rect.width, posFromBottomLeft.y / content.rect.height));
content.localScale = Vector3.one * z;
}
else if (_zoomMode == ZoomMode.RectSize)
{
Vector2 spcp;
Vector2 pp = new Vector2(content.pivot.x * content.rect.size.x, content.pivot.y * content.rect.size.y);
if (mouseIsInside)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, (Vector2)Input.mousePosition, null, out spcp);
posFromBottomLeft = pp + spcp;
}
else
{
//RectTransformUtility.ScreenPointToLocalPointInRectangle(content, (transform as RectTransform).rect.center, null, out spcp);
posFromBottomLeft = pp;
}
float curImgW = _initialWidth * _currentZoom;
float newImgW = _initialWidth * z;
float deltaW = newImgW - curImgW;
float MX = posFromBottomLeft.x - _horizMargin;
float mx = MX / curImgW;
float Px = (deltaW==0.0)? content.pivot.x : (mx * newImgW - MX) / deltaW;
float curImgH = _initialHeight * _currentZoom;
float newImgH = _initialHeight * z;
float deltaH = newImgH - curImgH;
float MY = posFromBottomLeft.y - _vertMargin;
float my = MY / curImgH;
float Py = (deltaH == 0.0) ? content.pivot.y : (my * newImgH - MY) / deltaH;
bool setPivot = true;
if (Px < -2 || Px > 3)
{
Px = Mathf.Clamp(Px, -1f, 2f);
setPivot = false;
}
if (Py < -2 || Py > 3)
{
Py = Mathf.Clamp(Py, -1f, 2f);
setPivot = false;
}
if (setPivot)
SetPivot(content, new Vector2(Px, Py));
content.sizeDelta = new Vector2(newImgW + 2*_horizMargin, newImgH + 2 * _vertMargin);
}
_currentZoom = z;
}
protected override void SetContentAnchoredPosition(Vector2 position)
{
if (_isPinching || blockPan) return;
base.SetContentAnchoredPosition(position);
}
bool OnPinchStart()
{
Vector2 pos1 = Input.touches[0].position;
Vector2 pos2 = Input.touches[1].position;
if (!RectTransformUtility.RectangleContainsScreenPoint((transform as RectTransform), pos1) ||
!RectTransformUtility.RectangleContainsScreenPoint((transform as RectTransform), pos2))
return false;
_startPinchDist = Distance(pos1, pos2) * content.localScale.x;
_startPinchZoom = _targetZoom;
_startPinchScreenPosition = (pos1 + pos2) / 2;
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, _startPinchScreenPosition, null, out _startPinchCenterPosition);
Vector2 pivotPosition = new Vector3(content.pivot.x * content.rect.size.x, content.pivot.y * content.rect.size.y);
Vector2 posFromBottomLeft = pivotPosition + _startPinchCenterPosition;
SetPivot(content, new Vector2(posFromBottomLeft.x / content.rect.width, posFromBottomLeft.y / content.rect.height));
blockPan = true;
return true;
}
void OnPinch()
{
float currentPinchDist = Distance(Input.touches[0].position, Input.touches[1].position) * content.localScale.x;
_targetZoom = (currentPinchDist / _startPinchDist) * _startPinchZoom;
_targetZoom = Mathf.Clamp(_targetZoom, _minZoom, _maxZoom);
}
float Distance(Vector2 pos1, Vector2 pos2)
{
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, pos1, null, out pos1);
RectTransformUtility.ScreenPointToLocalPointInRectangle(content, pos2, null, out pos2);
return Vector2.Distance(pos1, pos2);
}
static void SetPivot(RectTransform rectTransform, Vector2 pivot)
{
if (rectTransform == null) return;
Vector2 size = rectTransform.rect.size;
Vector2 deltaPivot = rectTransform.pivot - pivot;
Vector3 deltaPosition = new Vector3(deltaPivot.x * size.x, deltaPivot.y * size.y) * rectTransform.localScale.x;
rectTransform.pivot = pivot;
rectTransform.localPosition -= deltaPosition;
}
}