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:53:44 +08:00
public class ScrollSnapBase : MonoBehaviour , IBeginDragHandler , IDragHandler
2016-12-05 18:15:48 +08:00
{
2016-12-22 00:52:19 +08:00
internal RectTransform _screensContainer ;
2016-12-10 21:36:28 +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 ;
private float _childPos ;
2016-12-30 06:03:04 +08:00
internal Vector2 childAnchorPoint ;
2016-12-05 18:15:48 +08:00
internal ScrollRect _scroll_rect ;
internal Vector3 _lerp_target ;
internal bool _lerp ;
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-30 06:03:04 +08:00
internal int HalfNoVisibleItems ;
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
{
2016-12-22 00:52:19 +08:00
if ( value ! = _currentPage )
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]
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]
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]
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-19 06:51:16 +08:00
internal void CalculateVisible ( )
{
float MaskSize = isVertical ? MaskArea . rect . height : MaskArea . rect . width ;
HalfNoVisibleItems = ( int ) Math . Round ( MaskSize / ( _childSize * MaskBuffer ) , MidpointRounding . AwayFromZero ) / 2 + 2 ;
int StartingItemsBefore = StartingScreen - HalfNoVisibleItems < 0 ? 0 : HalfNoVisibleItems ;
int StartingItemsAfter = _screensContainer . childCount - StartingScreen < HalfNoVisibleItems ? _screensContainer . childCount - StartingScreen : HalfNoVisibleItems ;
for ( int i = StartingScreen - StartingItemsBefore ; i < StartingScreen + StartingItemsAfter - 1 ; i + + )
{
ChildObjects [ i ] . SetActive ( true ) ;
}
}
2016-12-22 01:51:38 +08:00
internal void UpdateVisible ( )
2016-12-19 06:51:16 +08:00
{
2016-12-30 06:03:04 +08:00
//If there are no objects in the scene, exit
if ( ChildObjects = = null | | ChildObjects . Length < 1 | | _screensContainer . childCount < 1 ) return ;
2016-12-22 00:52:19 +08:00
int BottomItem = _currentPage - HalfNoVisibleItems < 0 ? 0 : HalfNoVisibleItems ;
int TopItem = _screensContainer . childCount - _currentPage < HalfNoVisibleItems ? _screensContainer . childCount - _currentPage : HalfNoVisibleItems ;
2016-12-19 06:51:16 +08:00
for ( int i = CurrentPage - BottomItem ; i < CurrentPage + TopItem ; i + + )
{
ChildObjects [ i ] . SetActive ( true ) ;
}
2016-12-22 00:52:19 +08:00
if ( _screensContainer . childCount - _currentPage > HalfNoVisibleItems + 1 ) ChildObjects [ CurrentPage + TopItem + 1 ] . SetActive ( false ) ;
if ( _currentPage - HalfNoVisibleItems > 0 ) ChildObjects [ CurrentPage - BottomItem - 1 ] . 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-10 21:36:28 +08:00
internal int GetPageforPosition ( Vector3 pos )
{
return isVertical ?
- ( int ) Math . Round ( ( pos . y - _scrollStartPosition ) / _childSize ) :
- ( int ) Math . Round ( ( pos . x - _scrollStartPosition ) / _childSize ) ;
}
internal void GetPositionforPage ( int page , ref Vector3 target )
{
_childPos = - _childSize * page ;
if ( isVertical )
{
target . y = _childPos + _scrollStartPosition ;
}
else
{
target . x = _childPos + _scrollStartPosition ;
}
}
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
}
//changes the bullets on the bottom of the page - pagination
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
{
2016-12-30 06:03:04 +08:00
if ( _screensContainer | | ChildObjects ! = null )
2016-12-05 18:15:48 +08:00
{
2016-12-30 06:03:04 +08:00
var childCount = ChildObjects = = null ? _screensContainer . childCount : ChildObjects . Length ;
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-30 23:53:44 +08:00
/// <summary>
/// Event fires when the user starts to change the page, either via swipe or button
/// </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-05 18:15:48 +08:00
}
#region Interfaces
/// <summary>
/// Touch screen to start swiping
/// </summary>
/// <param name="eventData"></param>
public void OnBeginDrag ( PointerEventData eventData )
{
StartScreenChange ( ) ;
_startPosition = _screensContainer . localPosition ;
}
/// <summary>
/// While dragging do
/// </summary>
/// <param name="eventData"></param>
public void OnDrag ( PointerEventData eventData )
{
_lerp = false ;
}
# endregion
2016-12-10 21:36:28 +08:00
}
2016-12-05 18:15:48 +08:00
}