Created
July 28, 2014 20:00
-
-
Save stramit/714e60ecdf85c15758cd to your computer and use it in GitHub Desktop.
Scroll Rect
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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