mirror of
https://github.com/RHeavenStudio/HeavenStudio.git
synced 2025-06-12 11:27:39 +02:00
Editor stuff
This commit is contained in:
@ -0,0 +1,194 @@
|
||||
/*
|
||||
* EasingCore (https://github.com/setchi/EasingCore)
|
||||
* Copyright (c) 2020 setchi
|
||||
* Licensed under MIT (https://github.com/setchi/EasingCore/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
namespace UnityEngine.UI.Extensions.EasingCore
|
||||
{
|
||||
public enum Ease
|
||||
{
|
||||
Linear,
|
||||
InBack,
|
||||
InBounce,
|
||||
InCirc,
|
||||
InCubic,
|
||||
InElastic,
|
||||
InExpo,
|
||||
InQuad,
|
||||
InQuart,
|
||||
InQuint,
|
||||
InSine,
|
||||
OutBack,
|
||||
OutBounce,
|
||||
OutCirc,
|
||||
OutCubic,
|
||||
OutElastic,
|
||||
OutExpo,
|
||||
OutQuad,
|
||||
OutQuart,
|
||||
OutQuint,
|
||||
OutSine,
|
||||
InOutBack,
|
||||
InOutBounce,
|
||||
InOutCirc,
|
||||
InOutCubic,
|
||||
InOutElastic,
|
||||
InOutExpo,
|
||||
InOutQuad,
|
||||
InOutQuart,
|
||||
InOutQuint,
|
||||
InOutSine,
|
||||
}
|
||||
|
||||
public delegate float EasingFunction(float t);
|
||||
|
||||
public static class Easing
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the easing function
|
||||
/// </summary>
|
||||
/// <param name="type">Ease type</param>
|
||||
/// <returns>Easing function</returns>
|
||||
public static EasingFunction Get(Ease type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case Ease.Linear: return linear;
|
||||
case Ease.InBack: return inBack;
|
||||
case Ease.InBounce: return inBounce;
|
||||
case Ease.InCirc: return inCirc;
|
||||
case Ease.InCubic: return inCubic;
|
||||
case Ease.InElastic: return inElastic;
|
||||
case Ease.InExpo: return inExpo;
|
||||
case Ease.InQuad: return inQuad;
|
||||
case Ease.InQuart: return inQuart;
|
||||
case Ease.InQuint: return inQuint;
|
||||
case Ease.InSine: return inSine;
|
||||
case Ease.OutBack: return outBack;
|
||||
case Ease.OutBounce: return outBounce;
|
||||
case Ease.OutCirc: return outCirc;
|
||||
case Ease.OutCubic: return outCubic;
|
||||
case Ease.OutElastic: return outElastic;
|
||||
case Ease.OutExpo: return outExpo;
|
||||
case Ease.OutQuad: return outQuad;
|
||||
case Ease.OutQuart: return outQuart;
|
||||
case Ease.OutQuint: return outQuint;
|
||||
case Ease.OutSine: return outSine;
|
||||
case Ease.InOutBack: return inOutBack;
|
||||
case Ease.InOutBounce: return inOutBounce;
|
||||
case Ease.InOutCirc: return inOutCirc;
|
||||
case Ease.InOutCubic: return inOutCubic;
|
||||
case Ease.InOutElastic: return inOutElastic;
|
||||
case Ease.InOutExpo: return inOutExpo;
|
||||
case Ease.InOutQuad: return inOutQuad;
|
||||
case Ease.InOutQuart: return inOutQuart;
|
||||
case Ease.InOutQuint: return inOutQuint;
|
||||
case Ease.InOutSine: return inOutSine;
|
||||
default: return linear;
|
||||
}
|
||||
|
||||
float linear(float t) => t;
|
||||
|
||||
float inBack(float t) => t * t * t - t * Mathf.Sin(t * Mathf.PI);
|
||||
|
||||
float outBack(float t) => 1f - inBack(1f - t);
|
||||
|
||||
float inOutBack(float t) =>
|
||||
t < 0.5f
|
||||
? 0.5f * inBack(2f * t)
|
||||
: 0.5f * outBack(2f * t - 1f) + 0.5f;
|
||||
|
||||
float inBounce(float t) => 1f - outBounce(1f - t);
|
||||
|
||||
float outBounce(float t) =>
|
||||
t < 4f / 11.0f ?
|
||||
(121f * t * t) / 16.0f :
|
||||
t < 8f / 11.0f ?
|
||||
(363f / 40.0f * t * t) - (99f / 10.0f * t) + 17f / 5.0f :
|
||||
t < 9f / 10.0f ?
|
||||
(4356f / 361.0f * t * t) - (35442f / 1805.0f * t) + 16061f / 1805.0f :
|
||||
(54f / 5.0f * t * t) - (513f / 25.0f * t) + 268f / 25.0f;
|
||||
|
||||
float inOutBounce(float t) =>
|
||||
t < 0.5f
|
||||
? 0.5f * inBounce(2f * t)
|
||||
: 0.5f * outBounce(2f * t - 1f) + 0.5f;
|
||||
|
||||
float inCirc(float t) => 1f - Mathf.Sqrt(1f - (t * t));
|
||||
|
||||
float outCirc(float t) => Mathf.Sqrt((2f - t) * t);
|
||||
|
||||
float inOutCirc(float t) =>
|
||||
t < 0.5f
|
||||
? 0.5f * (1 - Mathf.Sqrt(1f - 4f * (t * t)))
|
||||
: 0.5f * (Mathf.Sqrt(-((2f * t) - 3f) * ((2f * t) - 1f)) + 1f);
|
||||
|
||||
float inCubic(float t) => t * t * t;
|
||||
|
||||
float outCubic(float t) => inCubic(t - 1f) + 1f;
|
||||
|
||||
float inOutCubic(float t) =>
|
||||
t < 0.5f
|
||||
? 4f * t * t * t
|
||||
: 0.5f * inCubic(2f * t - 2f) + 1f;
|
||||
|
||||
float inElastic(float t) => Mathf.Sin(13f * (Mathf.PI * 0.5f) * t) * Mathf.Pow(2f, 10f * (t - 1f));
|
||||
|
||||
float outElastic(float t) => Mathf.Sin(-13f * (Mathf.PI * 0.5f) * (t + 1)) * Mathf.Pow(2f, -10f * t) + 1f;
|
||||
|
||||
float inOutElastic(float t) =>
|
||||
t < 0.5f
|
||||
? 0.5f * Mathf.Sin(13f * (Mathf.PI * 0.5f) * (2f * t)) * Mathf.Pow(2f, 10f * ((2f * t) - 1f))
|
||||
: 0.5f * (Mathf.Sin(-13f * (Mathf.PI * 0.5f) * ((2f * t - 1f) + 1f)) * Mathf.Pow(2f, -10f * (2f * t - 1f)) + 2f);
|
||||
|
||||
float inExpo(float t) => Mathf.Approximately(0.0f, t) ? t : Mathf.Pow(2f, 10f * (t - 1f));
|
||||
|
||||
float outExpo(float t) => Mathf.Approximately(1.0f, t) ? t : 1f - Mathf.Pow(2f, -10f * t);
|
||||
|
||||
float inOutExpo(float v) =>
|
||||
Mathf.Approximately(0.0f, v) || Mathf.Approximately(1.0f, v)
|
||||
? v
|
||||
: v < 0.5f
|
||||
? 0.5f * Mathf.Pow(2f, (20f * v) - 10f)
|
||||
: -0.5f * Mathf.Pow(2f, (-20f * v) + 10f) + 1f;
|
||||
|
||||
float inQuad(float t) => t * t;
|
||||
|
||||
float outQuad(float t) => -t * (t - 2f);
|
||||
|
||||
float inOutQuad(float t) =>
|
||||
t < 0.5f
|
||||
? 2f * t * t
|
||||
: -2f * t * t + 4f * t - 1f;
|
||||
|
||||
float inQuart(float t) => t * t * t * t;
|
||||
|
||||
float outQuart(float t)
|
||||
{
|
||||
var u = t - 1f;
|
||||
return u * u * u * (1f - t) + 1f;
|
||||
}
|
||||
|
||||
float inOutQuart(float t) =>
|
||||
t < 0.5f
|
||||
? 8f * inQuart(t)
|
||||
: -8f * inQuart(t - 1f) + 1f;
|
||||
|
||||
float inQuint(float t) => t * t * t * t * t;
|
||||
|
||||
float outQuint(float t) => inQuint(t - 1f) + 1f;
|
||||
|
||||
float inOutQuint(float t) =>
|
||||
t < 0.5f
|
||||
? 16f * inQuint(t)
|
||||
: 0.5f * inQuint(2f * t - 2f) + 1f;
|
||||
|
||||
float inSine(float t) => Mathf.Sin((t - 1f) * (Mathf.PI * 0.5f)) + 1f;
|
||||
|
||||
float outSine(float t) => Mathf.Sin(t * (Mathf.PI * 0.5f));
|
||||
|
||||
float inOutSine(float t) => 0.5f * (1f - Mathf.Cos(t * Mathf.PI));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b2a35f9ff1c8487582b74620e380486
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,13 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public enum MovementDirection
|
||||
{
|
||||
Left,
|
||||
Right,
|
||||
Up,
|
||||
Down,
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11769de5e972543c7a4132ceff94a5c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,12 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public enum MovementType
|
||||
{
|
||||
Unrestricted = ScrollRect.MovementType.Unrestricted,
|
||||
Elastic = ScrollRect.MovementType.Elastic,
|
||||
Clamped = ScrollRect.MovementType.Clamped
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 585de8f4f272547c89041f94f2ad92b8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,11 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
public enum ScrollDirection
|
||||
{
|
||||
Vertical,
|
||||
Horizontal,
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3392a30c085c642139c92e94ec0d6309
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,597 @@
|
||||
/// Credit setchi (https://github.com/setchi)
|
||||
/// Sourced from - https://github.com/setchi/FancyScrollView
|
||||
|
||||
using System;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI.Extensions.EasingCore;
|
||||
|
||||
namespace UnityEngine.UI.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// スクロール位置の制御を行うコンポーネント.
|
||||
/// </summary>
|
||||
public class Scroller : UIBehaviour, IPointerUpHandler, IPointerDownHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
|
||||
{
|
||||
[SerializeField] RectTransform viewport = default;
|
||||
|
||||
/// <summary>
|
||||
/// ビューポートのサイズ.
|
||||
/// </summary>
|
||||
public float ViewportSize => scrollDirection == ScrollDirection.Horizontal
|
||||
? viewport.rect.size.x
|
||||
: viewport.rect.size.y;
|
||||
|
||||
[SerializeField] ScrollDirection scrollDirection = ScrollDirection.Vertical;
|
||||
|
||||
/// <summary>
|
||||
/// スクロール方向.
|
||||
/// </summary>
|
||||
public ScrollDirection ScrollDirection => scrollDirection;
|
||||
|
||||
[SerializeField] MovementType movementType = MovementType.Elastic;
|
||||
|
||||
/// <summary>
|
||||
/// コンテンツがスクロール範囲を越えて移動するときに使用する挙動.
|
||||
/// </summary>
|
||||
public MovementType MovementType
|
||||
{
|
||||
get => movementType;
|
||||
set => movementType = value;
|
||||
}
|
||||
|
||||
[SerializeField] float elasticity = 0.1f;
|
||||
|
||||
/// <summary>
|
||||
/// コンテンツがスクロール範囲を越えて移動するときに使用する弾力性の量.
|
||||
/// </summary>
|
||||
public float Elasticity
|
||||
{
|
||||
get => elasticity;
|
||||
set => elasticity = value;
|
||||
}
|
||||
|
||||
[SerializeField] float scrollSensitivity = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ViewportSize"/> の端から端まで Drag したときのスクロール位置の変化量.
|
||||
/// </summary>
|
||||
public float ScrollSensitivity
|
||||
{
|
||||
get => scrollSensitivity;
|
||||
set => scrollSensitivity = value;
|
||||
}
|
||||
|
||||
[SerializeField] bool inertia = true;
|
||||
|
||||
/// <summary>
|
||||
/// 慣性を使用するかどうか. <c>true</c> を指定すると慣性が有効に, <c>false</c> を指定すると慣性が無効になります.
|
||||
/// </summary>
|
||||
public bool Inertia
|
||||
{
|
||||
get => inertia;
|
||||
set => inertia = value;
|
||||
}
|
||||
|
||||
[SerializeField] float decelerationRate = 0.03f;
|
||||
|
||||
/// <summary>
|
||||
/// スクロールの減速率. <see cref="Inertia"/> が <c>true</c> の場合のみ有効です.
|
||||
/// </summary>
|
||||
public float DecelerationRate
|
||||
{
|
||||
get => decelerationRate;
|
||||
set => decelerationRate = value;
|
||||
}
|
||||
|
||||
[SerializeField] Snap snap = new Snap {
|
||||
Enable = true,
|
||||
VelocityThreshold = 0.5f,
|
||||
Duration = 0.3f,
|
||||
Easing = Ease.InOutCubic
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// <c>true</c> ならスナップし, <c>false</c>ならスナップしません.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// スナップを有効にすると, 慣性でスクロールが止まる直前に最寄りのセルへ移動します.
|
||||
/// </remarks>
|
||||
public bool SnapEnabled
|
||||
{
|
||||
get => snap.Enable;
|
||||
set => snap.Enable = value;
|
||||
}
|
||||
|
||||
[SerializeField] bool draggable = true;
|
||||
|
||||
/// <summary>
|
||||
/// Drag 入力を受付けるかどうか.
|
||||
/// </summary>
|
||||
public bool Draggable
|
||||
{
|
||||
get => draggable;
|
||||
set => draggable = value;
|
||||
}
|
||||
|
||||
[SerializeField] Scrollbar scrollbar = default;
|
||||
|
||||
/// <summary>
|
||||
/// スクロールバーのオブジェクト.
|
||||
/// </summary>
|
||||
public Scrollbar Scrollbar => scrollbar;
|
||||
|
||||
/// <summary>
|
||||
/// 現在のスクロール位置.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public float Position
|
||||
{
|
||||
get => currentPosition;
|
||||
set
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
velocity = 0f;
|
||||
dragging = false;
|
||||
|
||||
UpdatePosition(value);
|
||||
}
|
||||
}
|
||||
|
||||
readonly AutoScrollState autoScrollState = new AutoScrollState();
|
||||
|
||||
Action<float> onValueChanged;
|
||||
Action<int> onSelectionChanged;
|
||||
|
||||
Vector2 beginDragPointerPosition;
|
||||
float scrollStartPosition;
|
||||
float prevPosition;
|
||||
float currentPosition;
|
||||
|
||||
int totalCount;
|
||||
|
||||
bool hold;
|
||||
bool scrolling;
|
||||
bool dragging;
|
||||
float velocity;
|
||||
|
||||
[Serializable]
|
||||
class Snap
|
||||
{
|
||||
public bool Enable;
|
||||
public float VelocityThreshold;
|
||||
public float Duration;
|
||||
public Ease Easing;
|
||||
}
|
||||
|
||||
static readonly EasingFunction DefaultEasingFunction = Easing.Get(Ease.OutCubic);
|
||||
|
||||
class AutoScrollState
|
||||
{
|
||||
public bool Enable;
|
||||
public bool Elastic;
|
||||
public float Duration;
|
||||
public EasingFunction EasingFunction;
|
||||
public float StartTime;
|
||||
public float EndPosition;
|
||||
|
||||
public Action OnComplete;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Enable = false;
|
||||
Elastic = false;
|
||||
Duration = 0f;
|
||||
StartTime = 0f;
|
||||
EasingFunction = DefaultEasingFunction;
|
||||
EndPosition = 0f;
|
||||
OnComplete = null;
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
OnComplete?.Invoke();
|
||||
Reset();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Start()
|
||||
{
|
||||
base.Start();
|
||||
|
||||
if (scrollbar)
|
||||
{
|
||||
scrollbar.onValueChanged.AddListener(x => UpdatePosition(x * (totalCount - 1f), false));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// スクロール位置が変化したときのコールバックを設定します.
|
||||
/// </summary>
|
||||
/// <param name="callback">スクロール位置が変化したときのコールバック.</param>
|
||||
public void OnValueChanged(Action<float> callback) => onValueChanged = callback;
|
||||
|
||||
/// <summary>
|
||||
/// 選択位置が変化したときのコールバックを設定します.
|
||||
/// </summary>
|
||||
/// <param name="callback">選択位置が変化したときのコールバック.</param>
|
||||
public void OnSelectionChanged(Action<int> callback) => onSelectionChanged = callback;
|
||||
|
||||
/// <summary>
|
||||
/// アイテムの総数を設定します.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <paramref name="totalCount"/> を元に最大スクロール位置を計算します.
|
||||
/// </remarks>
|
||||
/// <param name="totalCount">アイテムの総数.</param>
|
||||
public void SetTotalCount(int totalCount) => this.totalCount = totalCount;
|
||||
|
||||
/// <summary>
|
||||
/// 指定した位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
public void ScrollTo(float position, float duration, Action onComplete = null) => ScrollTo(position, duration, Ease.OutCubic, onComplete);
|
||||
|
||||
/// <summary>
|
||||
/// 指定した位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="easing">移動に使用するイージング.</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
public void ScrollTo(float position, float duration, Ease easing, Action onComplete = null) => ScrollTo(position, duration, Easing.Get(easing), onComplete);
|
||||
|
||||
/// <summary>
|
||||
/// 指定した位置まで移動します.
|
||||
/// </summary>
|
||||
/// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
|
||||
/// <param name="duration">移動にかける秒数.</param>
|
||||
/// <param name="easingFunction">移動に使用するイージング関数.</param>
|
||||
/// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
|
||||
public void ScrollTo(float position, float duration, EasingFunction easingFunction, Action onComplete = null)
|
||||
{
|
||||
if (duration <= 0f)
|
||||
{
|
||||
Position = CircularPosition(position, totalCount);
|
||||
onComplete?.Invoke();
|
||||
return;
|
||||
}
|
||||
|
||||
autoScrollState.Reset();
|
||||
autoScrollState.Enable = true;
|
||||
autoScrollState.Duration = duration;
|
||||
autoScrollState.EasingFunction = easingFunction ?? DefaultEasingFunction;
|
||||
autoScrollState.StartTime = Time.unscaledTime;
|
||||
autoScrollState.EndPosition = currentPosition + CalculateMovementAmount(currentPosition, position);
|
||||
autoScrollState.OnComplete = onComplete;
|
||||
|
||||
velocity = 0f;
|
||||
scrollStartPosition = currentPosition;
|
||||
|
||||
UpdateSelection(Mathf.RoundToInt(CircularPosition(autoScrollState.EndPosition, totalCount)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指定したインデックスの位置までジャンプします.
|
||||
/// </summary>
|
||||
/// <param name="index">アイテムのインデックス.</param>
|
||||
public void JumpTo(int index)
|
||||
{
|
||||
if (index < 0 || index > totalCount - 1)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(index));
|
||||
}
|
||||
|
||||
UpdateSelection(index);
|
||||
Position = index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <paramref name="sourceIndex"/> から <paramref name="destIndex"/> に移動する際の移動方向を返します.
|
||||
/// スクロール範囲が無制限に設定されている場合は, 最短距離の移動方向を返します.
|
||||
/// </summary>
|
||||
/// <param name="sourceIndex">移動元のインデックス.</param>
|
||||
/// <param name="destIndex">移動先のインデックス.</param>
|
||||
/// <returns></returns>
|
||||
public MovementDirection GetMovementDirection(int sourceIndex, int destIndex)
|
||||
{
|
||||
var movementAmount = CalculateMovementAmount(sourceIndex, destIndex);
|
||||
return scrollDirection == ScrollDirection.Horizontal
|
||||
? movementAmount > 0
|
||||
? MovementDirection.Left
|
||||
: MovementDirection.Right
|
||||
: movementAmount > 0
|
||||
? MovementDirection.Up
|
||||
: MovementDirection.Down;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hold = true;
|
||||
velocity = 0f;
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (hold && snap.Enable)
|
||||
{
|
||||
UpdateSelection(Mathf.Clamp(Mathf.RoundToInt(currentPosition), 0, totalCount - 1));
|
||||
ScrollTo(Mathf.RoundToInt(currentPosition), snap.Duration, snap.Easing);
|
||||
}
|
||||
|
||||
hold = false;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IScrollHandler.OnScroll(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var delta = eventData.scrollDelta;
|
||||
|
||||
// Down is positive for scroll events, while in UI system up is positive.
|
||||
delta.y *= -1;
|
||||
var scrollDelta = scrollDirection == ScrollDirection.Horizontal
|
||||
? Mathf.Abs(delta.y) > Mathf.Abs(delta.x)
|
||||
? delta.y
|
||||
: delta.x
|
||||
: Mathf.Abs(delta.x) > Mathf.Abs(delta.y)
|
||||
? delta.x
|
||||
: delta.y;
|
||||
|
||||
if (eventData.IsScrolling())
|
||||
{
|
||||
scrolling = true;
|
||||
}
|
||||
|
||||
var position = currentPosition + scrollDelta / ViewportSize * scrollSensitivity;
|
||||
if (movementType == MovementType.Clamped)
|
||||
{
|
||||
position += CalculateOffset(position);
|
||||
}
|
||||
|
||||
if (autoScrollState.Enable)
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
hold = false;
|
||||
RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
viewport,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out beginDragPointerPosition);
|
||||
|
||||
scrollStartPosition = currentPosition;
|
||||
dragging = true;
|
||||
autoScrollState.Reset();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IDragHandler.OnDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left || !dragging)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
|
||||
viewport,
|
||||
eventData.position,
|
||||
eventData.pressEventCamera,
|
||||
out var dragPointerPosition))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var pointerDelta = dragPointerPosition - beginDragPointerPosition;
|
||||
var position = (scrollDirection == ScrollDirection.Horizontal ? -pointerDelta.x : pointerDelta.y)
|
||||
/ ViewportSize
|
||||
* scrollSensitivity
|
||||
+ scrollStartPosition;
|
||||
|
||||
var offset = CalculateOffset(position);
|
||||
position += offset;
|
||||
|
||||
if (movementType == MovementType.Elastic)
|
||||
{
|
||||
if (offset != 0f)
|
||||
{
|
||||
position -= RubberDelta(offset, scrollSensitivity);
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
void IEndDragHandler.OnEndDrag(PointerEventData eventData)
|
||||
{
|
||||
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
float CalculateOffset(float position)
|
||||
{
|
||||
if (movementType == MovementType.Unrestricted)
|
||||
{
|
||||
return 0f;
|
||||
}
|
||||
|
||||
if (position < 0f)
|
||||
{
|
||||
return -position;
|
||||
}
|
||||
|
||||
if (position > totalCount - 1)
|
||||
{
|
||||
return totalCount - 1 - position;
|
||||
}
|
||||
|
||||
return 0f;
|
||||
}
|
||||
|
||||
void UpdatePosition(float position, bool updateScrollbar = true)
|
||||
{
|
||||
onValueChanged?.Invoke(currentPosition = position);
|
||||
|
||||
if (scrollbar && updateScrollbar)
|
||||
{
|
||||
scrollbar.value = Mathf.Clamp01(position / Mathf.Max(totalCount - 1f, 1e-4f));
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateSelection(int index) => onSelectionChanged?.Invoke(index);
|
||||
|
||||
float RubberDelta(float overStretching, float viewSize) =>
|
||||
(1 - 1 / (Mathf.Abs(overStretching) * 0.55f / viewSize + 1)) * viewSize * Mathf.Sign(overStretching);
|
||||
|
||||
void Update()
|
||||
{
|
||||
var deltaTime = Time.unscaledDeltaTime;
|
||||
var offset = CalculateOffset(currentPosition);
|
||||
|
||||
if (autoScrollState.Enable)
|
||||
{
|
||||
var position = 0f;
|
||||
|
||||
if (autoScrollState.Elastic)
|
||||
{
|
||||
position = Mathf.SmoothDamp(currentPosition, currentPosition + offset, ref velocity,
|
||||
elasticity, Mathf.Infinity, deltaTime);
|
||||
|
||||
if (Mathf.Abs(velocity) < 0.01f)
|
||||
{
|
||||
position = Mathf.Clamp(Mathf.RoundToInt(position), 0, totalCount - 1);
|
||||
velocity = 0f;
|
||||
autoScrollState.Complete();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var alpha = Mathf.Clamp01((Time.unscaledTime - autoScrollState.StartTime) /
|
||||
Mathf.Max(autoScrollState.Duration, float.Epsilon));
|
||||
position = Mathf.LerpUnclamped(scrollStartPosition, autoScrollState.EndPosition,
|
||||
autoScrollState.EasingFunction(alpha));
|
||||
|
||||
if (Mathf.Approximately(alpha, 1f))
|
||||
{
|
||||
autoScrollState.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
else if (!(dragging || scrolling) && (!Mathf.Approximately(offset, 0f) || !Mathf.Approximately(velocity, 0f)))
|
||||
{
|
||||
var position = currentPosition;
|
||||
|
||||
if (movementType == MovementType.Elastic && !Mathf.Approximately(offset, 0f))
|
||||
{
|
||||
autoScrollState.Reset();
|
||||
autoScrollState.Enable = true;
|
||||
autoScrollState.Elastic = true;
|
||||
|
||||
UpdateSelection(Mathf.Clamp(Mathf.RoundToInt(position), 0, totalCount - 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(currentPosition), snap.Duration, snap.Easing);
|
||||
}
|
||||
}
|
||||
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, totalCount - 1f))
|
||||
{
|
||||
velocity = 0f;
|
||||
UpdateSelection(Mathf.RoundToInt(position));
|
||||
}
|
||||
}
|
||||
|
||||
UpdatePosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
if (!autoScrollState.Enable && (dragging || scrolling) && inertia)
|
||||
{
|
||||
var newVelocity = (currentPosition - prevPosition) / deltaTime;
|
||||
velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10f);
|
||||
}
|
||||
|
||||
prevPosition = currentPosition;
|
||||
scrolling = false;
|
||||
}
|
||||
|
||||
float CalculateMovementAmount(float sourcePosition, float destPosition)
|
||||
{
|
||||
if (movementType != MovementType.Unrestricted)
|
||||
{
|
||||
return Mathf.Clamp(destPosition, 0, totalCount - 1) - sourcePosition;
|
||||
}
|
||||
|
||||
var amount = CircularPosition(destPosition, totalCount) - CircularPosition(sourcePosition, totalCount);
|
||||
|
||||
if (Mathf.Abs(amount) > totalCount * 0.5f)
|
||||
{
|
||||
amount = Mathf.Sign(-amount) * (totalCount - Mathf.Abs(amount));
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
float CircularPosition(float p, int size) => size < 1 ? 0 : p < 0 ? size - 1 + (p + 1) % size : p % size;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f0e995f494626a4f878eedbded37c8d
|
||||
timeCreated: 1487408581
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user