289 lines
8.0 KiB
C#
289 lines
8.0 KiB
C#
using System.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.EventSystems;
|
|
|
|
namespace AlicizaX.UI
|
|
{
|
|
public class Scroller : MonoBehaviour, IScroller, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
|
|
{
|
|
private static readonly WaitForEndOfFrame EndOfFrameYield = new();
|
|
private Coroutine movementCoroutine;
|
|
|
|
protected float position;
|
|
public float Position { get => position; set => position = value; }
|
|
|
|
protected float velocity;
|
|
public float Velocity => velocity;
|
|
|
|
protected Direction direction;
|
|
public Direction Direction
|
|
{
|
|
get => direction;
|
|
set => direction = value;
|
|
}
|
|
|
|
protected Vector2 contentSize;
|
|
public Vector2 ContentSize
|
|
{
|
|
get => contentSize;
|
|
set => contentSize = value;
|
|
}
|
|
|
|
protected Vector2 viewSize;
|
|
public Vector2 ViewSize
|
|
{
|
|
get => viewSize;
|
|
set => viewSize = value;
|
|
}
|
|
|
|
protected float scrollSpeed = 1f;
|
|
public float ScrollSpeed
|
|
{
|
|
get => scrollSpeed;
|
|
set => scrollSpeed = value;
|
|
}
|
|
|
|
protected float wheelSpeed = 30f;
|
|
public float WheelSpeed
|
|
{
|
|
get => wheelSpeed;
|
|
set => wheelSpeed = value;
|
|
}
|
|
|
|
protected bool snap;
|
|
public bool Snap
|
|
{
|
|
get => snap;
|
|
set => snap = value;
|
|
}
|
|
|
|
protected ScrollerEvent scrollerEvent = new();
|
|
protected MoveStopEvent moveStopEvent = new();
|
|
protected DraggingEvent draggingEvent = new();
|
|
|
|
public float MaxPosition => direction == Direction.Vertical ?
|
|
Mathf.Max(contentSize.y - viewSize.y, 0) :
|
|
Mathf.Max(contentSize.x - viewSize.x, 0);
|
|
|
|
public float ViewLength => direction == Direction.Vertical ? viewSize.y : viewSize.x;
|
|
|
|
public ScrollerEvent OnValueChanged { get => scrollerEvent; set => scrollerEvent = value; }
|
|
|
|
public MoveStopEvent OnMoveStoped { get => moveStopEvent; set => moveStopEvent = value; }
|
|
|
|
public DraggingEvent OnDragging { get => draggingEvent; set => draggingEvent = value; }
|
|
|
|
public float dragStopTime = 0f;
|
|
|
|
|
|
public virtual void ScrollTo(float position, bool smooth = false)
|
|
{
|
|
if (Mathf.Approximately(position, this.position)) return;
|
|
|
|
StopMovement();
|
|
|
|
if (!smooth)
|
|
{
|
|
this.position = position;
|
|
OnValueChanged?.Invoke(this.position);
|
|
}
|
|
else
|
|
{
|
|
movementCoroutine = StartCoroutine(RunMotion(MoveTo(position)));
|
|
}
|
|
}
|
|
|
|
public virtual void ScrollToDuration(float position, float duration)
|
|
{
|
|
if (Mathf.Approximately(position, this.position))
|
|
{
|
|
return;
|
|
}
|
|
|
|
StopMovement();
|
|
if (duration <= 0f)
|
|
{
|
|
this.position = position;
|
|
OnValueChanged?.Invoke(this.position);
|
|
return;
|
|
}
|
|
|
|
movementCoroutine = StartCoroutine(RunMotion(ToPositionByDuration(position, duration)));
|
|
}
|
|
|
|
public virtual void ScrollToRatio(float ratio)
|
|
{
|
|
ScrollTo(MaxPosition * ratio, false);
|
|
}
|
|
|
|
public void OnBeginDrag(PointerEventData eventData)
|
|
{
|
|
OnDragging?.Invoke(true);
|
|
StopMovement();
|
|
}
|
|
|
|
public void OnEndDrag(PointerEventData eventData)
|
|
{
|
|
Inertia();
|
|
Elastic();
|
|
OnDragging?.Invoke(false);
|
|
}
|
|
|
|
public void OnDrag(PointerEventData eventData)
|
|
{
|
|
dragStopTime = Time.time;
|
|
|
|
velocity = GetDelta(eventData);
|
|
position += velocity;
|
|
|
|
OnValueChanged?.Invoke(position);
|
|
}
|
|
|
|
public void OnScroll(PointerEventData eventData)
|
|
{
|
|
StopMovement();
|
|
|
|
float rate = GetScrollRate() * wheelSpeed;
|
|
velocity = direction == Direction.Vertical ? -eventData.scrollDelta.y * rate : eventData.scrollDelta.x * rate;
|
|
position += velocity;
|
|
|
|
OnValueChanged?.Invoke(position);
|
|
Inertia();
|
|
Elastic();
|
|
}
|
|
|
|
internal virtual float GetDelta(PointerEventData eventData)
|
|
{
|
|
float rate = GetScrollRate();
|
|
return direction == Direction.Vertical ? eventData.delta.y * rate : -eventData.delta.x * rate;
|
|
}
|
|
|
|
private float GetScrollRate()
|
|
{
|
|
float rate = 1f;
|
|
if (position < 0)
|
|
{
|
|
rate = Mathf.Max(0, 1 - (Mathf.Abs(position) / ViewLength));
|
|
}
|
|
else if (position > MaxPosition)
|
|
{
|
|
rate = Mathf.Max(0, 1 - (Mathf.Abs(position - MaxPosition) / ViewLength));
|
|
}
|
|
return rate;
|
|
}
|
|
|
|
protected virtual void Inertia()
|
|
{
|
|
|
|
if (Mathf.Abs(velocity) > 0.1f)
|
|
{
|
|
StopMovement();
|
|
movementCoroutine = StartCoroutine(RunMotion(InertiaTo()));
|
|
}
|
|
else
|
|
{
|
|
OnMoveStoped?.Invoke();
|
|
}
|
|
}
|
|
|
|
protected virtual void Elastic()
|
|
{
|
|
if (position < 0)
|
|
{
|
|
StopMovement();
|
|
movementCoroutine = StartCoroutine(RunMotion(ElasticTo(0)));
|
|
}
|
|
else if (position > MaxPosition)
|
|
{
|
|
StopMovement();
|
|
movementCoroutine = StartCoroutine(RunMotion(ElasticTo(MaxPosition)));
|
|
}
|
|
}
|
|
|
|
IEnumerator InertiaTo()
|
|
{
|
|
float timer = 0f;
|
|
float p = position;
|
|
float v = velocity > 0 ? Mathf.Min(velocity, 100) : Mathf.Max(velocity, -100);
|
|
float duration = snap ? 0.1f : 1f;
|
|
while (timer < duration)
|
|
{
|
|
float y = (float)EaseUtil.EaseOutCirc(timer) * 40;
|
|
timer += Time.deltaTime;
|
|
position = p + y * v;
|
|
|
|
Elastic();
|
|
|
|
OnValueChanged?.Invoke(position);
|
|
|
|
yield return EndOfFrameYield;
|
|
}
|
|
|
|
OnMoveStoped?.Invoke();
|
|
}
|
|
|
|
IEnumerator ElasticTo(float targetPos)
|
|
{
|
|
yield return ToPosition(targetPos, 7);
|
|
}
|
|
|
|
IEnumerator MoveTo(float targetPos)
|
|
{
|
|
yield return ToPosition(targetPos, scrollSpeed);
|
|
}
|
|
|
|
IEnumerator ToPositionByDuration(float targetPos, float duration)
|
|
{
|
|
duration = Mathf.Max(duration, 0.0001f);
|
|
float startPos = position;
|
|
float elapsed = 0f;
|
|
while (elapsed < duration)
|
|
{
|
|
elapsed += Time.deltaTime;
|
|
float t = Mathf.Clamp01(elapsed / duration);
|
|
position = Mathf.Lerp(startPos, targetPos, t);
|
|
OnValueChanged?.Invoke(position);
|
|
yield return EndOfFrameYield;
|
|
}
|
|
|
|
position = targetPos;
|
|
OnValueChanged?.Invoke(position);
|
|
}
|
|
|
|
IEnumerator ToPosition(float targetPos, float speed)
|
|
{
|
|
float startPos = position;
|
|
float time = Time.deltaTime;
|
|
while (Mathf.Abs(targetPos - position) > 0.1f)
|
|
{
|
|
position = Mathf.Lerp(startPos, targetPos, time * speed);
|
|
OnValueChanged?.Invoke(position);
|
|
|
|
time += Time.deltaTime;
|
|
|
|
yield return EndOfFrameYield;
|
|
}
|
|
|
|
position = targetPos;
|
|
OnValueChanged?.Invoke(position);
|
|
}
|
|
|
|
private IEnumerator RunMotion(IEnumerator motion)
|
|
{
|
|
yield return motion;
|
|
movementCoroutine = null;
|
|
}
|
|
|
|
private void StopMovement()
|
|
{
|
|
if (movementCoroutine == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StopCoroutine(movementCoroutine);
|
|
movementCoroutine = null;
|
|
}
|
|
}
|
|
}
|