1 /****************************************************************************
  2  Copyright (c) 2011-2012 cocos2d-x.org
  3  Copyright (c) 2013-2014 Chukong Technologies Inc.
  4 
  5  http://www.cocos2d-x.org
  6 
  7  Permission is hereby granted, free of charge, to any person obtaining a copy
  8  of this software and associated documentation files (the "Software"), to deal
  9  in the Software without restriction, including without limitation the rights
 10  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 11  copies of the Software, and to permit persons to whom the Software is
 12  furnished to do so, subject to the following conditions:
 13 
 14  The above copyright notice and this permission notice shall be included in
 15  all copies or substantial portions of the Software.
 16 
 17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 18  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 19  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 20  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 21  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 22  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 23  THE SOFTWARE.
 24  ****************************************************************************/
 25 
 26 /**
 27  * The PageView control of Cocos UI.
 28  * @class
 29  * @extends ccui.Layout
 30  * @exmaple
 31  * var pageView = new ccui.PageView();
 32  * pageView.setTouchEnabled(true);
 33  * pageView.addPage(new ccui.Layout());
 34  * this.addChild(pageView);
 35  */
 36 ccui.PageView = ccui.Layout.extend(/** @lends ccui.PageView# */{
 37     _curPageIdx: 0,
 38     _pages: null,
 39     _touchMoveDirection: null,
 40     _touchStartLocation: 0,
 41     _touchMoveStartLocation: 0,
 42     _movePagePoint: null,
 43     _leftBoundaryChild: null,
 44     _rightBoundaryChild: null,
 45     _leftBoundary: 0,
 46     _rightBoundary: 0,
 47 
 48     _isAutoScrolling: false,
 49     _autoScrollDistance: 0,
 50     _autoScrollSpeed: 0,
 51     _autoScrollDirection: 0,
 52 
 53     _childFocusCancelOffset: 0,
 54     _pageViewEventListener: null,
 55     _pageViewEventSelector: null,
 56     _className:"PageView",
 57     //v3.2
 58     _customScrollThreshold: 0,
 59     _usingCustomScrollThreshold: false,
 60 
 61     /**
 62      * Allocates and initializes a UIPageView.
 63      * Constructor of ccui.PageView. please do not call this function by yourself, you should pass the parameters to constructor to initialize it
.
 64      * @example
 65      * // example
 66      * var uiPageView = new ccui.PageView();
 67      */
 68     ctor: function () {
 69         ccui.Layout.prototype.ctor.call(this);
 70         this._pages = [];
 71         this._touchMoveDirection = ccui.PageView.TOUCH_DIR_LEFT;
 72 
 73         this._movePagePoint = null;
 74         this._leftBoundaryChild = null;
 75         this._rightBoundaryChild = null;
 76 
 77         this._childFocusCancelOffset = 5;
 78         this._pageViewEventListener = null;
 79         this._pageViewEventSelector = null;
 80         this.setTouchEnabled(true);
 81     },
 82 
 83     /**
 84      * Initializes a ccui.PageView. Please do not call this function by yourself, you should pass the parameters to constructor to initialize it.
 85      * @returns {boolean}
 86      */
 87     init: function () {
 88         if (ccui.Layout.prototype.init.call(this)) {
 89             this.setClippingEnabled(true);
 90             return true;
 91         }
 92         return false;
 93     },
 94 
 95     /**
 96      * Calls the parent class' onEnter and schedules update function.
 97      * @override
 98      */
 99     onEnter:function(){
100         ccui.Layout.prototype.onEnter.call(this);
101         this.scheduleUpdate(true);
102     },
103 
104     /**
105      * Add a widget to a page of PageView.
106      * @param {ccui.Widget} widget widget to be added to PageView.
107      * @param {number} pageIdx index of page.
108      * @param {Boolean} forceCreate if force create and there is no page exist, PageView would create a default page for adding widget.
109      */
110     addWidgetToPage: function (widget, pageIdx, forceCreate) {
111         if (!widget || pageIdx < 0)
112             return;
113 
114         var pageCount = this._getPageCount();
115         if (pageIdx < 0 || pageIdx >= pageCount) {
116             if (forceCreate) {
117                 if (pageIdx > pageCount)
118                     cc.log("pageIdx is %d, it will be added as page id [%d]", pageIdx, pageCount);
119                 var newPage = this._createPage();
120                 newPage.addChild(widget);
121                 this.addPage(newPage);
122             }
123         } else {
124             var page = this._pages[pageIdx];
125             if (page)
126                 page.addChild(widget);
127         }
128     },
129 
130     _createPage: function () {
131         var newPage = new ccui.Layout();
132         newPage.setContentSize(this.getContentSize());
133         return newPage;
134     },
135 
136     /**
137      * Adds a page to ccui.PageView.
138      * @param {ccui.Layout} page
139      */
140     addPage: function (page) {
141         if (!page || this._pages.indexOf(page) !== -1)
142             return;
143 
144         this.addChild(page);
145         this._pages.push(page);
146         this._doLayoutDirty = true;
147     },
148 
149     /**
150      * Inserts a page in the specified location.
151      * @param {ccui.Layout} page page to be added to PageView.
152      * @param {Number} idx index
153      */
154     insertPage: function (page, idx) {
155         if (idx < 0 || !page || this._pages.indexOf(page) !== -1)
156             return;
157 
158         var pageCount = this._getPageCount();
159         if (idx >= pageCount)
160             this.addPage(page);
161         else {
162             this._pages[idx] = page;
163             this.addChild(page);
164         }
165         this._doLayoutDirty = true;
166     },
167 
168     /**
169      * Removes a page from PageView.
170      * @param {ccui.Layout} page
171      */
172     removePage: function (page) {
173         if (!page)
174             return;
175         this.removeChild(page);
176         var index = this._pages.indexOf(page);
177         if(index > -1)
178             this._pages.splice(index, 1);
179         this._doLayoutDirty = true;
180     },
181 
182     /**
183      * Removes a page at index of PageView.
184      * @param {number} index
185      */
186     removePageAtIndex: function (index) {
187         if (index < 0 || index >= this._pages.length)
188             return;
189         var page = this._pages[index];
190         if (page)
191             this.removePage(page);
192     },
193 
194     /**
195      * Removes all pages from PageView
196      */
197     removeAllPages: function(){
198         var locPages = this._pages;
199         for(var i = 0, len = locPages.length; i < len; i++)
200             this.removeChild(locPages[i]);
201         this._pages.length = 0;
202     },
203 
204     _updateBoundaryPages: function () {
205         var locPages = this._pages;
206         if (locPages.length <= 0) {
207             this._leftBoundaryChild = null;
208             this._rightBoundaryChild = null;
209             return;
210         }
211         this._leftBoundaryChild = locPages[0];
212         this._rightBoundaryChild = locPages[locPages.length - 1];
213     },
214 
215     _getPageCount: function(){
216         return this._pages.length;
217     },
218 
219     /**
220      * Get x position by index
221      * @param {number} idx
222      * @returns {number}
223      */
224     _getPositionXByIndex: function (idx) {
225         return (this.getContentSize().width * (idx - this._curPageIdx));
226     },
227 
228     _onSizeChanged: function () {
229         ccui.Layout.prototype._onSizeChanged.call(this);
230         this._rightBoundary = this.getContentSize().width;
231         this._doLayoutDirty = true;
232     },
233 
234     _updateAllPagesSize: function(){
235         var selfSize = this.getContentSize();
236         var locPages = this._pages;
237         for (var i = 0, len = locPages.length; i < len; i++)
238             locPages[i].setContentSize(selfSize);
239     },
240 
241     _updateAllPagesPosition: function(){
242         var pageCount = this._getPageCount();
243         if (pageCount <= 0) {
244             this._curPageIdx = 0;
245             return;
246         }
247 
248         if (this._curPageIdx >= pageCount)
249             this._curPageIdx = pageCount-1;
250 
251         var pageWidth = this.getContentSize().width;
252         var locPages = this._pages;
253         for (var i=0; i< pageCount; i++)
254             locPages[i].setPosition(cc.p((i - this._curPageIdx) * pageWidth, 0));
255     },
256 
257     /**
258      * scroll PageView to index.
259      * @param {number} idx index of page.
260      */
261     scrollToPage: function (idx) {
262         if (idx < 0 || idx >= this._pages.length)
263             return;
264         this._curPageIdx = idx;
265         var curPage = this._pages[idx];
266         this._autoScrollDistance = -(curPage.getPosition().x);
267         this._autoScrollSpeed = Math.abs(this._autoScrollDistance) / 0.2;
268         this._autoScrollDirection = this._autoScrollDistance > 0 ? ccui.PageView.DIRECTION_RIGHT : ccui.PageView.DIRECTION_LEFT;
269         this._isAutoScrolling = true;
270     },
271 
272     /**
273      * Called once per frame. Time is the number of seconds of a frame interval.
274      * @override
275      * @param {Number} dt
276      */
277     update: function (dt) {
278         if (this._isAutoScrolling)
279             this._autoScroll(dt);
280     },
281 
282     /**
283      * Does nothing. ccui.PageView's layout type is ccui.Layout.ABSOLUTE.
284      * @override
285      * @param {Number} type
286      */
287     setLayoutType:function(type){
288     },
289 
290     /**
291      * Returns the layout type of ccui.PageView. it's always ccui.Layout.ABSOLUTE.
292      * @returns {number}
293      */
294     getLayoutType: function(){
295         return ccui.Layout.ABSOLUTE;
296     },
297 
298     _autoScroll: function(dt){
299         var step;
300         switch (this._autoScrollDirection) {
301             case ccui.PageView.DIRECTION_LEFT:
302                 step = this._autoScrollSpeed * dt;
303                 if (this._autoScrollDistance + step >= 0.0) {
304                     step = -this._autoScrollDistance;
305                     this._autoScrollDistance = 0.0;
306                     this._isAutoScrolling = false;
307                 } else
308                     this._autoScrollDistance += step;
309                 this._scrollPages(-step);
310                 if(!this._isAutoScrolling)
311                     this._pageTurningEvent();
312                 break;
313                 break;
314             case ccui.PageView.DIRECTION_RIGHT:
315                 step = this._autoScrollSpeed * dt;
316                 if (this._autoScrollDistance - step <= 0.0) {
317                     step = this._autoScrollDistance;
318                     this._autoScrollDistance = 0.0;
319                     this._isAutoScrolling = false;
320                 } else
321                     this._autoScrollDistance -= step;
322                 this._scrollPages(step);
323                 if(!this._isAutoScrolling)
324                     this._pageTurningEvent();
325                 break;
326             default:
327                 break;
328         }
329     },
330 
331     /**
332      * The touch moved event callback handler of ccui.PageView.
333      * @override
334      * @param {cc.Touch} touch
335      * @param {cc.Event} event
336      */
337     onTouchMoved: function (touch, event) {
338         ccui.Layout.prototype.onTouchMoved.call(this, touch, event);
339         if (!this._isInterceptTouch)
340             this._handleMoveLogic(touch);
341     },
342 
343     /**
344      * The touch ended event callback handler of ccui.PageView.
345      * @override
346      * @param {cc.Touch} touch
347      * @param {cc.Event} event
348      */
349     onTouchEnded: function (touch, event) {
350         ccui.Layout.prototype.onTouchEnded.call(this, touch, event);
351         if (!this._isInterceptTouch)
352             this._handleReleaseLogic(touch);
353         this._isInterceptTouch = false;
354     },
355 
356     /**
357      * The touch canceled event callback handler of ccui.PageView.
358      * @param {cc.Touch} touch
359      * @param {cc.Event} event
360      */
361     onTouchCancelled: function (touch, event) {
362         ccui.Layout.prototype.onTouchCancelled.call(this, touch, event);
363         if (!this._isInterceptTouch)
364             this._handleReleaseLogic(touch);
365         this._isInterceptTouch = false;
366     },
367 
368     _doLayout: function(){
369         if (!this._doLayoutDirty)
370             return;
371 
372         this._updateAllPagesPosition();
373         this._updateAllPagesSize();
374         this._updateBoundaryPages();
375         this._doLayoutDirty = false;
376     },
377 
378     _movePages: function (offset) {
379         var arrayPages = this._pages;
380         var length = arrayPages.length;
381         for (var i = 0; i < length; i++) {
382             var child = arrayPages[i];
383             //var pos = child.getPosition();
384             //child.setPosition(pos.x + offset, pos.y);
385             child.setPositionX(child.getPositionX() + offset);
386         }
387     },
388 
389     _scrollPages: function (touchOffset) {
390         if (this._pages.length <= 0)
391             return false;
392         if (!this._leftBoundaryChild || !this._rightBoundaryChild)
393             return false;
394 
395         var realOffset = touchOffset;
396         switch (this._touchMoveDirection) {
397             case ccui.PageView.TOUCH_DIR_LEFT: // left
398                 var rightBoundary = this._rightBoundaryChild.getRightBoundary();
399                 if (rightBoundary + touchOffset <= this._rightBoundary) {
400                     realOffset = this._rightBoundary - rightBoundary;
401                     this._movePages(realOffset);
402                     return false;
403                 }
404                 break;
405             case ccui.PageView.TOUCH_DIR_RIGHT: // right
406                 var leftBoundary = this._leftBoundaryChild.getLeftBoundary();
407                 if (leftBoundary + touchOffset >= this._leftBoundary) {
408                     realOffset = this._leftBoundary - leftBoundary;
409                     this._movePages(realOffset);
410                     return false;
411                 }
412                 break;
413             default:
414                 break;
415         }
416 
417         this._movePages(realOffset);
418         return true;
419     },
420 
421     _handleMoveLogic: function (touch) {
422         var offset = touch.getLocation().x - touch.getPreviousLocation().x;
423         if (offset < 0)
424             this._touchMoveDirection = ccui.PageView.TOUCH_DIR_LEFT;
425         else if (offset > 0)
426             this._touchMoveDirection = ccui.PageView.TOUCH_DIR_RIGHT;
427         this._scrollPages(offset);
428     },
429 
430     /**
431      * Set custom scroll threshold to page view. If you don't specify the value, the pageView will scroll when half page view width reached.
432      * @since v3.2
433      * @param threshold
434      */
435     setCustomScrollThreshold: function(threshold){
436         cc.assert(threshold>0, "Invalid threshold!");
437         this._customScrollThreshold = threshold;
438         this.setUsingCustomScrollThreshold(true);
439     },
440 
441     /**
442      * Returns user defined scroll page threshold.
443      * @since v3.2
444      */
445     getCustomScrollThreshold: function(){
446         return this._customScrollThreshold;
447     },
448 
449     /**
450      * Set using user defined scroll page threshold or not. If you set it to false, then the default scroll threshold is pageView.width / 2.
451      * @since v3.2
452      */
453     setUsingCustomScrollThreshold: function(flag){
454         this._usingCustomScrollThreshold = flag;
455     },
456 
457     /**
458      * Queries whether we are using user defined scroll page threshold or not
459      */
460     isUsingCustomScrollThreshold: function(){
461         return this._usingCustomScrollThreshold;
462     },
463 
464     _handleReleaseLogic: function (touchPoint) {
465         if (this._pages.length <= 0)
466             return;
467         var curPage = this._pages[this._curPageIdx];
468         if (curPage) {
469             var curPagePos = curPage.getPosition();
470             var pageCount = this._pages.length;
471             var curPageLocation = curPagePos.x;
472             var pageWidth = this.getSize().width;
473             if (!this._usingCustomScrollThreshold)
474                 this._customScrollThreshold = pageWidth / 2.0;
475             var boundary = this._customScrollThreshold;
476             if (curPageLocation <= -boundary) {
477                 if (this._curPageIdx >= pageCount - 1)
478                     this._scrollPages(-curPageLocation);
479                 else
480                     this.scrollToPage(this._curPageIdx + 1);
481             } else if (curPageLocation >= boundary) {
482                 if (this._curPageIdx <= 0)
483                     this._scrollPages(-curPageLocation);
484                 else
485                     this.scrollToPage(this._curPageIdx - 1);
486             } else
487                 this.scrollToPage(this._curPageIdx);
488         }
489     },
490 
491     /**
492      * Intercept touch event, handle its child's touch event.
493      * @param {Number} eventType event type
494      * @param {ccui.Widget} sender
495      * @param {cc.Touch} touch
496      */
497     interceptTouchEvent: function (eventType, sender, touch) {
498         if(!this._touchEnabled)
499         {
500             ccui.Layout.prototype.interceptTouchEvent.call(this, eventType, sender, touch);
501             return;
502         }
503         var touchPoint = touch.getLocation();
504         switch (eventType) {
505             case ccui.Widget.TOUCH_BEGAN:
506                 this._touchBeganPosition.x = touchPoint.x;
507                 this._touchBeganPosition.y = touchPoint.y;
508                 this._isInterceptTouch = true;
509                 break;
510             case ccui.Widget.TOUCH_MOVED:
511                 this._touchMovePosition.x = touchPoint.x;
512                 this._touchMovePosition.y = touchPoint.y;
513                 var offset = 0;
514                 offset = Math.abs(sender.getTouchBeganPosition().x - touchPoint.x);
515                 if (offset > this._childFocusCancelOffset) {
516                     sender.setHighlighted(false);
517                     this._handleMoveLogic(touch);
518                 }
519                 break;
520             case ccui.Widget.TOUCH_ENDED:
521             case ccui.Widget.TOUCH_CANCELED:
522                 this._touchEndPosition.x = touchPoint.x;
523                 this._touchEndPosition.y = touchPoint.y;
524                 this._handleReleaseLogic(touch);
525                 if (sender.isSwallowTouches())
526                     this._isInterceptTouch = false;
527                 break;
528         }
529     },
530 
531     _pageTurningEvent: function () {
532         if(this._pageViewEventSelector){
533             if (this._pageViewEventListener)
534                 this._pageViewEventSelector.call(this._pageViewEventListener, this, ccui.PageView.EVENT_TURNING);
535             else
536                 this._pageViewEventSelector(this, ccui.PageView.EVENT_TURNING);
537         }
538         if(this._ccEventCallback)
539             this._ccEventCallback(this, ccui.PageView.EVENT_TURNING);
540     },
541 
542     /**
543      * Adds event listener to ccui.PageView.
544      * @param {Function} selector
545      * @param {Object} [target=]
546      * @deprecated since v3.0, please use addEventListener instead.
547      */
548     addEventListenerPageView: function (selector, target) {
549         this.addEventListener(selector, target);
550     },
551 
552     /**
553      * Adds event listener to ccui.PageView.
554      * @param {Function} selector
555      * @param {Object} [target=]
556      */
557     addEventListener: function(selector, target){
558         this._pageViewEventSelector = selector;
559         this._pageViewEventListener = target;
560     },
561 
562     /**
563      * Returns current page index
564      * @returns {number}
565      */
566     getCurPageIndex: function () {
567         return this._curPageIdx;
568     },
569 
570     /**
571      * Returns all pages of PageView
572      * @returns {Array}
573      */
574     getPages:function(){
575         return this._pages;
576     },
577 
578     /**
579      * Returns a page from PageView by index
580      * @param {Number} index
581      * @returns {ccui.Layout}
582      */
583     getPage: function(index){
584         if (index < 0 || index >= this._pages.length)
585             return null;
586         return this._pages[index];
587     },
588 
589     /**
590      * Returns the "class name" of ccui.PageView.
591      * @returns {string}
592      */
593     getDescription: function () {
594         return "PageView";
595     },
596 
597     _createCloneInstance: function () {
598         return new ccui.PageView();
599     },
600 
601     _copyClonedWidgetChildren: function (model) {
602         var arrayPages = model.getPages();
603         for (var i = 0; i < arrayPages.length; i++) {
604             var page = arrayPages[i];
605             this.addPage(page.clone());
606         }
607     },
608 
609     _copySpecialProperties: function (pageView) {
610         ccui.Layout.prototype._copySpecialProperties.call(this, pageView);
611         this._ccEventCallback = pageView._ccEventCallback;
612         this._pageViewEventListener = pageView._pageViewEventListener;
613         this._pageViewEventSelector = pageView._pageViewEventSelector;
614         this._usingCustomScrollThreshold = pageView._usingCustomScrollThreshold;
615         this._customScrollThreshold = pageView._customScrollThreshold;
616     }
617 });
618 /**
619  * allocates and initializes a UIPageView.
620  * @deprecated since v3.0, please use new ccui.PageView() instead.
621  * @return {ccui.PageView}
622  */
623 ccui.PageView.create = function () {
624     return new ccui.PageView();
625 };
626 
627 // Constants
628 //PageView event
629 /**
630  * The turning flag of ccui.PageView's event.
631  * @constant
632  * @type {number}
633  */
634 ccui.PageView.EVENT_TURNING = 0;
635 
636 //PageView touch direction
637 /**
638  * The left flag of ccui.PageView's touch direction.
639  * @constant
640  * @type {number}
641  */
642 ccui.PageView.TOUCH_DIR_LEFT = 0;
643 /**
644  * The right flag of ccui.PageView's touch direction.
645  * @constant
646  * @type {number}
647  */
648 ccui.PageView.TOUCH_DIR_RIGHT = 1;
649 
650 //PageView auto scroll direction
651 /**
652  * The right flag of ccui.PageView's auto scroll direction.
653  * @constant
654  * @type {number}
655  */
656 ccui.PageView.DIRECTION_LEFT = 0;
657 /**
658  * The right flag of ccui.PageView's auto scroll direction.
659  * @constant
660  * @type {number}
661  */
662 ccui.PageView.DIRECTION_RIGHT = 1;
663