323 lines
7.8 KiB
C#
323 lines
7.8 KiB
C#
/// Credit setchi (https://github.com/setchi)
|
|
/// Sourced from - https://github.com/setchi/FancyScrollView
|
|
|
|
using System;
|
|
using UnityEngine.Events;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace UnityEngine.UI.Extensions
|
|
{
|
|
|
|
public class ScrollPositionController : UIBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
|
|
{
|
|
#region Sub-Classes
|
|
[System.Serializable]
|
|
public class UpdatePositionEvent : UnityEvent<float> { }
|
|
|
|
[System.Serializable]
|
|
public class ItemSelectedEvent : UnityEvent<int> { }
|
|
#endregion
|
|
|
|
[Serializable]
|
|
struct Snap
|
|
{
|
|
public bool Enable;
|
|
public float VelocityThreshold;
|
|
public float Duration;
|
|
}
|
|
|
|
enum ScrollDirection
|
|
{
|
|
Vertical,
|
|
Horizontal,
|
|
}
|
|
|
|
enum MovementType
|
|
{
|
|
Unrestricted = ScrollRect.MovementType.Unrestricted,
|
|
Elastic = ScrollRect.MovementType.Elastic,
|
|
Clamped = ScrollRect.MovementType.Clamped
|
|
}
|
|
|
|
[SerializeField]
|
|
RectTransform viewport = null;
|
|
[SerializeField]
|
|
ScrollDirection directionOfRecognize = ScrollDirection.Vertical;
|
|
[SerializeField]
|
|
MovementType movementType = MovementType.Elastic;
|
|
[SerializeField]
|
|
float elasticity = 0.1f;
|
|
[SerializeField]
|
|
float scrollSensitivity = 1f;
|
|
[SerializeField]
|
|
bool inertia = true;
|
|
[SerializeField, Tooltip("Only used when inertia is enabled")]
|
|
float decelerationRate = 0.03f;
|
|
[SerializeField, Tooltip("Only used when inertia is enabled")]
|
|
Snap snap = new Snap { Enable = true, VelocityThreshold = 0.5f, Duration = 0.3f };
|
|
[SerializeField]
|
|
int dataCount;
|
|
|
|
#region Events
|
|
[Tooltip("Event that fires when the position of an item changes")]
|
|
public UpdatePositionEvent OnUpdatePosition;
|
|
|
|
[Tooltip("Event that fires when an item is selected/focused")]
|
|
public ItemSelectedEvent OnItemSelected;
|
|
#endregion
|
|
|
|
Vector2 pointerStartLocalPosition;
|
|
float dragStartScrollPosition;
|
|
float currentScrollPosition;
|
|
bool dragging;
|
|
|
|
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
|
|
{
|
|
if (eventData.button != PointerEventData.InputButton.Left)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pointerStartLocalPosition = Vector2.zero;
|
|
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
|
viewport,
|
|
eventData.position,
|
|
eventData.pressEventCamera,
|
|
out pointerStartLocalPosition);
|
|
|
|
dragStartScrollPosition = currentScrollPosition;
|
|
dragging = true;
|
|
}
|
|
|
|
void IDragHandler.OnDrag(PointerEventData eventData)
|
|
{
|
|
if (eventData.button != PointerEventData.InputButton.Left)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!dragging)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector2 localCursor;
|
|
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
|
viewport,
|
|
eventData.position,
|
|
eventData.pressEventCamera,
|
|
out localCursor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var pointerDelta = localCursor - pointerStartLocalPosition;
|
|
var position = (directionOfRecognize == ScrollDirection.Horizontal ? -pointerDelta.x : pointerDelta.y)
|
|
/ GetViewportSize()
|
|
* scrollSensitivity
|
|
+ dragStartScrollPosition;
|
|
|
|
var offset = CalculateOffset(position);
|
|
position += offset;
|
|
|
|
if (movementType == MovementType.Elastic)
|
|
{
|
|
if (offset != 0)
|
|
{
|
|
position -= RubberDelta(offset, scrollSensitivity);
|
|
}
|
|
}
|
|
UpdatePosition(position);
|
|
}
|
|
|
|
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
|
|
{
|
|
if (eventData.button != PointerEventData.InputButton.Left)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dragging = false;
|
|
}
|
|
|
|
float GetViewportSize()
|
|
{
|
|
return directionOfRecognize == ScrollDirection.Horizontal
|
|
? viewport.rect.size.x
|
|
: viewport.rect.size.y;
|
|
}
|
|
|
|
float CalculateOffset(float position)
|
|
{
|
|
if (movementType == MovementType.Unrestricted)
|
|
{
|
|
return 0;
|
|
}
|
|
if (position < 0)
|
|
{
|
|
return -position;
|
|
}
|
|
if (position > dataCount - 1)
|
|
{
|
|
return (dataCount - 1) - position;
|
|
}
|
|
return 0f;
|
|
}
|
|
|
|
void UpdatePosition(float position)
|
|
{
|
|
currentScrollPosition = position;
|
|
|
|
if (OnUpdatePosition != null)
|
|
{
|
|
OnUpdatePosition.Invoke(currentScrollPosition);
|
|
}
|
|
}
|
|
|
|
float RubberDelta(float overStretching, float viewSize)
|
|
{
|
|
return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching);
|
|
}
|
|
|
|
//public void OnUpdatePosition(Action<float> onUpdatePosition)
|
|
//{
|
|
// this.onUpdatePosition = onUpdatePosition;
|
|
//}
|
|
|
|
public void SetDataCount(int dataCont)
|
|
{
|
|
this.dataCount = dataCont;
|
|
}
|
|
|
|
float velocity;
|
|
float prevScrollPosition;
|
|
|
|
bool autoScrolling;
|
|
float autoScrollDuration;
|
|
float autoScrollStartTime;
|
|
float autoScrollPosition;
|
|
|
|
void Update()
|
|
{
|
|
var deltaTime = Time.unscaledDeltaTime;
|
|
var offset = CalculateOffset(currentScrollPosition);
|
|
|
|
if (autoScrolling)
|
|
{
|
|
var alpha = Mathf.Clamp01((Time.unscaledTime - autoScrollStartTime) / Mathf.Max(autoScrollDuration, float.Epsilon));
|
|
var position = Mathf.Lerp(dragStartScrollPosition, autoScrollPosition, EaseInOutCubic(0, 1, alpha));
|
|
UpdatePosition(position);
|
|
|
|
if (Mathf.Approximately(alpha, 1f))
|
|
{
|
|
autoScrolling = false;
|
|
// Auto scrolling is completed, get the item's index and firing OnItemSelected event.
|
|
if(OnItemSelected != null)
|
|
{
|
|
OnItemSelected.Invoke(Mathf.RoundToInt(GetLoopPosition(autoScrollPosition, dataCount)));
|
|
}
|
|
}
|
|
}
|
|
else if (!dragging && (offset != 0 || velocity != 0))
|
|
{
|
|
var position = currentScrollPosition;
|
|
// Apply spring physics if movement is elastic and content has an offset from the view.
|
|
if (movementType == MovementType.Elastic && offset != 0)
|
|
{
|
|
var speed = velocity;
|
|
position = Mathf.SmoothDamp(currentScrollPosition, currentScrollPosition + offset, ref speed, elasticity, Mathf.Infinity, deltaTime);
|
|
velocity = speed;
|
|
}
|
|
// Else move content according to velocity with deceleration applied.
|
|
else if (inertia)
|
|
{
|
|
velocity *= Mathf.Pow(decelerationRate, deltaTime);
|
|
if (Mathf.Abs(velocity) < 0.001f)
|
|
velocity = 0;
|
|
position += velocity * deltaTime;
|
|
|
|
if (snap.Enable && Mathf.Abs(velocity) < snap.VelocityThreshold)
|
|
{
|
|
ScrollTo(Mathf.RoundToInt(currentScrollPosition), snap.Duration);
|
|
}
|
|
}
|
|
// If we have neither elaticity or friction, there shouldn't be any velocity.
|
|
else
|
|
{
|
|
velocity = 0;
|
|
}
|
|
|
|
if (velocity != 0)
|
|
{
|
|
if (movementType == MovementType.Clamped)
|
|
{
|
|
offset = CalculateOffset(position);
|
|
position += offset;
|
|
}
|
|
UpdatePosition(position);
|
|
}
|
|
}
|
|
|
|
if (!autoScrolling && dragging && inertia)
|
|
{
|
|
var newVelocity = (currentScrollPosition - prevScrollPosition) / deltaTime;
|
|
velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10f);
|
|
}
|
|
|
|
if (currentScrollPosition != prevScrollPosition)
|
|
{
|
|
prevScrollPosition = currentScrollPosition;
|
|
}
|
|
}
|
|
|
|
public void ScrollTo(int index, float duration)
|
|
{
|
|
velocity = 0;
|
|
autoScrolling = true;
|
|
autoScrollDuration = duration;
|
|
autoScrollStartTime = Time.unscaledTime;
|
|
dragStartScrollPosition = currentScrollPosition;
|
|
|
|
autoScrollPosition = movementType == MovementType.Unrestricted
|
|
? CalculateClosestPosition(index)
|
|
: index;
|
|
}
|
|
|
|
float CalculateClosestPosition(int index)
|
|
{
|
|
var diff = GetLoopPosition(index, dataCount)
|
|
- GetLoopPosition(currentScrollPosition, dataCount);
|
|
|
|
if (Mathf.Abs(diff) > dataCount * 0.5f)
|
|
{
|
|
diff = Mathf.Sign(-diff) * (dataCount - Mathf.Abs(diff));
|
|
}
|
|
return diff + currentScrollPosition;
|
|
}
|
|
|
|
float GetLoopPosition(float position, int length)
|
|
{
|
|
if (position < 0)
|
|
{
|
|
position = (length - 1) + (position + 1) % length;
|
|
}
|
|
else if (position > length - 1)
|
|
{
|
|
position = position % length;
|
|
}
|
|
return position;
|
|
}
|
|
|
|
float EaseInOutCubic(float start, float end, float value)
|
|
{
|
|
value /= 0.5f;
|
|
end -= start;
|
|
if (value < 1f)
|
|
{
|
|
return end * 0.5f * value * value * value + start;
|
|
}
|
|
value -= 2f;
|
|
return end * 0.5f * (value * value * value + 2f) + start;
|
|
}
|
|
}
|
|
} |