1 /**************************************************************************** 2 Copyright (c) 2008-2010 Ricardo Quesada 3 Copyright (c) 2011-2012 cocos2d-x.org 4 Copyright (c) 2013-2014 Chukong Technologies Inc. 5 Copyright (c) 2010 Sangwoo Im 6 7 http://www.cocos2d-x.org 8 9 Permission is hereby granted, free of charge, to any person obtaining a copy 10 of this software and associated documentation files (the "Software"), to deal 11 in the Software without restriction, including without limitation the rights 12 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 copies of the Software, and to permit persons to whom the Software is 14 furnished to do so, subject to the following conditions: 15 16 The above copyright notice and this permission notice shall be included in 17 all copies or substantial portions of the Software. 18 19 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 THE SOFTWARE. 26 ****************************************************************************/ 27 28 /** 29 * @ignore 30 */ 31 cc.SCROLLVIEW_DIRECTION_NONE = -1; 32 33 cc.SCROLLVIEW_DIRECTION_HORIZONTAL = 0; 34 35 cc.SCROLLVIEW_DIRECTION_VERTICAL = 1; 36 37 cc.SCROLLVIEW_DIRECTION_BOTH = 2; 38 39 var SCROLL_DEACCEL_RATE = 0.95; 40 var SCROLL_DEACCEL_DIST = 1.0; 41 var BOUNCE_DURATION = 0.15; 42 var INSET_RATIO = 0.2; 43 var MOVE_INCH = 7.0/160.0; 44 var BOUNCE_BACK_FACTOR = 0.35; 45 46 cc.convertDistanceFromPointToInch = function(pointDis){ 47 var eglViewer = cc.view; 48 var factor = (eglViewer.getScaleX() + eglViewer.getScaleY())/2; 49 return (pointDis * factor) / 160; // CCDevice::getDPI() default value 50 }; 51 52 cc.ScrollViewDelegate = cc.Class.extend({ 53 scrollViewDidScroll:function (view) { 54 }, 55 scrollViewDidZoom:function (view) { 56 } 57 }); 58 59 /** 60 * ScrollView support for cocos2d -x. 61 * It provides scroll view functionalities to cocos2d projects natively. 62 * @class 63 * @extends cc.Layer 64 * 65 * @property {cc.Point} minOffset - <@readonly> The current container's minimum offset 66 * @property {cc.Point} maxOffset - <@readonly> The current container's maximum offset 67 * @property {Boolean} bounceable - Indicate whether the scroll view is bounceable 68 * @property {cc.Size} viewSize - The size of the scroll view 69 * @property {cc.Layer} container - The inside container of the scroll view 70 * @property {Number} direction - The direction allowed to scroll: cc.SCROLLVIEW_DIRECTION_BOTH by default, or cc.SCROLLVIEW_DIRECTION_NONE | cc.SCROLLVIEW_DIRECTION_HORIZONTAL | cc.SCROLLVIEW_DIRECTION_VERTICAL 71 * @property {cc.ScrollViewDelegate} delegate - The inside container of the scroll view 72 * @property {Boolean} clippingToBounds - Indicate whether the scroll view clips its children 73 */ 74 cc.ScrollView = cc.Layer.extend(/** @lends cc.ScrollView# */{ 75 _zoomScale:0, 76 _minZoomScale:0, 77 _maxZoomScale:0, 78 _delegate:null, 79 _direction:cc.SCROLLVIEW_DIRECTION_BOTH, 80 _dragging:false, 81 _contentOffset:null, 82 _container:null, 83 _touchMoved:false, 84 _maxInset:null, 85 _minInset:null, 86 _bounceable:false, 87 _clippingToBounds:false, 88 _scrollDistance:null, 89 _touchPoint:null, 90 _touchLength:0, 91 _touches:null, 92 _viewSize:null, 93 _minScale:0, 94 _maxScale:0, 95 96 //scissor rect for parent, just for restoring GL_SCISSOR_BOX 97 _parentScissorRect:null, 98 _scissorRestored:false, 99 100 // cache object 101 _tmpViewRect:null, 102 _touchListener: null, 103 _className:"ScrollView", 104 105 /** 106 * @contructor 107 * @param size 108 * @param container 109 * @returns {ScrollView} 110 */ 111 ctor:function (size, container) { 112 cc.Layer.prototype.ctor.call(this); 113 this._contentOffset = cc.p(0,0); 114 this._maxInset = cc.p(0, 0); 115 this._minInset = cc.p(0, 0); 116 this._scrollDistance = cc.p(0, 0); 117 this._touchPoint = cc.p(0, 0); 118 this._touches = []; 119 this._viewSize = cc.size(0, 0); 120 this._parentScissorRect = new cc.Rect(0,0,0,0); 121 this._tmpViewRect = new cc.Rect(0,0,0,0); 122 123 if(container != undefined) 124 this.initWithViewSize(size, container); 125 else 126 this.initWithViewSize(cc.size(200, 200), null); 127 128 }, 129 130 init:function () { 131 return this.initWithViewSize(cc.size(200, 200), null); 132 }, 133 134 /** 135 * initialized whether success or fail 136 * @param {cc.Size} size 137 * @param {cc.Node} container 138 * @return {Boolean} 139 */ 140 initWithViewSize:function (size, container) { 141 var pZero = cc.p(0,0); 142 if (cc.Layer.prototype.init.call(this)) { 143 this._container = container; 144 145 if (!this._container) { 146 this._container = cc.Layer.create(); 147 this._container.ignoreAnchorPointForPosition(false); 148 this._container.setAnchorPoint(pZero); 149 } 150 151 this.setViewSize(size); 152 153 this.setTouchEnabled(true); 154 this._touches.length = 0; 155 this._delegate = null; 156 this._bounceable = true; 157 this._clippingToBounds = true; 158 159 //this._container.setContentSize(CCSizeZero); 160 this._direction = cc.SCROLLVIEW_DIRECTION_BOTH; 161 this._container.setPosition(pZero); 162 this._touchLength = 0.0; 163 164 this.addChild(this._container); 165 this._minScale = this._maxScale = 1.0; 166 return true; 167 } 168 return false; 169 }, 170 171 /** 172 * Sets a new content offset. It ignores max/min offset. It just sets what's given. (just like UIKit's UIScrollView) 173 * 174 * @param {cc.Point} offset new offset 175 * @param {Number} [animated=] If true, the view will scroll to the new offset 176 */ 177 setContentOffset: function (offset, animated) { 178 if (animated) { //animate scrolling 179 this.setContentOffsetInDuration(offset, BOUNCE_DURATION); 180 return; 181 } 182 if (!this._bounceable) { 183 var minOffset = this.minContainerOffset(); 184 var maxOffset = this.maxContainerOffset(); 185 186 offset.x = Math.max(minOffset.x, Math.min(maxOffset.x, offset.x)); 187 offset.y = Math.max(minOffset.y, Math.min(maxOffset.y, offset.y)); 188 } 189 190 this._container.setPosition(offset); 191 var locDelegate = this._delegate; 192 if (locDelegate != null && locDelegate.scrollViewDidScroll) { 193 locDelegate.scrollViewDidScroll(this); 194 } 195 196 }, 197 198 getContentOffset:function () { 199 var locPos = this._container.getPosition(); 200 return cc.p(locPos.x, locPos.y); 201 }, 202 203 /** 204 * <p>Sets a new content offset. It ignores max/min offset. It just sets what's given. (just like UIKit's UIScrollView) <br/> 205 * You can override the animation duration with this method. 206 * </p> 207 * @param {cc.Point} offset new offset 208 * @param {Number} dt animation duration 209 */ 210 setContentOffsetInDuration:function (offset, dt) { 211 var scroll = cc.MoveTo.create(dt, offset); 212 var expire = cc.CallFunc.create(this._stoppedAnimatedScroll, this); 213 this._container.runAction(cc.Sequence.create(scroll, expire)); 214 this.schedule(this._performedAnimatedScroll); 215 }, 216 217 /** 218 * Sets a new scale and does that for a predefined duration. 219 * 220 * @param {Number} scale a new scale vale 221 * @param {Boolean} [animated=null] if YES, scaling is animated 222 */ 223 setZoomScale: function (scale, animated) { 224 if (animated) { 225 this.setZoomScaleInDuration(scale, BOUNCE_DURATION); 226 return; 227 } 228 229 var locContainer = this._container; 230 if (locContainer.getScale() != scale) { 231 var oldCenter, newCenter; 232 var center; 233 234 if (this._touchLength == 0.0) { 235 var locViewSize = this._viewSize; 236 center = cc.p(locViewSize.width * 0.5, locViewSize.height * 0.5); 237 center = this.convertToWorldSpace(center); 238 } else 239 center = this._touchPoint; 240 241 oldCenter = locContainer.convertToNodeSpace(center); 242 locContainer.setScale(Math.max(this._minScale, Math.min(this._maxScale, scale))); 243 newCenter = locContainer.convertToWorldSpace(oldCenter); 244 245 var offset = cc.pSub(center, newCenter); 246 if (this._delegate && this._delegate.scrollViewDidZoom) 247 this._delegate.scrollViewDidZoom(this); 248 this.setContentOffset(cc.pAdd(locContainer.getPosition(), offset)); 249 } 250 }, 251 252 getZoomScale:function () { 253 return this._container.getScale(); 254 }, 255 256 /** 257 * Sets a new scale for container in a given duration. 258 * 259 * @param {Number} s a new scale value 260 * @param {Number} dt animation duration 261 */ 262 setZoomScaleInDuration:function (s, dt) { 263 if (dt > 0) { 264 var locScale = this._container.getScale(); 265 if (locScale != s) { 266 var scaleAction = cc.ActionTween.create(dt, "zoomScale", locScale, s); 267 this.runAction(scaleAction); 268 } 269 } else { 270 this.setZoomScale(s); 271 } 272 }, 273 274 /** 275 * Returns the current container's minimum offset. You may want this while you animate scrolling by yourself 276 * @return {cc.Point} Returns the current container's minimum offset. 277 */ 278 minContainerOffset:function () { 279 var locContainer = this._container; 280 var locContentSize = locContainer.getContentSize(), locViewSize = this._viewSize; 281 return cc.p(locViewSize.width - locContentSize.width * locContainer.getScaleX(), 282 locViewSize.height - locContentSize.height * locContainer.getScaleY()); 283 }, 284 285 /** 286 * Returns the current container's maximum offset. You may want this while you animate scrolling by yourself 287 * @return {cc.Point} Returns the current container's maximum offset. 288 */ 289 maxContainerOffset:function () { 290 return cc.p(0.0, 0.0); 291 }, 292 293 /** 294 * Determines if a given node's bounding box is in visible bounds 295 * @param {cc.Node} node 296 * @return {Boolean} YES if it is in visible bounds 297 */ 298 isNodeVisible:function (node) { 299 var offset = this.getContentOffset(); 300 var size = this.getViewSize(); 301 var scale = this.getZoomScale(); 302 303 var viewRect = cc.rect(-offset.x / scale, -offset.y / scale, size.width / scale, size.height / scale); 304 305 return cc.rectIntersectsRect(viewRect, node.getBoundingBox()); 306 }, 307 308 /** 309 * Provided to make scroll view compatible with SWLayer's pause method 310 */ 311 pause:function (sender) { 312 this._container.pause(); 313 var selChildren = this._container.getChildren(); 314 for (var i = 0; i < selChildren.length; i++) { 315 selChildren[i].pause(); 316 } 317 this._super(); 318 }, 319 320 /** 321 * Provided to make scroll view compatible with SWLayer's resume method 322 */ 323 resume:function (sender) { 324 var selChildren = this._container.getChildren(); 325 for (var i = 0, len = selChildren.length; i < len; i++) { 326 selChildren[i].resume(); 327 } 328 this._container.resume(); 329 this._super(); 330 }, 331 332 isDragging:function () { 333 return this._dragging; 334 }, 335 isTouchMoved:function () { 336 return this._touchMoved; 337 }, 338 isBounceable:function () { 339 return this._bounceable; 340 }, 341 setBounceable:function (bounceable) { 342 this._bounceable = bounceable; 343 }, 344 345 /** 346 * <p> 347 * size to clip. CCNode boundingBox uses contentSize directly. <br/> 348 * It's semantically different what it actually means to common scroll views. <br/> 349 * Hence, this scroll view will use a separate size property. 350 * </p> 351 */ 352 getViewSize:function () { 353 return this._viewSize; 354 }, 355 356 setViewSize:function (size) { 357 this._viewSize = size; 358 cc.Node.prototype.setContentSize.call(this,size); 359 }, 360 361 getContainer:function () { 362 return this._container; 363 }, 364 365 setContainer:function (container) { 366 // Make sure that 'm_pContainer' has a non-NULL value since there are 367 // lots of logic that use 'm_pContainer'. 368 if (!container) 369 return; 370 371 this.removeAllChildren(true); 372 373 this._container = container; 374 container.ignoreAnchorPointForPosition(false); 375 container.setAnchorPoint(0, 0); 376 377 this.addChild(container); 378 this.setViewSize(this._viewSize); 379 }, 380 381 /** 382 * direction allowed to scroll. CCScrollViewDirectionBoth by default. 383 */ 384 getDirection:function () { 385 return this._direction; 386 }, 387 setDirection:function (direction) { 388 this._direction = direction; 389 }, 390 391 getDelegate:function () { 392 return this._delegate; 393 }, 394 setDelegate:function (delegate) { 395 this._delegate = delegate; 396 }, 397 398 /** override functions */ 399 // optional 400 onTouchBegan:function (touch, event) { 401 if (!this.isVisible()) 402 return false; 403 //var frameOriginal = this.getParent().convertToWorldSpace(this.getPosition()); 404 //var frame = cc.rect(frameOriginal.x, frameOriginal.y, this._viewSize.width, this._viewSize.height); 405 var frame = this._getViewRect(); 406 407 //dispatcher does not know about clipping. reject touches outside visible bounds. 408 var locContainer = this._container; 409 var locPoint = locContainer.convertToWorldSpace(locContainer.convertTouchToNodeSpace(touch)); 410 var locTouches = this._touches; 411 if (locTouches.length > 2 || this._touchMoved || !cc.rectContainsPoint(frame, locPoint)) 412 return false; 413 414 locTouches.push(touch); 415 //} 416 417 if (locTouches.length === 1) { // scrolling 418 this._touchPoint = this.convertTouchToNodeSpace(touch); 419 this._touchMoved = false; 420 this._dragging = true; //dragging started 421 this._scrollDistance.x = 0; 422 this._scrollDistance.y = 0; 423 this._touchLength = 0.0; 424 } else if (locTouches.length == 2) { 425 this._touchPoint = cc.pMidpoint(this.convertTouchToNodeSpace(locTouches[0]), 426 this.convertTouchToNodeSpace(locTouches[1])); 427 this._touchLength = cc.pDistance(locContainer.convertTouchToNodeSpace(locTouches[0]), 428 locContainer.convertTouchToNodeSpace(locTouches[1])); 429 this._dragging = false; 430 } 431 return true; 432 }, 433 434 onTouchMoved:function (touch, event) { 435 if (!this.isVisible()) 436 return; 437 438 if (this._touches.length === 1 && this._dragging) { // scrolling 439 this._touchMoved = true; 440 //var frameOriginal = this.getParent().convertToWorldSpace(this.getPosition()); 441 //var frame = cc.rect(frameOriginal.x, frameOriginal.y, this._viewSize.width, this._viewSize.height); 442 var frame = this._getViewRect(); 443 444 //var newPoint = this.convertTouchToNodeSpace(this._touches[0]); 445 var newPoint = this.convertTouchToNodeSpace(touch); 446 var moveDistance = cc.pSub(newPoint, this._touchPoint); 447 448 var dis = 0.0, locDirection = this._direction, pos; 449 if (locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL){ 450 dis = moveDistance.y; 451 pos = this._container.getPositionY(); 452 if (!(this.minContainerOffset().y <= pos && pos <= this.maxContainerOffset().y)) 453 moveDistance.y *= BOUNCE_BACK_FACTOR; 454 } else if (locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL){ 455 dis = moveDistance.x; 456 pos = this._container.getPositionX(); 457 if (!(this.minContainerOffset().x <= pos && pos <= this.maxContainerOffset().x)) 458 moveDistance.x *= BOUNCE_BACK_FACTOR; 459 }else { 460 dis = Math.sqrt(moveDistance.x * moveDistance.x + moveDistance.y * moveDistance.y); 461 462 pos = this._container.getPositionY(); 463 var _minOffset = this.minContainerOffset(), _maxOffset = this.maxContainerOffset(); 464 if (!(_minOffset.y <= pos && pos <= _maxOffset.y)) 465 moveDistance.y *= BOUNCE_BACK_FACTOR; 466 467 pos = this._container.getPositionX(); 468 if (!(_minOffset.x <= pos && pos <= _maxOffset.x)) 469 moveDistance.x *= BOUNCE_BACK_FACTOR; 470 } 471 472 if (!this._touchMoved && Math.abs(cc.convertDistanceFromPointToInch(dis)) < MOVE_INCH ){ 473 //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y); 474 return; 475 } 476 477 if (!this._touchMoved){ 478 moveDistance.x = 0; 479 moveDistance.y = 0; 480 } 481 482 this._touchPoint = newPoint; 483 this._touchMoved = true; 484 485 if (this._dragging) { 486 switch (locDirection) { 487 case cc.SCROLLVIEW_DIRECTION_VERTICAL: 488 moveDistance.x = 0.0; 489 break; 490 case cc.SCROLLVIEW_DIRECTION_HORIZONTAL: 491 moveDistance.y = 0.0; 492 break; 493 default: 494 break; 495 } 496 497 var locPosition = this._container.getPosition(); 498 var newX = locPosition.x + moveDistance.x; 499 var newY = locPosition.y + moveDistance.y; 500 501 this._scrollDistance = moveDistance; 502 this.setContentOffset(cc.p(newX, newY)); 503 } 504 } else if (this._touches.length === 2 && !this._dragging) { 505 var len = cc.pDistance(this._container.convertTouchToNodeSpace(this._touches[0]), 506 this._container.convertTouchToNodeSpace(this._touches[1])); 507 this.setZoomScale(this.getZoomScale() * len / this._touchLength); 508 } 509 }, 510 511 onTouchEnded:function (touch, event) { 512 if (!this.isVisible()) 513 return; 514 515 if (this._touches.length == 1 && this._touchMoved) 516 this.schedule(this._deaccelerateScrolling); 517 518 this._touches.length = 0; 519 this._dragging = false; 520 this._touchMoved = false; 521 }, 522 523 onTouchCancelled:function (touch, event) { 524 if (!this.isVisible()) 525 return; 526 527 this._touches.length = 0; 528 this._dragging = false; 529 this._touchMoved = false; 530 }, 531 532 setContentSize: function (size, height) { 533 if (this.getContainer() != null) { 534 if(height === undefined) 535 this.getContainer().setContentSize(size); 536 else 537 this.getContainer().setContentSize(size, height); 538 this.updateInset(); 539 } 540 }, 541 _setWidth: function (value) { 542 var container = this.getContainer(); 543 if (container != null) { 544 container._setWidth(value); 545 this.updateInset(); 546 } 547 }, 548 _setHeight: function (value) { 549 var container = this.getContainer(); 550 if (container != null) { 551 container._setHeight(value); 552 this.updateInset(); 553 } 554 }, 555 556 getContentSize:function () { 557 return this._container.getContentSize(); 558 }, 559 560 updateInset:function () { 561 if (this.getContainer() != null) { 562 var locViewSize = this._viewSize; 563 var tempOffset = this.maxContainerOffset(); 564 this._maxInset.x = tempOffset.x + locViewSize.width * INSET_RATIO; 565 this._maxInset.y = tempOffset.y + locViewSize.height * INSET_RATIO; 566 tempOffset = this.minContainerOffset(); 567 this._minInset.x = tempOffset.x - locViewSize.width * INSET_RATIO; 568 this._minInset.y = tempOffset.y - locViewSize.height * INSET_RATIO; 569 } 570 }, 571 572 /** 573 * Determines whether it clips its children or not. 574 */ 575 isClippingToBounds:function () { 576 return this._clippingToBounds; 577 }, 578 579 setClippingToBounds:function (clippingToBounds) { 580 this._clippingToBounds = clippingToBounds; 581 }, 582 583 visit:function (ctx) { 584 // quick return if not visible 585 if (!this.isVisible()) 586 return; 587 588 var context = ctx || cc._renderContext; 589 var i, locChildren = this._children, selChild, childrenLen; 590 if (cc._renderType === cc._RENDER_TYPE_CANVAS) { 591 context.save(); 592 this.transform(context); 593 this._beforeDraw(context); 594 595 if (locChildren && locChildren.length > 0) { 596 childrenLen = locChildren.length; 597 this.sortAllChildren(); 598 // draw children zOrder < 0 599 for (i = 0; i < childrenLen; i++) { 600 selChild = locChildren[i]; 601 if (selChild && selChild._localZOrder < 0) 602 selChild.visit(context); 603 else 604 break; 605 } 606 607 this.draw(context); // self draw 608 609 // draw children zOrder >= 0 610 for (; i < childrenLen; i++) 611 locChildren[i].visit(context); 612 } else{ 613 this.draw(context); // self draw 614 } 615 616 this._afterDraw(); 617 618 context.restore(); 619 } else { 620 cc.kmGLPushMatrix(); 621 var locGrid = this.grid; 622 if (locGrid && locGrid.isActive()) { 623 locGrid.beforeDraw(); 624 this.transformAncestors(); 625 } 626 627 this.transform(context); 628 this._beforeDraw(context); 629 if (locChildren && locChildren.length > 0) { 630 childrenLen = locChildren.length; 631 // draw children zOrder < 0 632 for (i = 0; i < childrenLen; i++) { 633 selChild = locChildren[i]; 634 if (selChild && selChild._localZOrder < 0) 635 selChild.visit(); 636 else 637 break; 638 } 639 640 // this draw 641 this.draw(context); 642 643 // draw children zOrder >= 0 644 for (; i < childrenLen; i++) 645 locChildren[i].visit(); 646 } else{ 647 this.draw(context); 648 } 649 650 this._afterDraw(context); 651 if (locGrid && locGrid.isActive()) 652 locGrid.afterDraw(this); 653 654 cc.kmGLPopMatrix(); 655 } 656 }, 657 658 addChild:function (child, zOrder, tag) { 659 if (!child) 660 throw new Error("child must not nil!"); 661 662 zOrder = zOrder || child.getLocalZOrder(); 663 tag = tag || child.getTag(); 664 665 //child.ignoreAnchorPointForPosition(false); 666 //child.setAnchorPoint(0, 0); 667 if (this._container != child) { 668 this._container.addChild(child, zOrder, tag); 669 } else { 670 cc.Layer.prototype.addChild.call(this, child, zOrder, tag); 671 } 672 }, 673 674 isTouchEnabled: function(){ 675 return this._touchListener != null; 676 }, 677 678 setTouchEnabled:function (e) { 679 if(this._touchListener) 680 cc.eventManager.removeListener(this._touchListener); 681 this._touchListener = null; 682 if (!e) { 683 this._dragging = false; 684 this._touchMoved = false; 685 this._touches.length = 0; 686 } else { 687 var listener = cc.EventListener.create({ 688 event: cc.EventListener.TOUCH_ONE_BY_ONE 689 }); 690 if(this.onTouchBegan) 691 listener.onTouchBegan = this.onTouchBegan.bind(this); 692 if(this.onTouchMoved) 693 listener.onTouchMoved = this.onTouchMoved.bind(this); 694 if(this.onTouchEnded) 695 listener.onTouchEnded = this.onTouchEnded.bind(this); 696 if(this.onTouchCancelled) 697 listener.onTouchCancelled = this.onTouchCancelled.bind(this); 698 this._touchListener = listener; 699 cc.eventManager.addListener(listener, this); 700 } 701 }, 702 703 /** 704 * Init this object with a given size to clip its content. 705 * 706 * @param size view size 707 * @return initialized scroll view object 708 */ 709 _initWithViewSize:function (size) { 710 return null; 711 }, 712 713 /** 714 * Relocates the container at the proper offset, in bounds of max/min offsets. 715 * 716 * @param animated If YES, relocation is animated 717 */ 718 _relocateContainer:function (animated) { 719 var min = this.minContainerOffset(); 720 var max = this.maxContainerOffset(); 721 var locDirection = this._direction; 722 723 var oldPoint = this._container.getPosition(); 724 var newX = oldPoint.x; 725 var newY = oldPoint.y; 726 if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL) { 727 newX = Math.max(newX, min.x); 728 newX = Math.min(newX, max.x); 729 } 730 731 if (locDirection == cc.SCROLLVIEW_DIRECTION_BOTH || locDirection == cc.SCROLLVIEW_DIRECTION_VERTICAL) { 732 newY = Math.min(newY, max.y); 733 newY = Math.max(newY, min.y); 734 } 735 736 if (newY != oldPoint.y || newX != oldPoint.x) { 737 this.setContentOffset(cc.p(newX, newY), animated); 738 } 739 }, 740 /** 741 * implements auto-scrolling behavior. change SCROLL_DEACCEL_RATE as needed to choose <br/> 742 * deacceleration speed. it must be less than 1.0. 743 * 744 * @param {Number} dt delta 745 */ 746 _deaccelerateScrolling:function (dt) { 747 if (this._dragging) { 748 this.unschedule(this._deaccelerateScrolling); 749 return; 750 } 751 752 var maxInset, minInset; 753 var oldPosition = this._container.getPosition(); 754 var locScrollDistance = this._scrollDistance; 755 this._container.setPosition(oldPosition.x + locScrollDistance.x , oldPosition.y + locScrollDistance.y); 756 if (this._bounceable) { 757 maxInset = this._maxInset; 758 minInset = this._minInset; 759 } else { 760 maxInset = this.maxContainerOffset(); 761 minInset = this.minContainerOffset(); 762 } 763 764 //check to see if offset lies within the inset bounds 765 var newX = this._container.getPositionX(); 766 var newY = this._container.getPositionY(); 767 768 locScrollDistance.x = locScrollDistance.x * SCROLL_DEACCEL_RATE; 769 locScrollDistance.y = locScrollDistance.y * SCROLL_DEACCEL_RATE; 770 771 this.setContentOffset(cc.p(newX, newY)); 772 773 if ((Math.abs(locScrollDistance.x) <= SCROLL_DEACCEL_DIST && 774 Math.abs(locScrollDistance.y) <= SCROLL_DEACCEL_DIST) || 775 newY > maxInset.y || newY < minInset.y || 776 newX > maxInset.x || newX < minInset.x || 777 newX == maxInset.x || newX == minInset.x || 778 newY == maxInset.y || newY == minInset.y) { 779 this.unschedule(this._deaccelerateScrolling); 780 this._relocateContainer(true); 781 } 782 }, 783 /** 784 * This method makes sure auto scrolling causes delegate to invoke its method 785 */ 786 _performedAnimatedScroll:function (dt) { 787 if (this._dragging) { 788 this.unschedule(this._performedAnimatedScroll); 789 return; 790 } 791 792 if (this._delegate && this._delegate.scrollViewDidScroll) 793 this._delegate.scrollViewDidScroll(this); 794 }, 795 /** 796 * Expire animated scroll delegate calls 797 */ 798 _stoppedAnimatedScroll:function (node) { 799 this.unschedule(this._performedAnimatedScroll); 800 // After the animation stopped, "scrollViewDidScroll" should be invoked, this could fix the bug of lack of tableview cells. 801 if (this._delegate && this._delegate.scrollViewDidScroll) { 802 this._delegate.scrollViewDidScroll(this); 803 } 804 }, 805 806 /** 807 * clip this view so that outside of the visible bounds can be hidden. 808 */ 809 _beforeDraw:function (context) { 810 if (this._clippingToBounds) { 811 this._scissorRestored = false; 812 var frame = this._getViewRect(), locEGLViewer = cc.view; 813 814 var scaleX = this.getScaleX(); 815 var scaleY = this.getScaleY(); 816 817 var ctx = context || cc._renderContext; 818 if (cc._renderType === cc._RENDER_TYPE_CANVAS) { 819 var getWidth = (this._viewSize.width * scaleX) * locEGLViewer.getScaleX(); 820 var getHeight = (this._viewSize.height * scaleY) * locEGLViewer.getScaleY(); 821 var startX = 0; 822 var startY = 0; 823 824 ctx.beginPath(); 825 ctx.rect(startX, startY, getWidth, -getHeight); 826 ctx.clip(); 827 ctx.closePath(); 828 } else { 829 var EGLViewer = cc.view; 830 if(EGLViewer.isScissorEnabled()){ 831 this._scissorRestored = true; 832 this._parentScissorRect = EGLViewer.getScissorRect(); 833 //set the intersection of m_tParentScissorRect and frame as the new scissor rect 834 if (cc.rectIntersection(frame, this._parentScissorRect)) { 835 var locPSRect = this._parentScissorRect; 836 var x = Math.max(frame.x, locPSRect.x); 837 var y = Math.max(frame.y, locPSRect.y); 838 var xx = Math.min(frame.x + frame.width, locPSRect.x + locPSRect.width); 839 var yy = Math.min(frame.y + frame.height, locPSRect.y + locPSRect.height); 840 EGLViewer.setScissorInPoints(x, y, xx - x, yy - y); 841 } 842 }else{ 843 ctx.enable(ctx.SCISSOR_TEST); 844 //clip 845 EGLViewer.setScissorInPoints(frame.x, frame.y, frame.width, frame.height); 846 } 847 } 848 } 849 }, 850 /** 851 * retract what's done in beforeDraw so that there's no side effect to 852 * other nodes. 853 */ 854 _afterDraw:function (context) { 855 if (this._clippingToBounds && cc._renderType === cc._RENDER_TYPE_WEBGL) { 856 if (this._scissorRestored) { //restore the parent's scissor rect 857 var rect = this._parentScissorRect; 858 cc.view.setScissorInPoints(rect.x, rect.y, rect.width, rect.height) 859 }else{ 860 var ctx = context || cc._renderContext; 861 ctx.disable(ctx.SCISSOR_TEST); 862 } 863 } 864 }, 865 /** 866 * Zoom handling 867 */ 868 _handleZoom:function () { 869 }, 870 871 _getViewRect:function(){ 872 var screenPos = this.convertToWorldSpace(cc.p(0,0)); 873 var locViewSize = this._viewSize; 874 875 var scaleX = this.getScaleX(); 876 var scaleY = this.getScaleY(); 877 878 for (var p = this._parent; p != null; p = p.getParent()) { 879 scaleX *= p.getScaleX(); 880 scaleY *= p.getScaleY(); 881 } 882 883 // Support negative scaling. Not doing so causes intersectsRect calls 884 // (eg: to check if the touch was within the bounds) to return false. 885 // Note, CCNode::getScale will assert if X and Y scales are different. 886 if (scaleX < 0) { 887 screenPos.x += locViewSize.width * scaleX; 888 scaleX = -scaleX; 889 } 890 if (scaleY < 0) { 891 screenPos.y += locViewSize.height * scaleY; 892 scaleY = -scaleY; 893 } 894 895 var locViewRect = this._tmpViewRect; 896 locViewRect.x = screenPos.x; 897 locViewRect.y = screenPos.y; 898 locViewRect.width = locViewSize.width * scaleX; 899 locViewRect.height = locViewSize.height * scaleY; 900 return locViewRect; 901 } 902 }); 903 904 var _p = cc.ScrollView.prototype; 905 906 // Extended properties 907 /** @expose */ 908 _p.minOffset; 909 cc.defineGetterSetter(_p, "minOffset", _p.minContainerOffset); 910 /** @expose */ 911 _p.maxOffset; 912 cc.defineGetterSetter(_p, "maxOffset", _p.maxContainerOffset); 913 /** @expose */ 914 _p.bounceable; 915 cc.defineGetterSetter(_p, "bounceable", _p.isBounceable, _p.setBounceable); 916 /** @expose */ 917 _p.viewSize; 918 cc.defineGetterSetter(_p, "viewSize", _p.getViewSize, _p.setViewSize); 919 /** @expose */ 920 _p.container; 921 cc.defineGetterSetter(_p, "container", _p.getContainer, _p.setContainer); 922 /** @expose */ 923 _p.direction; 924 cc.defineGetterSetter(_p, "direction", _p.getDirection, _p.setDirection); 925 /** @expose */ 926 _p.delegate; 927 cc.defineGetterSetter(_p, "delegate", _p.getDelegate, _p.setDelegate); 928 /** @expose */ 929 _p.clippingToBounds; 930 cc.defineGetterSetter(_p, "clippingToBounds", _p.isClippingToBounds, _p.setClippingToBounds); 931 932 _p = null; 933 934 /** 935 * Returns an autoreleased scroll view object. 936 * @deprecated 937 * @param {cc.Size} size view size 938 * @param {cc.Node} container parent object 939 * @return {cc.ScrollView} scroll view object 940 */ 941 cc.ScrollView.create = function (size, container) { 942 return new cc.ScrollView(size, container); 943 };