1 /****************************************************************************
  2  Copyright (c) 2010-2012 cocos2d-x.org
  3 
  4  http://www.cocos2d-x.org
  5 
  6  Permission is hereby granted, free of charge, to any person obtaining a copy
  7  of this software and associated documentation files (the "Software"), to deal
  8  in the Software without restriction, including without limitation the rights
  9  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 10  copies of the Software, and to permit persons to whom the Software is
 11  furnished to do so, subject to the following conditions:
 12 
 13  The above copyright notice and this permission notice shall be included in
 14  all copies or substantial portions of the Software.
 15 
 16  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 17  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 18  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 19  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 20  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 22  THE SOFTWARE.
 23  ****************************************************************************/
 24 
 25 /**
 26  * PageView event type
 27  * @type {Object}
 28  */
 29 ccs.PageViewEventType = {
 30     turning: 0
 31 };
 32 
 33 /**
 34  * PageView touch direction
 35  * @type {Object}
 36  */
 37 ccs.PVTouchDir = {
 38     touchLeft: 0,
 39     touchRight: 1
 40 };
 41 
 42 /**
 43  * Base class for ccs.PageView
 44  * @class
 45  * @extends ccs.Layout
 46  */
 47 ccs.PageView = ccs.Layout.extend(/** @lends ccs.PageView# */{
 48     _curPageIdx: 0,
 49     _pages: null,
 50     _touchMoveDir: null,
 51     _touchStartLocation: 0,
 52     _touchMoveStartLocation: 0,
 53     _movePagePoint: null,
 54     _leftChild: null,
 55     _rightChild: null,
 56     _leftBoundary: 0,
 57     _rightBoundary: 0,
 58     _isAutoScrolling: false,
 59     _autoScrollDistance: 0,
 60     _autoScrollSpeed: 0,
 61     _autoScrollDir: 0,
 62     _childFocusCancelOffset: 0,
 63     _pageViewEventListener: null,
 64     _pageViewEventSelector: null,
 65     ctor: function () {
 66         ccs.Layout.prototype.ctor.call(this);
 67         this._curPageIdx = 0;
 68         this._pages = [];
 69         this._touchMoveDir = ccs.PVTouchDir.touchLeft;
 70         this._touchStartLocation = 0;
 71         this._touchMoveStartLocation = 0;
 72         this._movePagePoint = null;
 73         this._leftChild = null;
 74         this._rightChild = null;
 75         this._leftBoundary = 0;
 76         this._rightBoundary = 0;
 77         this._isAutoScrolling = false;
 78         this._autoScrollDistance = 0;
 79         this._autoScrollSpeed = 0;
 80         this._autoScrollDir = 0;
 81         this._childFocusCancelOffset = 5;
 82         this._pageViewEventListener = null;
 83         this._pageViewEventSelector = null;
 84     },
 85 
 86     init: function () {
 87         if (ccs.Layout.prototype.init.call(this)) {
 88             this._pages = [];
 89             this.setClippingEnabled(true);
 90             this.setTouchEnabled(true);
 91             return true;
 92         }
 93         return false;
 94     },
 95 
 96     onEnter:function(){
 97         ccs.Layout.prototype.onEnter.call(this);
 98         this.setUpdateEnabled(true);
 99     },
100 
101     /**
102      * Add a widget to a page of pageview.
103      * @param {ccs.Widget} widget
104      * @param {number} pageIdx
105      * @param {Boolean} forceCreate
106      */
107     addWidgetToPage: function (widget, pageIdx, forceCreate) {
108         if (!widget) {
109             return;
110         }
111         if(pageIdx<0){
112             return;
113         }
114         var pageCount = this._pages.length;
115         if (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                 }
120                 var newPage = this.createPage();
121                 newPage.addChild(widget);
122                 this.addPage(newPage);
123             }
124         }
125         else {
126             var page = this._pages[pageIdx];
127             if (page) {
128                 page.addChild(widget);
129             }
130         }
131     },
132 
133     /**
134      * create page
135      * @returns {ccs.Layout}
136      */
137     createPage: function () {
138         var newPage = ccs.Layout.create();
139         newPage.setSize(this.getSize());
140         return newPage;
141     },
142 
143     /**
144      * Push back a page to pageview.
145      * @param {ccs.Layout} page
146      */
147     addPage: function (page) {
148         if (!page) {
149             return;
150         }
151         if (page.getWidgetType() != ccs.WidgetType.container) {
152             return;
153         }
154         if (cc.ArrayContainsObject(this._pages, page)) {
155             return;
156         }
157         var pSize = page.getSize();
158         var pvSize = this.getSize();
159         if (!(pSize.width==pvSize.width&&pSize.height==pvSize.height)) {
160             cc.log("page size does not match pageview size, it will be force sized!");
161             page.setSize(pvSize);
162         }
163         page.setPosition(cc.p(this.getPositionXByIndex(this._pages.length), 0));
164         this._pages.push(page);
165         this.addChild(page);
166         this.updateBoundaryPages();
167     },
168 
169     /**
170      * Inert a page to pageview.
171      * @param {ccs.Layout} page
172      * @param {Number} idx
173      */
174     insertPage: function (page, idx) {
175         if (idx < 0) {
176             return;
177         }
178         if (!page) {
179             return;
180         }
181         if (page.getWidgetType() != ccs.WidgetType.container) {
182             return;
183         }
184         if (cc.ArrayContainsObject(this._pages, page)) {
185             return;
186         }
187 
188         var pageCount = this._pages.length;
189         if (idx >= pageCount) {
190             this.addPage(page);
191         }
192         else {
193             cc.ArrayAppendObjectToIndex(this._pages, page, idx);
194             page.setPosition(cc.p(this.getPositionXByIndex(idx), 0));
195             this.addChild(page);
196             var pSize = page.getSize();
197             var pvSize = this.getSize();
198             if (!pSize.equals(pvSize)) {
199                 cc.log("page size does not match pageview size, it will be force sized!");
200                 page.setSize(pvSize);
201             }
202             var arrayPages = this._pages;
203             var length = arrayPages.length;
204             for (var i = (idx + 1); i < length; i++) {
205                 var behindPage = arrayPages[i];
206                 var formerPos = behindPage.getPosition();
207                 behindPage.setPosition(cc.p(formerPos.x + this.getSize().width, 0));
208             }
209             this.updateBoundaryPages();
210         }
211     },
212 
213     /**
214      * Remove a page of pageview.
215      * @param {ccs.Layout} page
216      */
217     removePage: function (page) {
218         if (!page) {
219             return;
220         }
221         this.removeChild(page);
222         this.updateChildrenPosition();
223         this.updateBoundaryPages();
224     },
225 
226     /**
227      * Remove a page at index of pageview.
228      * @param {number} index
229      */
230     removePageAtIndex: function (index) {
231         if (index < 0 || index >= this._pages.length) {
232             return;
233         }
234         var page = this._pages[index];
235         if (page) {
236             this.removePage(page);
237         }
238     },
239 
240     updateBoundaryPages: function () {
241         if (this._pages.length <= 0) {
242             this._leftChild = null;
243             this._rightChild = null;
244             return;
245         }
246         this._leftChild = this._pages[0];
247         this._rightChild = this._pages[this._pages.length-1];
248     },
249 
250     /**
251      * Get x position by index
252      * @param {number} idx
253      * @returns {number}
254      */
255     getPositionXByIndex: function (idx) {
256         return (this.getSize().width * (idx - this._curPageIdx));
257     },
258 
259     /**
260      * Add widget
261      * @param {ccs.Widget} widget
262      * @param {Number} zOrder
263      * @param {Number} tag
264      * @returns {boolean}
265      */
266     addChild: function (widget, zOrder, tag) {
267         return ccs.Layout.prototype.addChild.call(this, widget, zOrder, tag);
268     },
269 
270     /**
271      *  remove widget child override
272      * @param {ccs.Widget} child
273      * @param {Boolean} cleanup
274      */
275     removeChild: function (child, cleanup) {
276         if(cleanup)
277             cc.ArrayRemoveObject(this._pages, child);
278         ccs.Layout.prototype.removeChild.call(this, child, cleanup);
279     },
280 
281     onSizeChanged: function () {
282         ccs.Layout.prototype.onSizeChanged.call(this);
283         this._rightBoundary = this.getSize().width;
284         this.updateChildrenSize();
285         this.updateChildrenPosition();
286     },
287 
288     updateChildrenSize: function () {
289         if (!this._pages.length <= 0) {
290             return;
291         }
292 
293         var selfSize = this.getSize();
294         for (var i = 0; i < this._pages.length; i++) {
295             var page = this._pages[i];
296             page.setSize(selfSize);
297         }
298     },
299 
300     updateChildrenPosition: function () {
301         if (!this._pages) {
302             return;
303         }
304 
305         var pageCount = this._pages.length;
306         if (pageCount <= 0) {
307             this._curPageIdx = 0;
308             return;
309         }
310         if (this._curPageIdx >= pageCount) {
311             this._curPageIdx = pageCount - 1;
312         }
313         var pageWidth = this.getSize().width;
314         var arrayPages = this._pages;
315         for (var i = 0; i < pageCount; i++) {
316             var page = arrayPages[i];
317             page.setPosition(cc.p((i - this._curPageIdx) * pageWidth, 0));
318         }
319     },
320 
321     removeAllChildren: function (cleanup) {
322         if(cleanup)
323             this._pages.length = 0;
324         ccs.Layout.prototype.removeAllChildren.call(this, cleanup);
325     },
326 
327     /**
328      * scroll pageview to index.
329      * @param {number} idx
330      */
331     scrollToPage: function (idx) {
332         if (idx < 0 || idx >= this._pages.length) {
333             return;
334         }
335         this._curPageIdx = idx;
336         var curPage = this._pages[idx];
337         this._autoScrollDistance = -(curPage.getPosition().x);
338         this._autoScrollSpeed = Math.abs(this._autoScrollDistance) / 0.2;
339         this._autoScrollDir = this._autoScrollDistance > 0 ? 1 : 0;
340         this._isAutoScrolling = true;
341     },
342 
343     update: function (dt) {
344         if (this._isAutoScrolling) {
345             switch (this._autoScrollDir) {
346                 case 0:
347                     var step = this._autoScrollSpeed * dt;
348                     if (this._autoScrollDistance + step >= 0.0) {
349                         step = -this._autoScrollDistance;
350                         this._autoScrollDistance = 0.0;
351                         this._isAutoScrolling = false;
352                     }
353                     else {
354                         this._autoScrollDistance += step;
355                     }
356                     this.scrollPages(-step);
357                     if(!this._isAutoScrolling){
358                         this.pageTurningEvent();
359                     }
360                     break;
361                     break;
362                 case 1:
363                     var step = this._autoScrollSpeed * dt;
364                     if (this._autoScrollDistance - step <= 0.0) {
365                         step = this._autoScrollDistance;
366                         this._autoScrollDistance = 0.0;
367                         this._isAutoScrolling = false;
368                     }
369                     else {
370                         this._autoScrollDistance -= step;
371                     }
372                     this.scrollPages(step);
373                     if(!this._isAutoScrolling){
374                         this.pageTurningEvent();
375                     }
376                     break;
377                 default:
378                     break;
379             }
380         }
381     },
382 
383     onTouchBegan: function (touch,event) {
384         var pass = ccs.Layout.prototype.onTouchBegan.call(this, touch,event);
385         if (this._hitted){
386             this.handlePressLogic(touch.getLocation());
387         }
388         return pass;
389     },
390 
391     onTouchMoved: function (touch,event) {
392         var touchPoint = touch.getLocation();
393         this._touchMovePos.x = touchPoint.x;
394         this._touchMovePos.y = touchPoint.y;
395         this.handleMoveLogic(touchPoint);
396         var widgetParent = this.getWidgetParent();
397         if (widgetParent) {
398             widgetParent.checkChildInfo(1, this, touchPoint);
399         }
400         this.moveEvent();
401         if (!this.hitTest(touchPoint)) {
402             this.setFocused(false);
403             this.onTouchEnded(touch,event);
404         }
405     },
406 
407     onTouchEnded: function (touch, event) {
408         ccs.Layout.prototype.onTouchEnded.call(this, touch, event);
409         this.handleReleaseLogic(this._touchEndPos);
410     },
411 
412     onTouchCancelled: function (touch, event) {
413         var touchPoint = touch.getLocation();
414         ccs.Layout.prototype.onTouchCancelled.call(this, touch, event);
415         this.handleReleaseLogic(touchPoint);
416     },
417 
418     movePages: function (offset) {
419         var arrayPages = this._pages;
420         var length = arrayPages.length;
421         for (var i = 0; i < length; i++) {
422             var child = arrayPages[i];
423             var pos = child.getPosition();
424             child.setPosition(cc.p(pos.x + offset, pos.y));
425         }
426     },
427 
428     scrollPages: function (touchOffset) {
429         if (this._pages.length <= 0) {
430             return false;
431         }
432 
433         if (!this._leftChild || !this._rightChild) {
434             return false;
435         }
436 
437         var realOffset = touchOffset;
438 
439         switch (this._touchMoveDir) {
440             case ccs.PVTouchDir.touchLeft: // left
441                 if (this._rightChild.getRightInParent() + touchOffset <= this._rightBoundary) {
442                     realOffset = this._rightBoundary - this._rightChild.getRightInParent();
443                     this.movePages(realOffset);
444                     return false;
445                 }
446                 break;
447 
448             case ccs.PVTouchDir.touchRight: // right
449                 if (this._leftChild.getLeftInParent() + touchOffset >= this._leftBoundary) {
450                     realOffset = this._leftBoundary - this._leftChild.getLeftInParent();
451                     this.movePages(realOffset);
452                     return false;
453                 }
454                 break;
455             default:
456                 break;
457         }
458 
459         this.movePages(realOffset);
460         return true;
461     },
462 
463     handlePressLogic: function (touchPoint) {
464         var nsp = this.convertToNodeSpace(touchPoint);
465         this._touchMoveStartLocation = nsp.x;
466         this._touchStartLocation = nsp.x;
467     },
468 
469     handleMoveLogic: function (touchPoint) {
470         var nsp = this.convertToNodeSpace(touchPoint);
471         var offset = 0.0;
472         var moveX = nsp.x;
473         offset = moveX - this._touchMoveStartLocation;
474         this._touchMoveStartLocation = moveX;
475         if (offset < 0) {
476             this._touchMoveDir = ccs.PVTouchDir.touchLeft;
477         }
478         else if (offset > 0) {
479             this._touchMoveDir = ccs.PVTouchDir.touchRight;
480         }
481         this.scrollPages(offset);
482     },
483 
484     handleReleaseLogic: function (touchPoint) {
485         if (this._pages.length <= 0) {
486             return;
487         }
488         var curPage = this._pages[this._curPageIdx];
489         if (curPage) {
490             var curPagePos = curPage.getPosition();
491             var pageCount = this._pages.length;
492             var curPageLocation = curPagePos.x;
493             var pageWidth = this.getSize().width;
494             var boundary = pageWidth / 2.0;
495             if (curPageLocation <= -boundary) {
496                 if (this._curPageIdx >= pageCount - 1)
497                     this.scrollPages(-curPageLocation);
498                 else
499                     this.scrollToPage(this._curPageIdx + 1);
500             }
501             else if (curPageLocation >= boundary) {
502                 if (this._curPageIdx <= 0)
503                     this.scrollPages(-curPageLocation);
504                 else
505                     this.scrollToPage(this._curPageIdx - 1);
506             }
507             else {
508                 this.scrollToPage(this._curPageIdx);
509             }
510         }
511     },
512 
513     checkChildInfo: function (handleState, sender, touchPoint) {
514         if(this._enabled && this._touchEnabled)
515             this.interceptTouchEvent(handleState, sender, touchPoint);
516     },
517 
518     interceptTouchEvent: function (handleState, sender, touchPoint) {
519         switch (handleState) {
520             case 0:
521                 this.handlePressLogic(touchPoint);
522                 break;
523             case 1:
524                 var offset = 0;
525                 offset = Math.abs(sender.getTouchStartPos().x - touchPoint.x);
526                 if (offset > this._childFocusCancelOffset) {
527                     sender.setFocused(false);
528                     this.handleMoveLogic(touchPoint);
529                 }
530                 break;
531             case 2:
532                 this.handleReleaseLogic(touchPoint);
533                 break;
534 
535             case 3:
536                 break;
537         }
538     },
539 
540     pageTurningEvent: function () {
541         if (this._pageViewEventListener && this._pageViewEventSelector) {
542             this._pageViewEventSelector.call(this._pageViewEventListener, this, ccs.PageViewEventType.turning);
543         }
544     },
545 
546     /**
547      * @param {Function} selector
548      * @param {Object} target
549      */
550     addEventListenerPageView: function (selector, target) {
551         this._pageViewEventSelector = selector;
552         this._pageViewEventListener = target;
553     },
554 
555     /**
556      * get pages
557      * @returns {Array}
558      */
559     getPages:function(){
560         return this._pages;
561     },
562 
563     /**
564      * get cur page index
565      * @returns {number}
566      */
567     getCurPageIndex: function () {
568         return this._curPageIdx;
569     },
570 
571     /**
572      * Returns the "class name" of widget.
573      * @returns {string}
574      */
575     getDescription: function () {
576         return "PageView";
577     },
578 
579     createCloneInstance: function () {
580         return ccs.PageView.create();
581     },
582 
583     copyClonedWidgetChildren: function (model) {
584         var arrayPages = model.getPages();
585         for (var i = 0; i < arrayPages.length; i++) {
586             var page = arrayPages[i];
587             this.addPage(page.clone());
588         }
589     },
590 
591     copySpecialProperties: function (pageView) {
592         ccs.Layout.prototype.copySpecialProperties.call(this, pageView);
593     },
594 
595     doLayout: function () {
596         if (!this._doLayoutDirty)
597             return;
598         this._doLayoutDirty = false;
599     }
600 });
601 /**
602  * allocates and initializes a UIPageView.
603  * @constructs
604  * @return {ccs.PageView}
605  * @example
606  * // example
607  * var uiPageView = ccs.PageView.create();
608  */
609 ccs.PageView.create = function () {
610     var uiPageView = new ccs.PageView();
611     if (uiPageView && uiPageView.init()) {
612         return uiPageView;
613     }
614     return null;
615 };