Tanoda
ScrollSnapBase.cs
Go to the documentation of this file.
1
4
5using System;
8
10{
11 public class ScrollSnapBase : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler, IScrollSnap, IPointerClickHandler
12 {
13 internal Rect panelDimensions;
14 internal RectTransform _screensContainer;
15 internal bool _isVertical;
16
17 internal int _screens = 1;
18
19 internal float _scrollStartPosition;
20 internal float _childSize;
21 private float _childPos, _maskSize;
22 internal Vector2 _childAnchorPoint;
23 internal ScrollRect _scroll_rect;
24 internal Vector3 _lerp_target;
25 internal bool _lerp;
26 internal bool _pointerDown = false;
27 internal bool _settled = true;
28 internal Vector3 _startPosition = new Vector3();
29 [Tooltip("The currently active page")]
30 internal int _currentPage;
31 internal int _previousPage;
32 internal int _halfNoVisibleItems;
33 internal bool _moveStarted;
34 internal bool _isInfinite; // Is a UI Infinite scroller attached to the control
35 internal int _infiniteWindow; // The infinite window the control is in
36 internal float _infiniteOffset; // How much to offset a repositioning
37 private int _bottomItem, _topItem;
38
39 [Serializable]
40 public class SelectionChangeStartEvent : UnityEvent { }
41 [Serializable]
42 public class SelectionPageChangedEvent : UnityEvent<int> { }
43 [Serializable]
44 public class SelectionChangeEndEvent : UnityEvent<int> { }
45
46 [Tooltip("The screen / page to start the control on\n*Note, this is a 0 indexed array")]
47 [SerializeField]
48 public int StartingScreen = 0;
49
50 [Tooltip("The distance between two pages based on page height, by default pages are next to each other")]
51 [SerializeField]
52 [Range(0, 8)]
53 public float PageStep = 1;
54
55 [Tooltip("The gameobject that contains toggles which suggest pagination. (optional)")]
56 public GameObject Pagination;
57
58 [Tooltip("Button to go to the previous page. (optional)")]
59 public GameObject PrevButton;
60
61 [Tooltip("Button to go to the next page. (optional)")]
62 public GameObject NextButton;
63
64 [Tooltip("Transition speed between pages. (optional)")]
65 public float transitionSpeed = 7.5f;
66
67 [Tooltip("Hard Swipe forces to swiping to the next / previous page (optional)")]
68 public Boolean UseHardSwipe = false;
69
70 [Tooltip("Fast Swipe makes swiping page next / previous (optional)")]
71 public Boolean UseFastSwipe = false;
72
73 [Tooltip("Swipe Delta Threshold looks at the speed of input to decide if a swipe will be initiated (optional)")]
74 public Boolean UseSwipeDeltaThreshold = false;
75
76 [Tooltip("Offset for how far a swipe has to travel to initiate a page change (optional)")]
77 public int FastSwipeThreshold = 100;
78
79 [Tooltip("Speed at which the ScrollRect will keep scrolling before slowing down and stopping (optional)")]
80 public int SwipeVelocityThreshold = 100;
81
82 [Tooltip("Threshold for swipe speed to initiate a swipe, below threshold will return to closest page (optional)")]
83 public float SwipeDeltaThreshold = 5.0f;
84
85 [Tooltip("Use time scale instead of unscaled time (optional)")]
86 public Boolean UseTimeScale = true;
87
88 [Tooltip("The visible bounds area, controls which items are visible/enabled. *Note Should use a RectMask. (optional)")]
89 public RectTransform MaskArea;
90
91 [Tooltip("Pixel size to buffer around Mask Area. (optional)")]
92 public float MaskBuffer = 1;
93
94 public int CurrentPage
95 {
96 get
97 {
98 return _currentPage;
99 }
100
101 internal set
102 {
103 if (_isInfinite)
104 {
105 //Work out which infinite window we are in
106 float infWindow = (float)value / (float)_screensContainer.childCount;
107
108 if (infWindow < 0)
109 {
110 _infiniteWindow = (int)(Math.Floor(infWindow));
111 }
112 else
113 {
114 _infiniteWindow = value / _screensContainer.childCount;
115 }
116 //Invert the value if negative and differentiate from Window 0
117 _infiniteWindow = value < 0 ? (-_infiniteWindow) : _infiniteWindow;
118
119 //Calculate the page within the child count range
120 value = value % _screensContainer.childCount;
121 if (value < 0)
122 {
123 value = _screensContainer.childCount + value;
124 }
125 else if (value > _screensContainer.childCount - 1)
126 {
127 value = value - _screensContainer.childCount;
128 }
129 }
130 if ((value != _currentPage && value >= 0 && value < _screensContainer.childCount) || (value == 0 && _screensContainer.childCount == 0))
131 {
132 _previousPage = _currentPage;
133 _currentPage = value;
134 if (MaskArea) UpdateVisible();
135 if (!_lerp) ScreenChange();
136 OnCurrentScreenChange(_currentPage);
137 }
138 }
139 }
140
141 [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")]
142 public bool JumpOnEnable = false;
143
144 [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")]
145 public bool RestartOnEnable = false;
146
147 [Tooltip("(Experimental)\nBy default, child array objects will use the parent transform\nHowever you can disable this for some interesting effects")]
148 public bool UseParentTransform = true;
149
150 [Tooltip("Scroll Snap children. (optional)\nEither place objects in the scene as children OR\nPrefabs in this array, NOT BOTH")]
151 public GameObject[] ChildObjects;
152
153 [SerializeField]
154 [Tooltip("Event fires when a user starts to change the selection")]
155 private SelectionChangeStartEvent m_OnSelectionChangeStartEvent = new SelectionChangeStartEvent();
156 public SelectionChangeStartEvent OnSelectionChangeStartEvent { get { return m_OnSelectionChangeStartEvent; } set { m_OnSelectionChangeStartEvent = value; } }
157
158 [SerializeField]
159 [Tooltip("Event fires as the page changes, while dragging or jumping")]
160 private SelectionPageChangedEvent m_OnSelectionPageChangedEvent = new SelectionPageChangedEvent();
161 public SelectionPageChangedEvent OnSelectionPageChangedEvent { get { return m_OnSelectionPageChangedEvent; } set { m_OnSelectionPageChangedEvent = value; } }
162
163 [SerializeField]
164 [Tooltip("Event fires when the page settles after a user has dragged")]
165 private SelectionChangeEndEvent m_OnSelectionChangeEndEvent = new SelectionChangeEndEvent();
166 public SelectionChangeEndEvent OnSelectionChangeEndEvent { get { return m_OnSelectionChangeEndEvent; } set { m_OnSelectionChangeEndEvent = value; } }
167
168 // Use this for initialization
169 void Awake()
170 {
171 if (_scroll_rect == null)
172 {
173 _scroll_rect = gameObject.GetComponent<ScrollRect>();
174 }
175 if (_scroll_rect.horizontalScrollbar && _scroll_rect.horizontal)
176 {
177 var hscroll = _scroll_rect.horizontalScrollbar.gameObject.AddComponent<ScrollSnapScrollbarHelper>();
178 hscroll.ss = this;
179 }
180 if (_scroll_rect.verticalScrollbar && _scroll_rect.vertical)
181 {
182 var vscroll = _scroll_rect.verticalScrollbar.gameObject.AddComponent<ScrollSnapScrollbarHelper>();
183 vscroll.ss = this;
184 }
185 panelDimensions = gameObject.GetComponent<RectTransform>().rect;
186
187 if (StartingScreen < 0)
188 {
189 StartingScreen = 0;
190 }
191
192 _screensContainer = _scroll_rect.content;
193
194 InitialiseChildObjects();
195
196 if (NextButton)
197 NextButton.GetComponent<Button>().onClick.AddListener(() => { NextScreen(); });
198
199 if (PrevButton)
200 PrevButton.GetComponent<Button>().onClick.AddListener(() => { PreviousScreen(); });
201
202 _isInfinite = GetComponent<UI_InfiniteScroll>() != null;
203 }
204
205 internal void InitialiseChildObjects()
206 {
207 if (ChildObjects != null && ChildObjects.Length > 0)
208 {
209 if (_screensContainer.transform.childCount > 0)
210 {
211 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");
212 return;
213 }
214
215 InitialiseChildObjectsFromArray();
216
217 if (GetComponent<UI_InfiniteScroll>() != null)
218 {
219 GetComponent<UI_InfiniteScroll>().Init();
220 }
221 }
222 else
223 {
224 InitialiseChildObjectsFromScene();
225 }
226 }
227
228 internal void InitialiseChildObjectsFromScene()
229 {
230 int childCount = _screensContainer.childCount;
231 ChildObjects = new GameObject[childCount];
232 for (int i = 0; i < childCount; i++)
233 {
234 ChildObjects[i] = _screensContainer.transform.GetChild(i).gameObject;
235 if (MaskArea && ChildObjects[i].activeSelf)
236 {
237 ChildObjects[i].SetActive(false);
238 }
239 }
240 }
241
242 internal void InitialiseChildObjectsFromArray()
243 {
244 int childCount = ChildObjects.Length;
245 RectTransform childRect;
246 GameObject child;
247 for (int i = 0; i < childCount; i++)
248 {
249 child = GameObject.Instantiate(ChildObjects[i]);
250 //Optionally, use original GO transform when initialising, by default will use parent RectTransform position/rotation
252 {
253 childRect = child.GetComponent<RectTransform>();
254 childRect.rotation = _screensContainer.rotation;
255 childRect.localScale = _screensContainer.localScale;
256 childRect.position = _screensContainer.position;
257 }
258
259 child.transform.SetParent(_screensContainer.transform);
260 ChildObjects[i] = child;
261 if (MaskArea && ChildObjects[i].activeSelf)
262 {
263 ChildObjects[i].SetActive(false);
264 }
265 }
266 }
267
268 internal void UpdateVisible()
269 {
270 //If there are no objects in the scene or a mask, exit
271 if (!MaskArea || ChildObjects == null || ChildObjects.Length < 1 || _screensContainer.childCount < 1)
272 {
273 return;
274 }
275
276 _maskSize = _isVertical ? MaskArea.rect.height : MaskArea.rect.width;
277 _halfNoVisibleItems = (int)Math.Round(_maskSize / (_childSize * MaskBuffer), MidpointRounding.AwayFromZero) / 2;
278 _bottomItem = _topItem = 0;
279 //work out how many items below the current page can be visible
280 for (int i = _halfNoVisibleItems + 1; i > 0; i--)
281 {
282 _bottomItem = _currentPage - i < 0 ? 0 : i;
283 if (_bottomItem > 0) break;
284 }
285
286 //work out how many items above the current page can be visible
287 for (int i = _halfNoVisibleItems + 1; i > 0; i--)
288 {
289 _topItem = _screensContainer.childCount - _currentPage - i < 0 ? 0 : i;
290 if (_topItem > 0) break;
291 }
292
293 //Set the active items active
294 for (int i = CurrentPage - _bottomItem; i < CurrentPage + _topItem; i++)
295 {
296 try
297 {
298 ChildObjects[i].SetActive(true);
299 }
300 catch
301 {
302 Debug.Log("Failed to setactive child [" + i + "]");
303 }
304 }
305
306 //Deactivate items out of visibility at the bottom of the ScrollRect Mask (only on scroll)
307 if (_currentPage > _halfNoVisibleItems) ChildObjects[CurrentPage - _bottomItem].SetActive(false);
308 //Deactivate items out of visibility at the top of the ScrollRect Mask (only on scroll)
309 if (_screensContainer.childCount - _currentPage > _topItem) ChildObjects[CurrentPage + _topItem].SetActive(false);
310 }
311
312 //Function for switching screens with buttons
313 public void NextScreen()
314 {
315 if (_currentPage < _screens - 1 || _isInfinite)
316 {
317 if (!_lerp) StartScreenChange();
318
319 _lerp = true;
320 if (_isInfinite)
321 {
322 CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition) + 1;
323 }
324 else
325 {
326 CurrentPage = _currentPage + 1;
327 }
328 GetPositionforPage(_currentPage, ref _lerp_target);
329 ScreenChange();
330 }
331
332 }
333
334 //Function for switching screens with buttons
335 public void PreviousScreen()
336 {
337 if (_currentPage > 0 || _isInfinite)
338 {
339 if (!_lerp) StartScreenChange();
340
341 _lerp = true;
342 if (_isInfinite)
343 {
344 CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition) - 1;
345 }
346 else
347 {
348 CurrentPage = _currentPage - 1;
349 }
350 GetPositionforPage(_currentPage, ref _lerp_target);
351 ScreenChange();
352 }
353 }
354
360 public void GoToScreen(int screenIndex)
361 {
362 if (screenIndex <= _screens - 1 && screenIndex >= 0)
363 {
364 if (!_lerp) StartScreenChange();
365
366 _lerp = true;
367 CurrentPage = screenIndex;
368 GetPositionforPage(_currentPage, ref _lerp_target);
369 ScreenChange();
370 }
371 }
372
378 internal int GetPageforPosition(Vector3 pos)
379 {
380 return _isVertical ?
381 (int)Math.Round((_scrollStartPosition - pos.y) / _childSize) :
382 (int)Math.Round((_scrollStartPosition - pos.x) / _childSize);
383 }
384
390 internal bool IsRectSettledOnaPage(Vector3 pos)
391 {
392 return _isVertical ?
393 -((pos.y - _scrollStartPosition) / _childSize) == -(int)Math.Round((pos.y - _scrollStartPosition) / _childSize) :
394 -((pos.x - _scrollStartPosition) / _childSize) == -(int)Math.Round((pos.x - _scrollStartPosition) / _childSize);
395 }
396
402 internal void GetPositionforPage(int page, ref Vector3 target)
403 {
404 _childPos = -_childSize * page;
405 if (_isVertical)
406 {
407 _infiniteOffset = _screensContainer.anchoredPosition.y < 0 ? -_screensContainer.sizeDelta.y * _infiniteWindow : _screensContainer.sizeDelta.y * _infiniteWindow;
408 _infiniteOffset = _infiniteOffset == 0 ? 0 : _infiniteOffset < 0 ? _infiniteOffset - _childSize * _infiniteWindow : _infiniteOffset + _childSize * _infiniteWindow;
409 target.y = _childPos + _scrollStartPosition + _infiniteOffset;
410 }
411 else
412 {
413 _infiniteOffset = _screensContainer.anchoredPosition.x < 0 ? -_screensContainer.sizeDelta.x * _infiniteWindow : _screensContainer.sizeDelta.x * _infiniteWindow;
414 _infiniteOffset = _infiniteOffset == 0 ? 0 : _infiniteOffset < 0 ? _infiniteOffset - _childSize * _infiniteWindow : _infiniteOffset + _childSize * _infiniteWindow;
415 target.x = _childPos + _scrollStartPosition + _infiniteOffset;
416 }
417 }
418
422 internal void ScrollToClosestElement()
423 {
424 _lerp = true;
425 CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition);
426 GetPositionforPage(_currentPage, ref _lerp_target);
427 OnCurrentScreenChange(_currentPage);
428 }
429
433 internal void OnCurrentScreenChange(int currentScreen)
434 {
435 ChangeBulletsInfo(currentScreen);
436 ToggleNavigationButtons(currentScreen);
437 }
438
443 private void ChangeBulletsInfo(int targetScreen)
444 {
445 if (Pagination)
446 for (int i = 0; i < Pagination.transform.childCount; i++)
447 {
448 Pagination.transform.GetChild(i).GetComponent<Toggle>().isOn = (targetScreen == i)
449 ? true
450 : false;
451 }
452 }
453
458 private void ToggleNavigationButtons(int targetScreen)
459 {
460 //If this is using an Infinite Scroll, then don't disable
461 if (!_isInfinite)
462 {
463 if (PrevButton)
464 {
465 PrevButton.GetComponent<Button>().interactable = targetScreen > 0;
466 }
467
468 if (NextButton)
469 {
470 NextButton.GetComponent<Button>().interactable = targetScreen < _screensContainer.transform.childCount - 1;
471 }
472 }
473 }
474
475 private void OnValidate()
476 {
477 if (_scroll_rect == null)
478 {
479 _scroll_rect = GetComponent<ScrollRect>();
480 }
481 if (!_scroll_rect.horizontal && !_scroll_rect.vertical)
482 {
483 Debug.LogError("ScrollRect has to have a direction, please select either Horizontal OR Vertical with the appropriate control.");
484 }
485 if (_scroll_rect.horizontal && _scroll_rect.vertical)
486 {
487 Debug.LogError("ScrollRect has to be unidirectional, only use either Horizontal or Vertical on the ScrollRect, NOT both.");
488 }
489 var ScrollRectContent = gameObject.GetComponent<ScrollRect>().content;
490 if (ScrollRectContent != null)
491 {
492 var children = ScrollRectContent.childCount;
493 if (children != 0 || ChildObjects != null)
494 {
495 var childCount = ChildObjects == null || ChildObjects.Length == 0 ? children : ChildObjects.Length;
496 if (StartingScreen > childCount - 1)
497 {
498 StartingScreen = childCount - 1;
499 }
500
501 if (StartingScreen < 0)
502 {
503 StartingScreen = 0;
504 }
505 }
506 }
507
508 if (MaskBuffer <= 0)
509 {
510 MaskBuffer = 1;
511 }
512
513 if (PageStep < 0)
514 {
515 PageStep = 0;
516 }
517
518 if (PageStep > 8)
519 {
520 PageStep = 9;
521 }
522 var infiniteScroll = GetComponent<UI_InfiniteScroll>();
523 if (ChildObjects != null && ChildObjects.Length > 0 && infiniteScroll != null && !infiniteScroll.InitByUser)
524 {
525 Debug.LogError($"[{gameObject.name}]When using procedural children with a ScrollSnap (Adding Prefab ChildObjects) and the Infinite Scroll component\nYou must set the 'InitByUser' option to true, to enable late initialising");
526 }
527 }
528
532 public void StartScreenChange()
533 {
534 if (!_moveStarted)
535 {
536 _moveStarted = true;
538 }
539 }
540
544 internal void ScreenChange()
545 {
546 OnSelectionPageChangedEvent.Invoke(_currentPage);
547 }
548
552 internal void EndScreenChange()
553 {
554 OnSelectionChangeEndEvent.Invoke(_currentPage);
555 _settled = true;
556 _moveStarted = false;
557 }
558
563 public Transform CurrentPageObject()
564 {
565 return _screensContainer.GetChild(CurrentPage);
566 }
567
572 public void CurrentPageObject(out Transform returnObject)
573 {
574 returnObject = _screensContainer.GetChild(CurrentPage);
575 }
576
577 #region Drag Interfaces
582 public void OnBeginDrag(PointerEventData eventData)
583 {
584 _pointerDown = true;
585 _settled = false;
587 _startPosition = _screensContainer.anchoredPosition;
588 }
589
594 public void OnDrag(PointerEventData eventData)
595 {
596 _lerp = false;
597 }
598
599 public virtual void OnEndDrag(PointerEventData eventData) { }
600
601 #endregion
602
603 #region IScrollSnap Interface
604
608 int IScrollSnap.CurrentPage()
609 {
610 return CurrentPage = GetPageforPosition(_screensContainer.anchoredPosition);
611 }
612
616 public void SetLerp(bool value)
617 {
618 _lerp = value;
619 }
620
624 public void ChangePage(int page)
625 {
626 GoToScreen(page);
627 }
628
629 public void OnPointerClick(PointerEventData eventData)
630 {
631 var position = _screensContainer.anchoredPosition;
632 }
633
634 #endregion
635 }
636}
Es.InkPainter.Math Math
Definition: PaintTest.cs:7
UnityEngine.UI.Button Button
Definition: Pointer.cs:7
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
SelectionPageChangedEvent OnSelectionPageChangedEvent
void StartScreenChange()
Event fires when the user starts to change the page, either via swipe or button.
SelectionChangeStartEvent OnSelectionChangeStartEvent
SelectionChangeEndEvent OnSelectionChangeEndEvent
void SetLerp(bool value)
Added to provide a uniform interface for the ScrollBarHelper
Transform CurrentPageObject()
Returns the Transform of the Current page
virtual void OnEndDrag(PointerEventData eventData)
void OnBeginDrag(PointerEventData eventData)
Touch screen to start swiping
void OnDrag(PointerEventData eventData)
While dragging do
void ChangePage(int page)
Added to provide a uniform interface for the ScrollBarHelper
void CurrentPageObject(out Transform returnObject)
Returns the Transform of the Current page in an out parameter for performance
void OnPointerClick(PointerEventData eventData)
void GoToScreen(int screenIndex)
Function for switching to a specific screen *Note, this is based on a 0 starting index - 0 to x
Credit Erdener Gonenc - @PixelEnvision.