2016-12-22 02:50:42 +08:00
using System ;
2016-12-05 18:15:48 +08:00
using UnityEngine.Events ;
2016-12-22 02:50:42 +08:00
using UnityEngine.EventSystems ;
2016-12-05 18:15:48 +08:00
namespace UnityEngine.UI.Extensions
{
2016-12-30 23:56:00 +08:00
public class ScrollSnapBase : MonoBehaviour , IBeginDragHandler , IDragHandler , IPointerDownHandler , IPointerUpHandler
2016-12-05 18:15:48 +08:00
{
2016-12-22 00:52:19 +08:00
internal RectTransform _screensContainer ;
2016-12-31 23:14:39 +08:00
internal bool _isVertical ;
2016-12-05 18:15:48 +08:00
internal int _screens = 1 ;
2016-12-10 21:36:28 +08:00
internal float _scrollStartPosition ;
internal float _childSize ;
2016-12-31 23:14:39 +08:00
private float _childPos , _maskSize ;
internal Vector2 _childAnchorPoint ;
2016-12-05 18:15:48 +08:00
internal ScrollRect _scroll_rect ;
internal Vector3 _lerp_target ;
internal bool _lerp ;
2016-12-30 23:56:00 +08:00
internal bool _pointerDown = false ;
2016-12-31 23:14:39 +08:00
internal bool _settled = true ;
2016-12-22 00:52:19 +08:00
internal Vector3 _startPosition = new Vector3 ( ) ;
[Tooltip("The currently active page")]
internal int _currentPage ;
internal int _previousPage ;
2016-12-31 23:14:39 +08:00
internal int _halfNoVisibleItems ;
private int _bottomItem , _topItem ;
2016-12-30 06:03:04 +08:00
2016-12-05 18:15:48 +08:00
[Serializable]
public class SelectionChangeStartEvent : UnityEvent { }
[Serializable]
2016-12-19 06:51:16 +08:00
public class SelectionPageChangedEvent : UnityEvent < int > { }
[Serializable]
2016-12-30 23:53:44 +08:00
public class SelectionChangeEndEvent : UnityEvent < int > { }
2016-12-05 18:15:48 +08:00
2016-12-30 06:03:04 +08:00
[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(1, 8)]
public float PageStep = 1 ;
2016-12-10 21:36:28 +08:00
2016-12-05 18:15:48 +08:00
[Tooltip("The gameobject that contains toggles which suggest pagination. (optional)")]
public GameObject Pagination ;
[Tooltip("Button to go to the next page. (optional)")]
public GameObject NextButton ;
2016-12-30 06:03:04 +08:00
2016-12-05 18:15:48 +08:00
[Tooltip("Button to go to the previous page. (optional)")]
public GameObject PrevButton ;
2016-12-30 06:03:04 +08:00
2016-12-05 18:15:48 +08:00
[Tooltip("Transition speed between pages. (optional)")]
public float transitionSpeed = 7.5f ;
[Tooltip("Fast Swipe makes swiping page next / previous (optional)")]
public Boolean UseFastSwipe = false ;
2016-12-30 06:03:04 +08:00
2016-12-05 18:15:48 +08:00
[Tooltip("How far swipe has to travel to initiate a page change (optional)")]
public int FastSwipeThreshold = 100 ;
2016-12-30 06:03:04 +08:00
2016-12-22 00:52:19 +08:00
[Tooltip("Speed at which the ScrollRect will keep scrolling before slowing down and stopping (optional)")]
2016-12-05 18:15:48 +08:00
public int SwipeVelocityThreshold = 200 ;
2016-12-30 06:03:04 +08:00
[Tooltip("The visible bounds area, controls which items are visible/enabled. *Note Should use a RectMask. (optional)")]
public RectTransform MaskArea ;
2016-12-05 18:15:48 +08:00
2016-12-30 06:03:04 +08:00
[Tooltip("Pixel size to buffer arround Mask Area. (optional)")]
public float MaskBuffer = 1 ;
2016-12-05 18:15:48 +08:00
public int CurrentPage
{
get
{
2016-12-22 00:52:19 +08:00
return _currentPage ;
2016-12-05 18:15:48 +08:00
}
2016-12-19 06:51:16 +08:00
internal set
{
2017-01-04 20:16:11 +08:00
if ( ( value ! = _currentPage & & value > = 0 & & value < _screensContainer . childCount ) | | ( value = = 0 & & _screensContainer . childCount = = 0 ) )
2016-12-19 06:51:16 +08:00
{
2016-12-22 00:52:19 +08:00
_previousPage = _currentPage ;
_currentPage = value ;
if ( MaskArea ) UpdateVisible ( ) ;
2016-12-30 23:44:36 +08:00
ScreenChange ( ) ;
ChangeBulletsInfo ( _currentPage ) ;
2016-12-19 06:51:16 +08:00
}
}
2016-12-05 18:15:48 +08:00
}
2016-12-30 23:44:36 +08:00
[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 ;
2016-12-30 06:03:04 +08:00
[Tooltip("Scroll Snap children. (optional)\nEither place objects in the scene as children OR\nPrefabs in this array, NOT BOTH")]
public GameObject [ ] ChildObjects ;
2016-12-05 18:15:48 +08:00
[SerializeField]
2016-12-31 23:14:39 +08:00
[Tooltip("Event fires when a user starts to change the selection")]
2016-12-05 18:15:48 +08:00
private SelectionChangeStartEvent m_OnSelectionChangeStartEvent = new SelectionChangeStartEvent ( ) ;
public SelectionChangeStartEvent OnSelectionChangeStartEvent { get { return m_OnSelectionChangeStartEvent ; } set { m_OnSelectionChangeStartEvent = value ; } }
2016-12-19 06:51:16 +08:00
[SerializeField]
2016-12-31 23:14:39 +08:00
[Tooltip("Event fires as the page changes, while dragging or jumping")]
2016-12-19 06:51:16 +08:00
private SelectionPageChangedEvent m_OnSelectionPageChangedEvent = new SelectionPageChangedEvent ( ) ;
public SelectionPageChangedEvent OnSelectionPageChangedEvent { get { return m_OnSelectionPageChangedEvent ; } set { m_OnSelectionPageChangedEvent = value ; } }
2016-12-05 18:15:48 +08:00
[SerializeField]
2016-12-31 23:14:39 +08:00
[Tooltip("Event fires when the page settles after a user has dragged")]
2016-12-05 18:15:48 +08:00
private SelectionChangeEndEvent m_OnSelectionChangeEndEvent = new SelectionChangeEndEvent ( ) ;
public SelectionChangeEndEvent OnSelectionChangeEndEvent { get { return m_OnSelectionChangeEndEvent ; } set { m_OnSelectionChangeEndEvent = value ; } }
2016-12-19 06:51:16 +08:00
// Use this for initialization
void Awake ( )
{
_scroll_rect = gameObject . GetComponent < ScrollRect > ( ) ;
if ( _scroll_rect . horizontalScrollbar | | _scroll_rect . verticalScrollbar )
{
Debug . LogWarning ( "Warning, using scrollbars with the Scroll Snap controls is not advised as it causes unpredictable results" ) ;
}
2016-12-30 06:03:04 +08:00
if ( StartingScreen < 0 )
{
StartingScreen = 0 ;
}
2016-12-19 06:51:16 +08:00
_screensContainer = _scroll_rect . content ;
if ( ChildObjects ! = null & & ChildObjects . Length > 0 )
{
if ( _screensContainer . transform . childCount > 0 )
{
Debug . LogError ( "ScrollRect Content has children, this is not supported when using managed Child Objects\n Either remove the ScrollRect Content children or clear the ChildObjects array" ) ;
return ;
}
2016-12-22 01:51:38 +08:00
InitialiseChildObjectsFromArray ( ) ;
2016-12-19 06:51:16 +08:00
}
else
{
2016-12-22 01:51:38 +08:00
InitialiseChildObjectsFromScene ( ) ;
2016-12-19 06:51:16 +08:00
}
if ( NextButton )
NextButton . GetComponent < Button > ( ) . onClick . AddListener ( ( ) = > { NextScreen ( ) ; } ) ;
if ( PrevButton )
PrevButton . GetComponent < Button > ( ) . onClick . AddListener ( ( ) = > { PreviousScreen ( ) ; } ) ;
}
2016-12-22 01:51:38 +08:00
internal void InitialiseChildObjectsFromScene ( )
{
int childCount = _screensContainer . childCount ;
ChildObjects = new GameObject [ childCount ] ;
for ( int i = 0 ; i < childCount ; i + + )
{
ChildObjects [ i ] = _screensContainer . transform . GetChild ( i ) . gameObject ;
if ( MaskArea & & ChildObjects [ i ] . activeSelf )
{
ChildObjects [ i ] . SetActive ( false ) ;
}
}
}
internal void InitialiseChildObjectsFromArray ( )
{
int childCount = ChildObjects . Length ;
2016-12-30 23:44:36 +08:00
RectTransform childRect ;
GameObject child ;
2016-12-22 01:51:38 +08:00
for ( int i = 0 ; i < childCount ; i + + )
{
2016-12-30 23:44:36 +08:00
child = GameObject . Instantiate ( ChildObjects [ i ] ) ;
//Optionally, use original GO transform when initialising, by default will use parent RectTransform position/rotation
if ( UseParentTransform )
{
childRect = child . GetComponent < RectTransform > ( ) ;
childRect . rotation = _screensContainer . rotation ;
childRect . localScale = _screensContainer . localScale ;
childRect . position = _screensContainer . position ;
}
child . transform . SetParent ( _screensContainer . transform ) ;
ChildObjects [ i ] = child ;
2016-12-22 01:51:38 +08:00
if ( MaskArea & & ChildObjects [ i ] . activeSelf )
{
ChildObjects [ i ] . SetActive ( false ) ;
}
}
}
2016-12-31 23:14:39 +08:00
internal void UpdateVisible ( )
2016-12-19 06:51:16 +08:00
{
2016-12-31 23:14:39 +08:00
//If there are no objects in the scene or a mask, exit
2017-01-04 20:16:11 +08:00
if ( ! MaskArea | | ChildObjects = = null | | ChildObjects . Length < 1 | | _screensContainer . childCount < 1 )
{
return ;
}
2016-12-31 23:14:39 +08:00
_maskSize = _isVertical ? MaskArea . rect . height : MaskArea . rect . width ;
_halfNoVisibleItems = ( int ) Math . Round ( _maskSize / ( _childSize * MaskBuffer ) , MidpointRounding . AwayFromZero ) / 2 ;
_bottomItem = _topItem = 0 ;
//work out how many items below the current page can be visible
for ( int i = _halfNoVisibleItems + 1 ; i > 0 ; i - - )
2016-12-19 06:51:16 +08:00
{
2016-12-31 23:14:39 +08:00
_bottomItem = _currentPage - i < 0 ? 0 : i ;
if ( _bottomItem > 0 ) break ;
2016-12-19 06:51:16 +08:00
}
2016-12-31 23:14:39 +08:00
//work out how many items above the current page can be visible
for ( int i = _halfNoVisibleItems + 1 ; i > 0 ; i - - )
{
_topItem = _screensContainer . childCount - _currentPage - i < 0 ? 0 : i ;
if ( _topItem > 0 ) break ;
}
//Set the active items active
for ( int i = CurrentPage - _bottomItem ; i < CurrentPage + _topItem ; i + + )
2016-12-19 06:51:16 +08:00
{
2016-12-31 23:14:39 +08:00
try
{
ChildObjects [ i ] . SetActive ( true ) ;
}
catch
{
Debug . Log ( "Failed to setactive child [" + i + "]" ) ;
}
2016-12-19 06:51:16 +08:00
}
2016-12-22 00:52:19 +08:00
2016-12-31 23:14:39 +08:00
//Deactivate items out of visibility at the bottom of the ScrollRect Mask (only on scroll)
if ( _currentPage > _halfNoVisibleItems ) ChildObjects [ CurrentPage - _bottomItem ] . SetActive ( false ) ;
//Deactivate items out of visibility at the top of the ScrollRect Mask (only on scroll)
if ( _screensContainer . childCount - _currentPage > _topItem ) ChildObjects [ CurrentPage + _topItem ] . SetActive ( false ) ;
2016-12-19 06:51:16 +08:00
}
2016-12-05 18:15:48 +08:00
//Function for switching screens with buttons
public void NextScreen ( )
{
2016-12-22 00:52:19 +08:00
if ( _currentPage < _screens - 1 )
2016-12-05 18:15:48 +08:00
{
if ( ! _lerp ) StartScreenChange ( ) ;
_lerp = true ;
2016-12-22 00:52:19 +08:00
CurrentPage = _currentPage + 1 ;
GetPositionforPage ( _currentPage , ref _lerp_target ) ;
2016-12-05 18:15:48 +08:00
}
}
//Function for switching screens with buttons
public void PreviousScreen ( )
{
2016-12-22 00:52:19 +08:00
if ( _currentPage > 0 )
2016-12-05 18:15:48 +08:00
{
if ( ! _lerp ) StartScreenChange ( ) ;
_lerp = true ;
2016-12-22 00:52:19 +08:00
CurrentPage = _currentPage - 1 ;
GetPositionforPage ( _currentPage , ref _lerp_target ) ;
2016-12-05 18:15:48 +08:00
}
}
/// <summary>
/// Function for switching to a specific screen
/// *Note, this is based on a 0 starting index - 0 to x
/// </summary>
/// <param name="screenIndex">0 starting index of page to jump to</param>
public void GoToScreen ( int screenIndex )
{
if ( screenIndex < = _screens - 1 & & screenIndex > = 0 )
{
if ( ! _lerp ) StartScreenChange ( ) ;
_lerp = true ;
2016-12-19 06:51:16 +08:00
CurrentPage = screenIndex ;
2016-12-22 00:52:19 +08:00
GetPositionforPage ( _currentPage , ref _lerp_target ) ;
2016-12-05 18:15:48 +08:00
}
}
2016-12-31 23:14:39 +08:00
/// <summary>
/// Gets the closest page for the current Scroll Rect container position
/// </summary>
/// <param name="pos">Position to test, normally the Scroll Rect container Local position</param>
/// <returns>Closest Page number (zero indexed array value)</returns>
2016-12-10 21:36:28 +08:00
internal int GetPageforPosition ( Vector3 pos )
{
2016-12-31 23:14:39 +08:00
return _isVertical ?
2016-12-10 21:36:28 +08:00
- ( int ) Math . Round ( ( pos . y - _scrollStartPosition ) / _childSize ) :
- ( int ) Math . Round ( ( pos . x - _scrollStartPosition ) / _childSize ) ;
}
2016-12-31 23:14:39 +08:00
/// <summary>
/// Validates if the current Scroll Rect container position is within the bounds for a page
/// </summary>
/// <param name="pos">Position to test, normally the Scroll Rect container Local position</param>
/// <returns>True / False, is the position in the bounds of a page</returns>
internal bool IsRectSettledOnaPage ( Vector3 pos )
{
return _isVertical ?
- ( ( pos . y - _scrollStartPosition ) / _childSize ) = = - ( int ) Math . Round ( ( pos . y - _scrollStartPosition ) / _childSize ) :
- ( ( pos . x - _scrollStartPosition ) / _childSize ) = = - ( int ) Math . Round ( ( pos . x - _scrollStartPosition ) / _childSize ) ;
}
/// <summary>
/// Returns the local position for a child page based on the required page number
/// </summary>
/// <param name="page">Page that the position is required for (Zero indexed array value)</param>
/// <param name="target">Outputs the local position for the selected page</param>
2016-12-10 21:36:28 +08:00
internal void GetPositionforPage ( int page , ref Vector3 target )
{
_childPos = - _childSize * page ;
2016-12-31 23:14:39 +08:00
if ( _isVertical )
2016-12-10 21:36:28 +08:00
{
target . y = _childPos + _scrollStartPosition ;
}
else
{
target . x = _childPos + _scrollStartPosition ;
}
}
2016-12-31 23:14:39 +08:00
/// <summary>
/// Updates the _Lerp target to the closest page and updates the pagination bullets. Each control's update loop will then handle the move.
/// </summary>
2016-12-05 18:15:48 +08:00
internal void ScrollToClosestElement ( )
{
_lerp = true ;
2016-12-19 06:51:16 +08:00
CurrentPage = GetPageforPosition ( _screensContainer . localPosition ) ;
2016-12-22 00:52:19 +08:00
GetPositionforPage ( _currentPage , ref _lerp_target ) ;
ChangeBulletsInfo ( _currentPage ) ;
2016-12-05 18:15:48 +08:00
}
2016-12-31 23:14:39 +08:00
/// <summary>
/// changes the bullets on the bottom of the page - pagination
/// </summary>
/// <param name="targetScreen"></param>
2016-12-19 06:51:16 +08:00
internal void ChangeBulletsInfo ( int targetScreen )
2016-12-05 18:15:48 +08:00
{
if ( Pagination )
for ( int i = 0 ; i < Pagination . transform . childCount ; i + + )
{
2016-12-19 06:51:16 +08:00
Pagination . transform . GetChild ( i ) . GetComponent < Toggle > ( ) . isOn = ( targetScreen = = i )
2016-12-05 18:15:48 +08:00
? true
: false ;
}
}
2016-12-19 06:51:16 +08:00
private void OnValidate ( )
2016-12-05 18:15:48 +08:00
{
2017-01-03 02:17:18 +08:00
var children = gameObject . GetComponent < ScrollRect > ( ) . content . childCount ;
if ( children ! = 0 | | ChildObjects ! = null )
2016-12-05 18:15:48 +08:00
{
2017-01-03 02:17:18 +08:00
var childCount = ChildObjects = = null | | ChildObjects . Length = = 0 ? children : ChildObjects . Length ;
2016-12-30 06:03:04 +08:00
if ( StartingScreen > childCount - 1 )
{
StartingScreen = childCount - 1 ;
}
if ( StartingScreen < 0 )
{
StartingScreen = 0 ;
}
2016-12-05 18:15:48 +08:00
}
2016-12-30 06:03:04 +08:00
if ( MaskBuffer < = 0 )
2016-12-05 18:15:48 +08:00
{
2016-12-30 06:03:04 +08:00
MaskBuffer = 1 ;
2016-12-05 18:15:48 +08:00
}
2016-12-31 23:14:39 +08:00
if ( PageStep < 0 )
{
PageStep = 0 ;
}
if ( PageStep > 8 )
{
PageStep = 9 ;
}
2016-12-05 18:15:48 +08:00
}
2016-12-30 23:53:44 +08:00
/// <summary>
2016-12-30 23:56:00 +08:00
/// Event fires when the user starts to change the page, either via swipe or button.
2016-12-30 23:53:44 +08:00
/// </summary>
2016-12-05 18:15:48 +08:00
internal void StartScreenChange ( )
{
OnSelectionChangeStartEvent . Invoke ( ) ;
}
2016-12-30 23:53:44 +08:00
/// <summary>
/// Event fires when the currently viewed page changes, also updates while the scroll is moving
/// </summary>
2016-12-30 23:44:36 +08:00
internal void ScreenChange ( )
2016-12-19 06:51:16 +08:00
{
2016-12-22 00:52:19 +08:00
OnSelectionPageChangedEvent . Invoke ( _currentPage ) ;
2016-12-19 06:51:16 +08:00
}
2016-12-30 23:53:44 +08:00
/// <summary>
/// Event fires when control settles on a page, outputs the new page number
/// </summary>
2016-12-05 18:15:48 +08:00
internal void EndScreenChange ( )
{
2016-12-30 23:53:44 +08:00
OnSelectionChangeEndEvent . Invoke ( _currentPage ) ;
2016-12-31 23:14:39 +08:00
_settled = true ;
2016-12-05 18:15:48 +08:00
}
#region Interfaces
/// <summary>
/// Touch screen to start swiping
/// </summary>
/// <param name="eventData"></param>
public void OnBeginDrag ( PointerEventData eventData )
{
2016-12-31 23:14:39 +08:00
_settled = false ;
2016-12-05 18:15:48 +08:00
StartScreenChange ( ) ;
_startPosition = _screensContainer . localPosition ;
}
/// <summary>
/// While dragging do
/// </summary>
/// <param name="eventData"></param>
public void OnDrag ( PointerEventData eventData )
{
_lerp = false ;
}
2016-12-30 23:56:00 +08:00
public void OnPointerDown ( PointerEventData eventData )
{
_pointerDown = true ;
}
public void OnPointerUp ( PointerEventData eventData )
{
_pointerDown = false ;
}
2016-12-05 18:15:48 +08:00
# endregion
2016-12-10 21:36:28 +08:00
}
2016-12-05 18:15:48 +08:00
}