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