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     _eventCallback: null,
 58 
 59     /**
 60      * Allocates and initializes a UIPageView.
 61      * Constructor of ccui.PageView. please do not call this function by yourself, you should pass the parameters to constructor to initialize it
.
 62      * @example
 63      * // example
 64      * var uiPageView = new ccui.PageView();
 65      */
 66     ctor: function () {
 67         ccui.Layout.prototype.ctor.call(this);
 68         this._pages = [];
 69         this._touchMoveDirection = ccui.PageView.TOUCH_DIR_LEFT;
 70 
 71         this._movePagePoint = null;
 72         this._leftBoundaryChild = null;
 73         this._rightBoundaryChild = null;
 74 
 75         this._childFocusCancelOffset = 5;
 76         this._pageViewEventListener = null;
 77         this._pageViewEventSelector = null;
 78         this.setTouchEnabled(true);
 79     },
 80 
 81     /**
 82      * Initializes a ccui.PageView. Please do not call this function by yourself, you should pass the parameters to constructor to initialize it.
 83      * @returns {boolean}
 84      */
 85     init: function () {
 86         if (ccui.Layout.prototype.init.call(this)) {
 87             this.setClippingEnabled(true);
 88             return true;
 89         }
 90         return false;
 91     },
 92 
 93     /**
 94      * Calls the parent class' onEnter and schedules update function.
 95      * @override
 96      */
 97     onEnter:function(){
 98         ccui.Layout.prototype.onEnter.call(this);
 99         this.scheduleUpdate(true);
100     },
101 
102     /**
103      * Add a widget to a page of PageView.
104      * @param {ccui.Widget} widget widget to be added to PageView.
105      * @param {number} pageIdx index of page.
106      * @param {Boolean} forceCreate if force create and there is no page exist, PageView would create a default page for adding widget.
107      */
108     addWidgetToPage: function (widget, pageIdx, forceCreate) {
109         if (!widget || pageIdx < 0)
110             return;
111 
112         var pageCount = this._getPageCount();
113         if (pageIdx < 0 || pageIdx >= pageCount) {
114             if (forceCreate) {
115                 if (pageIdx > pageCount)
116                     cc.log("pageIdx is %d, it will be added as page id [%d]", pageIdx, pageCount);
117                 var newPage = this._createPage();
118                 newPage.addChild(widget);
119                 this.addPage(newPage);
120             }
121         } else {
122             var page = this._pages[pageIdx];
123             if (page)
124                 page.addChild(widget);
125         }
126     },
127 
128     _createPage: function () {
129         var newPage = ccui.Layout.create();
130         newPage.setContentSize(this.getContentSize());
131         return newPage;
132     },
133 
134     /**
135      * Adds a page to ccui.PageView.
136      * @param {ccui.Layout} page
137      */
138     addPage: function (page) {
139         if (!page || this._pages.indexOf(page) != -1)
140             return;
141 
142         this.addChild(page);
143         this._pages.push(page);
144         this._doLayoutDirty = true;
145     },
146 
147     /**
148      * Inserts a page in the specified location.
149      * @param {ccui.Layout} page page to be added to PageView.
150      * @param {Number} idx index
151      */
152     insertPage: function (page, idx) {
153         if (idx < 0 || !page || this._pages.indexOf(page) != -1)
154             return;
155 
156         var pageCount = this._getPageCount();
157         if (idx >= pageCount)
158             this.addPage(page);
159         else {
160             this._pages[idx] = page;
161             this.addChild(page);
162         }
163         this._doLayoutDirty = true;
164     },
165 
166     /**
167      * Removes a page from PageView.
168      * @param {ccui.Layout} page
169      */
170     removePage: function (page) {
171         if (!page)
172             return;
173         this.removeChild(page);
174         var index = this._pages.indexOf(page);
175         if(index > -1)
176             this._pages.splice(index, 1);
177         this._doLayoutDirty = true;
178     },
179 
180     /**
181      * Removes a page at index of PageView.
182      * @param {number} index
183      */
184     removePageAtIndex: function (index) {
185         if (index < 0 || index >= this._pages.length)
186             return;
187         var page = this._pages[index];
188         if (page)
189             this.removePage(page);
190     },
191 
192     /**
193      * Removes all pages from PageView
194      */
195     removeAllPages: function(){
196         var locPages = this._pages;
197         for(var i = 0, len = locPages.length; i < len; i++)
198             this.removeChild(locPages[i]);
199         this._pages.length = 0;
200     },
201 
202     _updateBoundaryPages: function () {
203         var locPages = this._pages;
204         if (locPages.length <= 0) {
205             this._leftBoundaryChild = null;
206             this._rightBoundaryChild = null;
207             return;
208         }
209         this._leftBoundaryChild = locPages[0];
210         this._rightBoundaryChild = locPages[locPages.length - 1];
211     },
212 
213     _getPageCount: function(){
214         return this._pages.length;
215     },
216 
217     /**
218      * Get x position by index
219      * @param {number} idx
220      * @returns {number}
221      */
222     _getPositionXByIndex: function (idx) {
223         return (this.getContentSize().width * (idx - this._curPageIdx));
224     },
225 
226     _onSizeChanged: function () {
227         ccui.Layout.prototype._onSizeChanged.call(this);
228         this._rightBoundary = this.getContentSize().width;
229         this._doLayoutDirty = true;
230     },
231 
232     _updateAllPagesSize: function(){
233         var selfSize = this.getContentSize();
234         var locPages = this._pages;
235         for (var i = 0, len = locPages.length; i < len; i++)
236             locPages[i].setContentSize(selfSize);
237     },
238 
239     _updateAllPagesPosition: function(){
240         var pageCount = this._getPageCount();
241         if (pageCount <= 0) {
242             this._curPageIdx = 0;
243             return;
244         }
245 
246         if (this._curPageIdx >= pageCount)
247             this._curPageIdx = pageCount-1;
248 
249         var pageWidth = this.getContentSize().width;
250         var locPages = this._pages;
251         for (var i=0; i< pageCount; i++)
252             locPages[i].setPosition(cc.p((i - this._curPageIdx) * pageWidth, 0));
253     },
254 
255     /**
256      * scroll PageView to index.
257      * @param {number} idx index of page.
258      */
259     scrollToPage: function (idx) {
260         if (idx < 0 || idx >= this._pages.length)
261             return;
262         this._curPageIdx = idx;
263         var curPage = this._pages[idx];
264         this._autoScrollDistance = -(curPage.getPosition().x);
265         this._autoScrollSpeed = Math.abs(this._autoScrollDistance) / 0.2;
266         this._autoScrollDirection = this._autoScrollDistance > 0 ? ccui.PageView.DIRECTION_RIGHT : ccui.PageView.DIRECTION_LEFT;
267         this._isAutoScrolling = true;
268     },
269 
270     /**
271      * Called once per frame. Time is the number of seconds of a frame interval.
272      * @override
273      * @param {Number} dt
274      */
275     update: function (dt) {
276         if (this._isAutoScrolling)
277             this._autoScroll(dt);
278     },
279 
280     /**
281      * Does nothing. ccui.PageView's layout type is ccui.Layout.ABSOLUTE.
282      * @override
283      * @param {Number} type
284      */
285     setLayoutType:function(type){
286     },
287 
288     /**
289      * Returns the layout type of ccui.PageView. it's always ccui.Layout.ABSOLUTE.
290      * @returns {number}
291      */
292     getLayoutType: function(){
293         return ccui.Layout.ABSOLUTE;
294     },
295 
296     _autoScroll: function(dt){
297         var step;
298         switch (this._autoScrollDirection) {
299             case ccui.PageView.DIRECTION_LEFT:
300                 step = this._autoScrollSpeed * dt;
301                 if (this._autoScrollDistance + step >= 0.0) {
302                     step = -this._autoScrollDistance;
303                     this._autoScrollDistance = 0.0;
304                     this._isAutoScrolling = false;
305                 } else
306                     this._autoScrollDistance += step;
307                 this._scrollPages(-step);
308                 if(!this._isAutoScrolling)
309                     this._pageTurningEvent();
310                 break;
311                 break;
312             case ccui.PageView.DIRECTION_RIGHT:
313                 step = this._autoScrollSpeed * dt;
314                 if (this._autoScrollDistance - step <= 0.0) {
315                     step = this._autoScrollDistance;
316                     this._autoScrollDistance = 0.0;
317                     this._isAutoScrolling = false;
318                 } else
319                     this._autoScrollDistance -= step;
320                 this._scrollPages(step);
321                 if(!this._isAutoScrolling)
322                     this._pageTurningEvent();
323                 break;
324             default:
325                 break;
326         }
327     },
328 
329     /**
330      * The touch moved event callback handler of ccui.PageView.
331      * @override
332      * @param {cc.Touch} touch
333      * @param {cc.Event} event
334      */
335     onTouchMoved: function (touch, event) {
336         this._handleMoveLogic(touch);
337         var widgetParent = this.getWidgetParent();
338         if (widgetParent)
339             widgetParent.interceptTouchEvent(ccui.Widget.TOUCH_MOVED, this, touch);
340         this._moveEvent();
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         this._handleReleaseLogic(touch);
352     },
353 
354     /**
355      * The touch canceled event callback handler of ccui.PageView.
356      * @param {cc.Touch} touch
357      * @param {cc.Event} event
358      */
359     onTouchCancelled: function (touch, event) {
360         ccui.Layout.prototype.onTouchCancelled.call(this, touch, event);
361         this._handleReleaseLogic(touch);
362     },
363 
364     _doLayout: function(){
365         if (!this._doLayoutDirty)
366             return;
367 
368         this._updateAllPagesPosition();
369         this._updateAllPagesSize();
370         this._updateBoundaryPages();
371         this._doLayoutDirty = false;
372     },
373 
374     _movePages: function (offset) {
375         var arrayPages = this._pages;
376         var length = arrayPages.length;
377         for (var i = 0; i < length; i++) {
378             var child = arrayPages[i];
379             //var pos = child.getPosition();
380             //child.setPosition(pos.x + offset, pos.y);
381             child.setPositionX(child.getPositionX() + offset);
382         }
383     },
384 
385     _scrollPages: function (touchOffset) {
386         if (this._pages.length <= 0)
387             return false;
388         if (!this._leftBoundaryChild || !this._rightBoundaryChild)
389             return false;
390 
391         var realOffset = touchOffset;
392         switch (this._touchMoveDirection) {
393             case ccui.PageView.TOUCH_DIR_LEFT: // left
394                 var rightBoundary = this._rightBoundaryChild.getRightBoundary();
395                 if (rightBoundary + touchOffset <= this._rightBoundary) {
396                     realOffset = this._rightBoundary - rightBoundary;
397                     this._movePages(realOffset);
398                     return false;
399                 }
400                 break;
401             case ccui.PageView.TOUCH_DIR_RIGHT: // right
402                 var leftBoundary = this._leftBoundaryChild.getLeftBoundary();
403                 if (leftBoundary + touchOffset >= this._leftBoundary) {
404                     realOffset = this._leftBoundary - leftBoundary;
405                     this._movePages(realOffset);
406                     return false;
407                 }
408                 break;
409             default:
410                 break;
411         }
412 
413         this._movePages(realOffset);
414         return true;
415     },
416 
417     _handleMoveLogic: function (touch) {
418         var offset = touch.getLocation().x - touch.getPreviousLocation().x;
419         if (offset < 0)
420             this._touchMoveDirection = ccui.PageView.TOUCH_DIR_LEFT;
421         else if (offset > 0)
422             this._touchMoveDirection = ccui.PageView.TOUCH_DIR_RIGHT;
423         this._scrollPages(offset);
424     },
425 
426     _handleReleaseLogic: function (touchPoint) {
427         if (this._pages.length <= 0)
428             return;
429         var curPage = this._pages[this._curPageIdx];
430         if (curPage) {
431             var curPagePos = curPage.getPosition();
432             var pageCount = this._pages.length;
433             var curPageLocation = curPagePos.x;
434             var pageWidth = this.getSize().width;
435             var boundary = pageWidth / 2.0;
436             if (curPageLocation <= -boundary) {
437                 if (this._curPageIdx >= pageCount - 1)
438                     this._scrollPages(-curPageLocation);
439                 else
440                     this.scrollToPage(this._curPageIdx + 1);
441             }
442             else if (curPageLocation >= boundary) {
443                 if (this._curPageIdx <= 0)
444                     this._scrollPages(-curPageLocation);
445                 else
446                     this.scrollToPage(this._curPageIdx - 1);
447             } else
448                 this.scrollToPage(this._curPageIdx);
449         }
450     },
451 
452     /**
453      * Intercept touch event, handle its child's touch event.
454      * @param {Number} eventType event type
455      * @param {ccui.Widget} sender
456      * @param {cc.Touch} touch
457      */
458     interceptTouchEvent: function (eventType, sender, touch) {
459         var touchPoint = touch.getLocation();
460         switch (eventType) {
461             case ccui.Widget.TOUCH_BEGAN:
462                 this._touchBeganPosition.x = touchPoint.x;
463                 this._touchBeganPosition.y = touchPoint.y;
464                 break;
465             case ccui.Widget.TOUCH_MOVED:
466                 this._touchMovePosition.x = touchPoint.x;
467                 this._touchMovePosition.y = touchPoint.y;
468                 var offset = 0;
469                 offset = Math.abs(sender.getTouchBeganPosition().x - touchPoint.x);
470                 if (offset > this._childFocusCancelOffset) {
471                     sender.setFocused(false);
472                     this._handleMoveLogic(touch);
473                 }
474                 break;
475             case ccui.Widget.TOUCH_ENDED:
476                 this._touchEndPosition.x = touchPoint.x;
477                 this._touchEndPosition.y = touchPoint.y;
478                 break;
479             case ccui.Widget.TOUCH_CANCELED:
480                 this._handleReleaseLogic(touch);
481                 break;
482         }
483     },
484 
485     _pageTurningEvent: function () {
486         if (this._pageViewEventListener && this._pageViewEventSelector)
487             this._pageViewEventSelector.call(this._pageViewEventListener, this, ccui.PageView.EVENT_TURNING);
488         if (this._eventCallback)
489             this._eventCallback(this, ccui.PageView.EVENT_TURNING);
490     },
491 
492     /**
493      * Adds event listener to ccui.PageView.
494      * @param {Function} selector
495      * @param {Object} target
496      * @deprecated since v3.0, please use addEventListener instead.
497      */
498     addEventListenerPageView: function (selector, target) {
499         this._pageViewEventSelector = selector;
500         this._pageViewEventListener = target;
501     },
502 
503     /**
504      * Adds event listener to ccui.PageView.
505      * @param callback
506      */
507     addEventListener: function(callback){
508         this._eventCallback = callback;
509     },
510 
511     /**
512      * Returns current page index
513      * @returns {number}
514      */
515     getCurPageIndex: function () {
516         return this._curPageIdx;
517     },
518 
519     /**
520      * Returns all pages of PageView
521      * @returns {Array}
522      */
523     getPages:function(){
524         return this._pages;
525     },
526 
527     /**
528      * Returns a page from PageView by index
529      * @param {Number} index
530      * @returns {ccui.Layout}
531      */
532     getPage: function(index){
533         if (index < 0 || index >= this.getPages().size())
534             return null;
535         return this._pages[index];
536     },
537 
538     /**
539      * Returns the "class name" of ccui.PageView.
540      * @returns {string}
541      */
542     getDescription: function () {
543         return "PageView";
544     },
545 
546     _createCloneInstance: function () {
547         return ccui.PageView.create();
548     },
549 
550     _copyClonedWidgetChildren: function (model) {
551         var arrayPages = model.getPages();
552         for (var i = 0; i < arrayPages.length; i++) {
553             var page = arrayPages[i];
554             this.addPage(page.clone());
555         }
556     },
557 
558     _copySpecialProperties: function (pageView) {
559         ccui.Layout.prototype._copySpecialProperties.call(this, pageView);
560         this._eventCallback = pageView._eventCallback;
561         this._pageViewEventListener = pageView._pageViewEventListener;
562         this._pageViewEventSelector = pageView._pageViewEventSelector;
563     }
564 });
565 /**
566  * allocates and initializes a UIPageView.
567  * @deprecated since v3.0, please use new ccui.PageView() instead.
568  * @return {ccui.PageView}
569  * @example
570  * // example
571  * var uiPageView = ccui.PageView.create();
572  */
573 ccui.PageView.create = function () {
574     return new ccui.PageView();
575 };
576 
577 // Constants
578 //PageView event
579 /**
580  * The turning flag of ccui.PageView's event.
581  * @constant
582  * @type {number}
583  */
584 ccui.PageView.EVENT_TURNING = 0;
585 
586 //PageView touch direction
587 /**
588  * The left flag of ccui.PageView's touch direction.
589  * @constant
590  * @type {number}
591  */
592 ccui.PageView.TOUCH_DIR_LEFT = 0;
593 /**
594  * The right flag of ccui.PageView's touch direction.
595  * @constant
596  * @type {number}
597  */
598 ccui.PageView.TOUCH_DIR_RIGHT = 1;
599 
600 //PageView auto scroll direction
601 /**
602  * The right flag of ccui.PageView's auto scroll direction.
603  * @constant
604  * @type {number}
605  */
606 ccui.PageView.DIRECTION_LEFT = 0;
607 /**
608  * The right flag of ccui.PageView's auto scroll direction.
609  * @constant
610  * @type {number}
611  */
612 ccui.PageView.DIRECTION_RIGHT = 1;
613