/// Credit BinaryX /// Sourced from - http://forum.unity3d.com/threads/scripts-useful-4-6-scripts-collection.264161/page-2#post-1945602 /// Updated by simonDarksideJ - removed dependency on a custom ScrollRect script. Now implements drag interfaces and standard Scroll Rect. using System; using UnityEngine.Events; using UnityEngine.EventSystems; namespace UnityEngine.UI.Extensions { public class ScrollSnapBase : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IScrollSnap, IPointerClickHandler { internal Rect panelDimensions; internal RectTransform _screensContainer; internal bool _isVertical; internal int _screens = 1; internal float _scrollStartPosition; internal float _childSize; private float _childPos, _maskSize; internal Vector2 _childAnchorPoint; internal ScrollRect _scroll_rect; internal Vector3 _lerp_target; internal bool _lerp; internal bool _pointerDown = false; internal bool _settled = true; internal Vector3 _startPosition = new Vector3(); [Tooltip("The currently active page")] internal int _currentPage; internal int _previousPage; internal int _halfNoVisibleItems; internal bool _isInfinite; // Is a UI Infinite scroller attached to the control internal int _infiniteWindow; // The infinite window the control is in internal float _infiniteOffset; // How much to offset a repositioning private int _bottomItem, _topItem; internal bool _startEventCalled = false; internal bool _endEventCalled = false; internal bool _suspendEvents = false; [Serializable] public class SelectionChangeStartEvent : UnityEvent { } [Serializable] public class SelectionPageChangedEvent : UnityEvent { } [Serializable] public class SelectionChangeEndEvent : UnityEvent { } [Tooltip("The screen / page to start the control on\n*Note, this is a 0 indexed array")] [SerializeField] public int StartingScreen = 0; [Tooltip("The distance between two pages based on page height, by default pages are next to each other")] [SerializeField] [Range(0, 8)] public float PageStep = 1; [Tooltip("The gameobject that contains toggles which suggest pagination. (optional)")] public GameObject Pagination; [Tooltip("Button to go to the previous page. (optional)")] public GameObject PrevButton; [Tooltip("Button to go to the next page. (optional)")] public GameObject NextButton; [Tooltip("Transition speed between pages. (optional)")] public float transitionSpeed = 7.5f; [Tooltip("Hard Swipe forces to swiping to the next / previous page (optional)")] public Boolean UseHardSwipe = false; [Tooltip("Fast Swipe makes swiping page next / previous (optional)")] public Boolean UseFastSwipe = false; [Tooltip("Swipe Delta Threshold looks at the speed of input to decide if a swipe will be initiated (optional)")] public Boolean UseSwipeDeltaThreshold = false; [Tooltip("Offset for how far a swipe has to travel to initiate a page change (optional)")] public int FastSwipeThreshold = 100; [Tooltip("Speed at which the ScrollRect will keep scrolling before slowing down and stopping (optional)")] public int SwipeVelocityThreshold = 100; [Tooltip("Threshold for swipe speed to initiate a swipe, below threshold will return to closest page (optional)")] public float SwipeDeltaThreshold = 5.0f; [Tooltip("Use time scale instead of unscaled time (optional)")] public Boolean UseTimeScale = true; [Tooltip("The visible bounds area, controls which items are visible/enabled. *Note Should use a RectMask. (optional)")] public RectTransform MaskArea; [Tooltip("Pixel size to buffer around Mask Area. (optional)")] public float MaskBuffer = 1; public int CurrentPage { get { return _currentPage; } internal set { if (_isInfinite) { //Work out which infinite window we are in float infWindow = (float)value / (float)_screensContainer.childCount; if (infWindow < 0) { _infiniteWindow = (int)(Math.Floor(infWindow)); } else { _infiniteWindow = value / _screensContainer.childCount; } //Invert the value if negative and differentiate from Window 0 _infiniteWindow = value < 0 ? (-_infiniteWindow) : _infiniteWindow; //Calculate the page within the child count range value = value % _screensContainer.childCount; if (value < 0) { value = _screensContainer.childCount + value; } else if (value > _screensContainer.childCount - 1) { value = value - _screensContainer.childCount; } } if ((value != _currentPage && value >= 0 && value < _screensContainer.childCount) || (value == 0 && _screensContainer.childCount == 0)) { _previousPage = _currentPage; _currentPage = value; if (MaskArea) UpdateVisible(); if (!_lerp) ScreenChange(); OnCurrentScreenChange(_currentPage); } } } [Tooltip("By default the container will lerp to the start when enabled in the scene, this option overrides this and forces it to simply jump without lerping")] public bool JumpOnEnable = false; [Tooltip("By default the container will return to the original starting page when enabled, this option overrides this behaviour and stays on the current selection")] public bool RestartOnEnable = false; [Tooltip("(Experimental)\nBy default, child array objects will use the parent transform\nHowever you can disable this for some interesting effects")] public bool UseParentTransform = true; [Tooltip("Scroll Snap children. (optional)\nEither place objects in the scene as children OR\nPrefabs in this array, NOT BOTH")] public GameObject[] ChildObjects; [SerializeField] [Tooltip("Event fires when a user starts to change the selection")] private SelectionChangeStartEvent m_OnSelectionChangeStartEvent = new SelectionChangeStartEvent(); public SelectionChangeStartEvent OnSelectionChangeStartEvent { get { return m_OnSelectionChangeStartEvent; } set { m_OnSelectionChangeStartEvent = value; } } [SerializeField] [Tooltip("Event fires as the page changes, while dragging or jumping")] private SelectionPageChangedEvent m_OnSelectionPageChangedEvent = new SelectionPageChangedEvent(); public SelectionPageChangedEvent OnSelectionPageChangedEvent { get { return m_OnSelectionPageChangedEvent; } set { m_OnSelectionPageChangedEvent = value; } } [SerializeField] [Tooltip("Event fires when the page settles after a user has dragged")] private SelectionChangeEndEvent m_OnSelectionChangeEndEvent = new SelectionChangeEndEvent(); public SelectionChangeEndEvent OnSelectionChangeEndEvent { get { return m_OnSelectionChangeEndEvent; } set { m_OnSelectionChangeEndEvent = value; } } // Use this for initialization void Awake() { if (_scroll_rect == null) { _scroll_rect = gameObject.GetComponent(); } if (_scroll_rect.horizontalScrollbar && _scroll_rect.horizontal) { var hscroll = _scroll_rect.horizontalScrollbar.gameObject.AddComponent(); hscroll.ss = this; } if (_scroll_rect.verticalScrollbar && _scroll_rect.vertical) { var vscroll = _scroll_rect.verticalScrollbar.gameObject.AddComponent(); vscroll.ss = this; } panelDimensions = gameObject.GetComponent().rect; if (StartingScreen < 0) { StartingScreen = 0; } _screensContainer = _scroll_rect.content; InitialiseChildObjects(); if (NextButton) NextButton.GetComponent