com.unity.uiextensions.nosa.../Runtime/Scripts/Layout/FancyScrollView/Scroller/Scroller.cs

597 lines
20 KiB
C#
Raw Normal View History

/// 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;
2019-11-16 22:49:21 +08:00
[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>
2019-11-16 22:49:21 +08:00
public bool SnapEnabled
{
get => snap.Enable;
set => snap.Enable = value;
}
[SerializeField] bool draggable = true;
/// <summary>
/// Drag 入力を受付けるかどうか.
/// </summary>
2019-11-16 22:49:21 +08:00
public bool Draggable
{
2019-11-16 22:49:21 +08:00
get => draggable;
set => draggable = value;
}
[SerializeField] Scrollbar scrollbar = default;
/// <summary>
/// スクロールバーのオブジェクト.
/// </summary>
2019-11-16 22:49:21 +08:00
public Scrollbar Scrollbar => scrollbar;
/// <summary>
/// 現在のスクロール位置.
/// </summary>
/// <value></value>
2019-11-16 22:49:21 +08:00
public float Position
{
2019-11-16 22:49:21 +08:00
get => currentPosition;
set
{
autoScrollState.Reset();
velocity = 0f;
dragging = false;
UpdatePosition(value);
}
}
2019-11-16 22:49:21 +08:00
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;
2019-11-16 22:49:21 +08:00
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;
2019-11-16 22:49:21 +08:00
public float EndPosition;
public Action OnComplete;
public void Reset()
{
Enable = false;
Elastic = false;
Duration = 0f;
StartTime = 0f;
EasingFunction = DefaultEasingFunction;
2019-11-16 22:49:21 +08:00
EndPosition = 0f;
OnComplete = null;
}
public void Complete()
{
OnComplete?.Invoke();
Reset();
}
}
2019-11-16 22:49:21 +08:00
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>
2019-11-16 22:49:21 +08:00
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)
{
2019-11-16 22:49:21 +08:00
Position = CircularPosition(position, totalCount);
onComplete?.Invoke();
return;
}
autoScrollState.Reset();
autoScrollState.Enable = true;
autoScrollState.Duration = duration;
autoScrollState.EasingFunction = easingFunction ?? DefaultEasingFunction;
autoScrollState.StartTime = Time.unscaledTime;
2019-11-16 22:49:21 +08:00
autoScrollState.EndPosition = currentPosition + CalculateMovementAmount(currentPosition, position);
autoScrollState.OnComplete = onComplete;
velocity = 0f;
2019-11-16 22:49:21 +08:00
scrollStartPosition = currentPosition;
2019-11-16 22:49:21 +08:00
UpdateSelection(Mathf.RoundToInt(CircularPosition(autoScrollState.EndPosition, totalCount)));
}
/// <summary>
/// 指定したインデックスの位置までジャンプします.
/// </summary>
/// <param name="index">アイテムのインデックス.</param>
public void JumpTo(int index)
{
2019-11-16 22:49:21 +08:00
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>
2019-11-16 22:49:21 +08:00
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)
{
Release 2.3.0 (#429) * Package upver for Development * Added OnHighlightChanged and OnPressChanged events Added getters and setters for Highlighted and Pressed * Patch fix for UILineRenderer * Update package preview release * Resolves issue where the lower range value would become stuck when moved to the max value position Resolves: https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/issues/381/cant-move-range-slider-if-low-is-moved-to * Updated Infinite scroll to work with content of different sizes * Clean-up and reset pivots on scene start * Patches from PR * Clean up range slider unused variables * Updated Dropdown list to NOT resize text Rect on draw * Upgraded RangeSlider to work in both Horizontal and Verticle setups, just like regular slider. Also fixed a minor issue with offset when dragging on the bar. # Conflicts: # Runtime/Scripts/Controls/RangeSlider.cs * Taking in fix from https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/132 * Applying PR manually, because Bitbucket https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/128 * Merged in fix manually because... Bitbucket https://bitbucket.org/UnityUIExtensions/unity-ui-extensions/pull-requests/130 * Remove old BitBucket Pipeline for GitHub * Fixes issue #398 where the Next / Previous buttons filed to work if the ScrollSnap was previously scrolling. Also renamed the Extension Methods scripts and added a new function. Resolves: #398 * Resolves #397 Moved OnValidate checks which redraw the component to the RectTransformDimensionsCHanged event * Updated UIParticleSystem access to Particles array to ensure it is more stable. Updated some #if statements to be better future proofed Resolves #360 * Fixed the UIConnector to safely handle when no parent canvas can be found. Resolves #392 * Fixed issue which allowed an item marked as NOT transferable to actually be transferred between lists Resolves #382 * Updated #if filter inclusion to 2019_1_OR_Newer resolves: - https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/411 * Updated UIVertical scroller to be 2022 compliant Resolves: - https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/410 * Updated Curly UI to wait until end of the frame to recalculate positions Also updated Editor script to work in 2022 Resolves: - https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/409 * Updated Depth Texture sampler in UI Particles Shaders Resolves: - https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/401 * Remove meta duplicates for HSVColour Picker * Add newly generated HSV picker meta files * Hard reset of Colour picker guids * Updated Points to always be an array of 1 when set to nothing. Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/295 * Updated Cooldown button to work with Keyboard input Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/171 * Added error handling around setting Unity UI Components for Vertical/Horizontal scrolling Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/296 * Protecting Remove too * Added SetArc method to UICircle as requested Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/280 * Marked ScrollPositionController as Obsolete, users should use the newer Scroller component instead, will be removed in a future release. Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/305 * Updated ScrollPositionControllerEditor as obsolete too * Removed unneeded size calculation which caused some issues with mixed height/width children. Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/380 * Resolved issue whereby the last row in a flow layout group would not size correctly. Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/357 * Updated all components using "LayoutGroup" to override their OnDisable feature to incorporate this fix: https://gist.github.com/randomize/73ca6d3b6aa7210073692eb5cabd537e Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/178 * Checking in new MinMaxSlider TODO - Finish Editor creator * Added Editor Menu Option to create a Min/Max slider Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/270 * Marked TileSizeFitter as obsolete as Unity has made this unworkable Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/214 * Updated Editor create options to add the correct Event System Input module for the Input system used, now or old. Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/378 * Updated Editor menu layout * Updated initialisation logic to not cause an endless loop in the TabNavigationHelper Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/208 * Added new FIFO based UI Line Render when dynamic line rendering is needed (basic, no Beziers) Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/324 * Clean-up of ScrollSnapBase * Updated "Action" use to "UnityAction" to avoid Unity issues Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/253 * Updated UIVerticalScroller for standards and added UIHorizontalScroller Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/205 * Updated ReorderableList/ReorderableListElement to prevent creating a "Fake" droppable when the item is not transferable Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/164 * Updated panel drawing for ComboBox controls and added DropdownOffset Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/393 * Base update for pointers to new version / package home * Cleanup and ensuring the UIParticleSystem is disposed on Destroy correctly. Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/412 * Refresh FancyScrollView with latest fixes * Remove broken examples link * Break Module * Update Examples module to new home * Updating GitHub artifacts and automation * Updated build issue with ReorderableListElement * Revised the Curly UI fix as it was preventing the graphic from being updated in the scene view. Thanks to @solidsign for the update. * Removed legacy Examples link, moving to separate repository * Added new submodule for extracted examples * Fix class spellings and update MultiTouchScrollRect * Updated NonDrawingGraphic to require a CanvasRender, else it causes an error on run (and doesn't work) - Resolves: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/420 * Add updated test flow for builds * Fix github issue templates * Add the Version upgrade pipeline * Added ResetSelectableHighlight component * Resolves issue in 2022 with the missing Text component Fixes: https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/424 * The BIG Unity 2022 Text reorganisation * Remove editor validation and add error checking for the ColorLabel component * Add 2019 to the testing validation * Switch android builds to windows * Several lifetime feature updates for the ComboBox controls: - Resolves startup issue that prevented the control being used (Unity changed the start order in some instances), this was causing null reference issues with comboboxes - Added the ability to set a specific item on start and not just the first - Added the ability to disable the dropdown to make a read-only dropdown Resolves: - https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/426 - https://github.com/Unity-UI-Extensions/com.unity.uiextensions/issues/425 * Resolved issues with DisplayAbove and using a 0 ItemsToDisplay * Update pipelines for release * Final checks for merge! --------- Co-authored-by: Robert Rioja <rrioja@immersivedisplayinc.com> Co-authored-by: Simon Jackson <darkside@xna-uk.net> Co-authored-by: Ben MacKinnon <bilmackinnon@googlemail.com> Co-authored-by: Simon Jackson <sjackson@ethar.com> Co-authored-by: action <action@users.noreply.github.com>
2023-02-07 22:35:43 +08:00
UpdateSelection(Mathf.RoundToInt(CircularPosition(currentPosition, totalCount)));
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)
{
2019-11-16 22:49:21 +08:00
if (!draggable || eventData.button != PointerEventData.InputButton.Left)
{
return;
}
hold = false;
RectTransformUtility.ScreenPointToLocalPointInRectangle(
viewport,
eventData.position,
eventData.pressEventCamera,
2019-11-16 22:49:21 +08:00
out beginDragPointerPosition);
2019-11-16 22:49:21 +08:00
scrollStartPosition = currentPosition;
dragging = true;
autoScrollState.Reset();
}
/// <inheritdoc/>
void IDragHandler.OnDrag(PointerEventData eventData)
{
2019-11-16 22:49:21 +08:00
if (!draggable || eventData.button != PointerEventData.InputButton.Left || !dragging)
{
return;
}
if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
viewport,
eventData.position,
eventData.pressEventCamera,
2019-11-16 22:49:21 +08:00
out var dragPointerPosition))
{
return;
}
2019-11-16 22:49:21 +08:00
var pointerDelta = dragPointerPosition - beginDragPointerPosition;
var position = (scrollDirection == ScrollDirection.Horizontal ? -pointerDelta.x : pointerDelta.y)
/ ViewportSize
* scrollSensitivity
2019-11-16 22:49:21 +08:00
+ 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)
{
2019-11-16 22:49:21 +08:00
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;
}
2019-11-16 22:49:21 +08:00
void UpdatePosition(float position, bool updateScrollbar = true)
{
2019-11-16 22:49:21 +08:00
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;
2019-11-16 22:49:21 +08:00
var offset = CalculateOffset(currentPosition);
if (autoScrollState.Enable)
{
var position = 0f;
if (autoScrollState.Elastic)
{
2019-11-16 22:49:21 +08:00
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) /
2019-11-16 22:49:21 +08:00
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)))
{
2019-11-16 22:49:21 +08:00
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)
{
2019-11-16 22:49:21 +08:00
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)
{
2019-11-16 22:49:21 +08:00
var newVelocity = (currentPosition - prevPosition) / deltaTime;
velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10f);
}
2019-11-16 22:49:21 +08:00
prevPosition = currentPosition;
scrolling = false;
}
2019-11-16 22:49:21 +08:00
float CalculateMovementAmount(float sourcePosition, float destPosition)
{
2019-11-16 22:49:21 +08:00
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;
}
2019-11-16 22:49:21 +08:00
}