| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383 |
- /// 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
- {
- [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;
- readonly AutoScrollState autoScrollState = new AutoScrollState();
- Action<float> onUpdatePosition;
- Action<int> onItemSelected;
- Vector2 pointerStartLocalPosition;
- float dragStartScrollPosition;
- float prevScrollPosition;
- float currentScrollPosition;
- bool dragging;
- float velocity;
- enum ScrollDirection
- {
- Vertical,
- Horizontal,
- }
- enum MovementType
- {
- Unrestricted = ScrollRect.MovementType.Unrestricted,
- Elastic = ScrollRect.MovementType.Elastic,
- Clamped = ScrollRect.MovementType.Clamped
- }
- [Serializable]
- struct Snap
- {
- public bool Enable;
- public float VelocityThreshold;
- public float Duration;
- }
- class AutoScrollState
- {
- public bool Enable;
- public bool Elastic;
- public float Duration;
- public float StartTime;
- public float EndScrollPosition;
- public void Reset()
- {
- Enable = false;
- Elastic = false;
- Duration = 0f;
- StartTime = 0f;
- EndScrollPosition = 0f;
- }
- }
- public void OnUpdatePosition(Action<float> onUpdatePosition)
- {
- this.onUpdatePosition = onUpdatePosition;
- }
- public void OnItemSelected(Action<int> onItemSelected)
- {
- this.onItemSelected = onItemSelected;
- }
- public void SetDataCount(int dataCount)
- {
- this.dataCount = dataCount;
- }
- public void ScrollTo(int index, float duration)
- {
- autoScrollState.Reset();
- autoScrollState.Enable = true;
- autoScrollState.Duration = duration;
- autoScrollState.StartTime = Time.unscaledTime;
- autoScrollState.EndScrollPosition = CalculateDestinationIndex(index);
- velocity = 0f;
- dragStartScrollPosition = currentScrollPosition;
- ItemSelected(Mathf.RoundToInt(GetCircularPosition(autoScrollState.EndScrollPosition, dataCount)));
- }
- public void JumpTo(int index)
- {
- autoScrollState.Reset();
- velocity = 0f;
- dragging = false;
- index = CalculateDestinationIndex(index);
- ItemSelected(index);
- UpdatePosition(index);
- }
- 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;
- autoScrollState.Reset();
- }
- 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 != 0f)
- {
- 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 0f;
- }
- if (position < 0f)
- {
- return -position;
- }
- if (position > dataCount - 1)
- {
- return dataCount - 1 - position;
- }
- return 0f;
- }
- void UpdatePosition(float position)
- {
- currentScrollPosition = position;
- if (onUpdatePosition != null)
- {
- onUpdatePosition(currentScrollPosition);
- }
- }
- void ItemSelected(int index)
- {
- if (onItemSelected != null)
- {
- onItemSelected(index);
- }
- }
- float RubberDelta(float overStretching, float viewSize)
- {
- return (1 - (1 / ((Mathf.Abs(overStretching) * 0.55f / viewSize) + 1))) * viewSize * Mathf.Sign(overStretching);
- }
- void Update()
- {
- var deltaTime = Time.unscaledDeltaTime;
- var offset = CalculateOffset(currentScrollPosition);
- if (autoScrollState.Enable)
- {
- var position = 0f;
- if (autoScrollState.Elastic)
- {
- var speed = velocity;
- position = Mathf.SmoothDamp(currentScrollPosition, currentScrollPosition + offset, ref speed, elasticity, Mathf.Infinity, deltaTime);
- velocity = speed;
- if (Mathf.Abs(velocity) < 0.01f)
- {
- position = Mathf.Clamp(Mathf.RoundToInt(position), 0, dataCount - 1);
- velocity = 0f;
- autoScrollState.Reset();
- }
- }
- else
- {
- var alpha = Mathf.Clamp01((Time.unscaledTime - autoScrollState.StartTime) / Mathf.Max(autoScrollState.Duration, float.Epsilon));
- position = Mathf.Lerp(dragStartScrollPosition, autoScrollState.EndScrollPosition, EaseInOutCubic(0, 1, alpha));
- if (Mathf.Approximately(alpha, 1f))
- {
- autoScrollState.Reset();
- }
- }
- UpdatePosition(position);
- }
- else if (!dragging && (!Mathf.Approximately(offset, 0f) || !Mathf.Approximately(velocity, 0f)))
- {
- var position = currentScrollPosition;
- if (movementType == MovementType.Elastic && !Mathf.Approximately(offset, 0f))
- {
- autoScrollState.Reset();
- autoScrollState.Enable = true;
- autoScrollState.Elastic = true;
- ItemSelected(Mathf.Clamp(Mathf.RoundToInt(position), 0, dataCount - 1));
- }
- else if (inertia)
- {
- velocity *= Mathf.Pow(decelerationRate, deltaTime);
- if (Mathf.Abs(velocity) < 0.001f)
- {
- velocity = 0f;
- }
- position += velocity * deltaTime;
- if (snap.Enable && Mathf.Abs(velocity) < snap.VelocityThreshold)
- {
- ScrollTo(Mathf.RoundToInt(currentScrollPosition), snap.Duration);
- }
- }
- else
- {
- velocity = 0f;
- }
- if (!Mathf.Approximately(velocity, 0f))
- {
- if (movementType == MovementType.Clamped)
- {
- offset = CalculateOffset(position);
- position += offset;
- if (Mathf.Approximately(position, 0f) || Mathf.Approximately(position, dataCount - 1f))
- {
- velocity = 0f;
- ItemSelected(Mathf.RoundToInt(position));
- }
- }
- UpdatePosition(position);
- }
- }
- if (!autoScrollState.Enable && dragging && inertia)
- {
- var newVelocity = (currentScrollPosition - prevScrollPosition) / deltaTime;
- velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10f);
- }
- if (currentScrollPosition != prevScrollPosition)
- {
- prevScrollPosition = currentScrollPosition;
- }
- }
- int CalculateDestinationIndex(int index)
- {
- return movementType == MovementType.Unrestricted
- ? CalculateClosestIndex(index)
- : Mathf.Clamp(index, 0, dataCount - 1);
- }
- int CalculateClosestIndex(int index)
- {
- var diff = GetCircularPosition(index, dataCount)
- - GetCircularPosition(currentScrollPosition, dataCount);
- if (Mathf.Abs(diff) > dataCount * 0.5f)
- {
- diff = Mathf.Sign(-diff) * (dataCount - Mathf.Abs(diff));
- }
- return Mathf.RoundToInt(diff + currentScrollPosition);
- }
- float GetCircularPosition(float position, int length)
- {
- return position < 0 ? length - 1 + (position + 1) % length : position % length;
- }
- 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;
- }
- }
- }
|