Skip to content

Instantly share code, notes, and snippets.

@stramit
Created July 28, 2014 20:00
Show Gist options
  • Save stramit/714e60ecdf85c15758cd to your computer and use it in GitHub Desktop.
Save stramit/714e60ecdf85c15758cd to your computer and use it in GitHub Desktop.
Scroll Rect
using System.Collections.Generic;
using UnityEngine.EventSystems;
namespace UnityEngine.UI
{
[AddComponentMenu ("UI/Scroll Rect", 33)]
[SelectionBase]
[ExecuteInEditMode]
[RequireComponent(typeof(RectTransform))]
public class ScrollRect : UIBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
{
public enum MovementType
{
Unrestricted, // Unrestricted movement -- can scroll forever
Elastic, // Restricted but flexible -- can go past the edges, but springs back in place
Clamped, // Restricted movement where it's not possible to go past the edges
}
[SerializeField]
private RectTransform m_Content;
public RectTransform content { get { return m_Content; } set { m_Content = value; } }
[SerializeField]
private bool m_Horizontal = true;
public bool horizontal { get { return m_Horizontal; } set { m_Horizontal = value; } }
[SerializeField]
private bool m_Vertical = true;
public bool vertical { get { return m_Vertical; } set { m_Vertical = value; } }
[SerializeField]
private MovementType m_MovementType = MovementType.Elastic;
public MovementType movementType { get { return m_MovementType; } set { m_MovementType = value; } }
[SerializeField]
private float m_Elasticity = 0.1f; // Only used for MovementType.Elastic
public float elasticity { get { return m_Elasticity; } set { m_Elasticity = value; } }
[SerializeField]
private bool m_Inertia = true;
public bool intertia { get { return m_Inertia; } set { m_Inertia = value; } }
[SerializeField]
private float m_DecelerationRate = 0.135f; // Only used when intertia is enabled
public float decelerationRate { get { return m_DecelerationRate; } set { m_DecelerationRate = value; } }
[SerializeField]
private Scrollbar m_HorizontalScrollbar;
public Scrollbar horizontalScrollbar
{
get
{
return m_HorizontalScrollbar;
}
set
{
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.RemoveListener (SetHorizontalNormalizedPosition);
m_HorizontalScrollbar = value;
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.AddListener (SetHorizontalNormalizedPosition);
}
}
[SerializeField]
private Scrollbar m_VerticalScrollbar;
public Scrollbar verticalScrollbar
{
get
{
return m_VerticalScrollbar;
}
set
{
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.RemoveListener (SetVerticalNormalizedPosition);
m_VerticalScrollbar = value;
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.AddListener (SetVerticalNormalizedPosition);
}
}
// The offset from handle position to mouse down position
private Vector2 m_PointerStartLocalCursor = Vector2.zero;
private Vector2 m_ContentStartPosition = Vector2.zero;
private RectTransform m_ViewRect;
private Bounds m_ContentBounds;
private Bounds m_ViewBounds;
private bool m_AllowHorizontal;
private bool m_AllowVertical;
private Vector2 m_Velocity;
private bool m_Dragging = false;
private Vector2 m_LastPosition = Vector2.zero;
protected ScrollRect()
{}
protected override void OnEnable ()
{
base.OnEnable ();
m_ViewRect = transform as RectTransform;
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.AddListener (SetHorizontalNormalizedPosition);
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.AddListener (SetVerticalNormalizedPosition);
UpdateBounds ();
UpdateScrollbars (Vector2.zero);
}
protected override void OnDisable ()
{
base.OnDisable ();
if (m_HorizontalScrollbar)
m_HorizontalScrollbar.onValueChanged.RemoveListener (SetHorizontalNormalizedPosition);
if (m_VerticalScrollbar)
m_VerticalScrollbar.onValueChanged.RemoveListener (SetVerticalNormalizedPosition);
}
public override bool IsActive ()
{
return base.IsActive () && m_Content != null;
}
public void OnBeginDrag (PointerEventData data)
{
if (!IsActive ())
return;
m_PointerStartLocalCursor = Vector2.zero;
RectTransformUtility.ScreenPointToLocalPointInRectangle (m_ViewRect, data.position, data.pressEventCamera, out m_PointerStartLocalCursor);
m_ContentStartPosition = m_Content.anchoredPosition;
m_Velocity = Vector2.zero;
m_Dragging = true;
UpdateBounds ();
}
public void OnEndDrag(PointerEventData data)
{
m_Dragging = false;
}
public void OnDrag(PointerEventData data)
{
if (!IsActive())
return;
Vector2 localCursor;
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle (m_ViewRect, data.position, data.pressEventCamera, out localCursor))
return;
UpdateBounds ();
var pointerDelta = localCursor - m_PointerStartLocalCursor;
Vector2 position = m_ContentStartPosition + pointerDelta;
// Offset to get content into place in the view.
Vector2 offset = CalculateOffset (position - m_Content.anchoredPosition);
position += offset;
if (m_MovementType == MovementType.Elastic)
{
if (offset.x != 0)
position.x = position.x - RubberDelta (offset.x, m_ViewBounds.size.x);
if (offset.y != 0)
position.y = position.y - RubberDelta (offset.y, m_ViewBounds.size.y);
}
if (!m_Horizontal || !m_AllowHorizontal)
position.x = m_Content.anchoredPosition.x;
if (!m_Vertical || !m_AllowVertical)
position.y = m_Content.anchoredPosition.y;
m_Content.anchoredPosition = position;
}
protected virtual void LateUpdate ()
{
if (!m_Content)
return;
float deltaTime = Time.unscaledDeltaTime;
if (m_Dragging && m_Inertia)
{
Vector3 newVelocity = (m_Content.anchoredPosition - m_LastPosition) / deltaTime;
m_Velocity = Vector3.Lerp (m_Velocity, newVelocity, deltaTime * 10);
}
m_LastPosition = m_Content.anchoredPosition;
Vector2 offset = CalculateOffset (Vector2.zero);
if (!m_Dragging && (offset != Vector2.zero || m_Velocity != Vector2.zero))
{
Vector2 position = m_Content.anchoredPosition;
for (int axis = 0; axis < 2; axis++)
{
// Apply spring physics if movement is elastic and content has an offset from the view.
if (m_MovementType == MovementType.Elastic && offset[axis] != 0)
{
float speed = m_Velocity[axis];
position[axis] = Mathf.SmoothDamp (m_Content.anchoredPosition[axis], m_Content.anchoredPosition[axis] + offset[axis], ref speed, m_Elasticity);
m_Velocity[axis] = speed;
}
// Else move content according to velocity with deceleration applied.
else if (m_Inertia)
{
m_Velocity[axis] *= Mathf.Pow (m_DecelerationRate, deltaTime);
position[axis] += m_Velocity[axis] * deltaTime;
}
// If we have neither elaticity or friction, there shouldn't be any velocity.
else
{
m_Velocity[axis] = 0;
}
}
if (m_Velocity != Vector2.zero)
{
if (m_MovementType == MovementType.Clamped)
{
offset = CalculateOffset (position - m_Content.anchoredPosition);
position += offset;
}
if (!m_Horizontal || !m_AllowHorizontal)
position.x = m_Content.anchoredPosition.x;
if (!m_Vertical || !m_AllowVertical)
position.y = m_Content.anchoredPosition.y;
if (position != m_Content.anchoredPosition)
m_Content.anchoredPosition = position;
}
}
UpdateBounds ();
UpdateScrollbars (offset);
}
void UpdateScrollbars (Vector2 offset)
{
if (m_HorizontalScrollbar)
{
m_HorizontalScrollbar.size = Mathf.Clamp01 ((m_ViewBounds.size.x - Mathf.Abs (offset.x)) / m_ContentBounds.size.x);
m_HorizontalScrollbar.value = horizontalNormalizedPosition;
}
if (m_VerticalScrollbar)
{
m_VerticalScrollbar.size = Mathf.Clamp01 ((m_ViewBounds.size.y - Mathf.Abs (offset.y)) / m_ContentBounds.size.y);
m_VerticalScrollbar.value = verticalNormalizedPosition;
}
}
public Vector2 normalizedPosition
{
get
{
return new Vector2 (horizontalNormalizedPosition, verticalNormalizedPosition);
}
set
{
SetHorizontalNormalizedPosition (value.x);
SetVerticalNormalizedPosition (value.y);
}
}
public float horizontalNormalizedPosition
{
get
{
if (m_ContentBounds.size.x <= m_ViewBounds.size.x)
return 0;
return Mathf.Clamp01 ((m_ViewBounds.min.x - m_ContentBounds.min.x) / (m_ContentBounds.size.x - m_ViewBounds.size.x));
}
set
{
SetHorizontalNormalizedPosition (value);
}
}
public float verticalNormalizedPosition
{
get
{
if (m_ContentBounds.size.y <= m_ViewBounds.size.y)
return 0;
return Mathf.Clamp01 ((m_ViewBounds.min.y - m_ContentBounds.min.y) / (m_ContentBounds.size.y - m_ViewBounds.size.y));
}
set
{
SetVerticalNormalizedPosition (value);
}
}
void SetHorizontalNormalizedPosition (float value)
{
UpdateBounds ();
float scroll = m_ViewBounds.min.x - value * (m_ContentBounds.size.x - m_ViewBounds.size.x);
Vector2 anchoredPosition = m_Content.anchoredPosition;
anchoredPosition.x += scroll - m_ContentBounds.min.x;
m_Content.anchoredPosition = anchoredPosition;
}
void SetVerticalNormalizedPosition (float value)
{
UpdateBounds ();
float scroll = m_ViewBounds.min.y - value * (m_ContentBounds.size.y - m_ViewBounds.size.y);
Vector2 anchoredPosition = m_Content.anchoredPosition;
anchoredPosition.y += scroll - m_ContentBounds.min.y;
m_Content.anchoredPosition = anchoredPosition;
}
static float RubberDelta (float overStretching, float viewSize)
{
return (1 - (1 / ((Mathf.Abs (overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign (overStretching);
}
void UpdateBounds ()
{
m_ContentBounds = GetBounds();
m_ViewBounds = new Bounds (m_ViewRect.rect.center, m_ViewRect.rect.size);
m_AllowHorizontal = (m_MovementType == MovementType.Unrestricted) || m_ContentBounds.size.x > m_ViewBounds.size.x;
m_AllowVertical = (m_MovementType == MovementType.Unrestricted) || m_ContentBounds.size.y > m_ViewBounds.size.y;
}
private readonly Vector3[] m_Corners = new Vector3[4];
private Bounds GetBounds()
{
var vMin = new Vector3 (float.MaxValue, float.MaxValue, float.MaxValue);
var vMax = new Vector3 (float.MinValue, float.MinValue, float.MinValue);
var toLocal = m_ViewRect.worldToLocalMatrix;
m_Content.GetWorldCorners (m_Corners);
for (int j = 0; j < 4; j++)
{
Vector3 v = toLocal.MultiplyPoint3x4 (m_Corners[j]);
vMin = Vector3.Min (v, vMin);
vMax = Vector3.Max (v, vMax);
}
var bounds = new Bounds (vMin, Vector3.zero);
bounds.Encapsulate (vMax);
return bounds;
}
Vector3 CalculateOffset (Vector2 delta)
{
Vector3 offset = Vector3.zero;
if (m_MovementType == MovementType.Unrestricted)
return offset;
Vector2 min = m_ContentBounds.min;
Vector2 max = m_ContentBounds.max;
if (m_Horizontal)
{
min.x += delta.x;
max.x += delta.x;
if (!m_AllowHorizontal || min.x > m_ViewBounds.min.x)
offset.x = m_ViewBounds.min.x - min.x;
else if (max.x < m_ViewBounds.max.x)
offset.x = m_ViewBounds.max.x - max.x;
}
if (m_Vertical)
{
min.y += delta.y;
max.y += delta.y;
if (!m_AllowVertical || max.y < m_ViewBounds.max.y)
offset.y = m_ViewBounds.max.y - max.y;
else if (min.y > m_ViewBounds.min.y)
offset.y = m_ViewBounds.min.y - min.y;
}
return offset;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment