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 if (!container && !this._container) { 144 container = new cc.Layer(); 145 } 146 if (container) { 147 this.setContainer(container); 148 } 149 this.setViewSize(size); 150 151 this.setTouchEnabled(true); 152 this._touches.length = 0; 153 this._delegate = null; 154 this._bounceable = true; 155 this._clippingToBounds = true; 156 157 //this._container.setContentSize(CCSizeZero); 158 this._direction = cc.SCROLLVIEW_DIRECTION_BOTH; 159 this._container.setPosition(pZero); 160 this._touchLength = 0.0; 161 162 this._minScale = this._maxScale = 1.0; 163 return true; 164 } 165 return false; 166 }, 167 168 /** 169 * Sets a new content offset. It ignores max/min offset. It just sets what's given. (just like UIKit's UIScrollView) 170 * 171 * @param {cc.Point} offset new offset 172 * @param {Number} [animated=] If true, the view will scroll to the new offset 173 */ 174 setContentOffset: function (offset, animated) { 175 if (animated) { //animate scrolling 176 this.setContentOffsetInDuration(offset, BOUNCE_DURATION); 177 return; 178 } 179 if (!this._bounceable) { 180 var minOffset = this.minContainerOffset(); 181 var maxOffset = this.maxContainerOffset(); 182 183 offset.x = Math.max(minOffset.x, Math.min(maxOffset.x, offset.x)); 184 offset.y = Math.max(minOffset.y, Math.min(maxOffset.y, offset.y)); 185 } 186 187 this._container.setPosition(offset); 188 var locDelegate = this._delegate; 189 if (locDelegate != null && locDelegate.scrollViewDidScroll) { 190 locDelegate.scrollViewDidScroll(this); 191 } 192 193 }, 194 195 getContentOffset:function () { 196 var locPos = this._container.getPosition(); 197 return cc.p(locPos.x, locPos.y); 198 }, 199 200 /** 201 * <p>Sets a new content offset. It ignores max/min offset. It just sets what's given. (just like UIKit's UIScrollView) <br/> 202 * You can override the animation duration with this method. 203 * </p> 204 * @param {cc.Point} offset new offset 205 * @param {Number} dt animation duration 206 */ 207 setContentOffsetInDuration:function (offset, dt) { 208 var scroll = cc.moveTo(dt, offset); 209 var expire = cc.callFunc(this._stoppedAnimatedScroll, this); 210 this._container.runAction(cc.sequence(scroll, expire)); 211 this.schedule(this._performedAnimatedScroll); 212 }, 213 214 /** 215 * Sets a new scale and does that for a predefined duration. 216 * 217 * @param {Number} scale a new scale vale 218 * @param {Boolean} [animated=null] if YES, scaling is animated 219 */ 220 setZoomScale: function (scale, animated) { 221 if (animated) { 222 this.setZoomScaleInDuration(scale, BOUNCE_DURATION); 223 return; 224 } 225 226 var locContainer = this._container; 227 if (locContainer.getScale() !== scale) { 228 var oldCenter, newCenter; 229 var center; 230 231 if (this._touchLength === 0.0) { 232 var locViewSize = this._viewSize; 233 center = cc.p(locViewSize.width * 0.5, locViewSize.height * 0.5); 234 center = this.convertToWorldSpace(center); 235 } else 236 center = this._touchPoint; 237 238 oldCenter = locContainer.convertToNodeSpace(center); 239 locContainer.setScale(Math.max(this._minScale, Math.min(this._maxScale, scale))); 240 newCenter = locContainer.convertToWorldSpace(oldCenter); 241 242 var offset = cc.pSub(center, newCenter); 243 if (this._delegate && this._delegate.scrollViewDidZoom) 244 this._delegate.scrollViewDidZoom(this); 245 this.setContentOffset(cc.pAdd(locContainer.getPosition(), offset)); 246 } 247 }, 248 249 getZoomScale:function () { 250 return this._container.getScale(); 251 }, 252 253 /** 254 * Sets a new scale for container in a given duration. 255 * 256 * @param {Number} s a new scale value 257 * @param {Number} dt animation duration 258 */ 259 setZoomScaleInDuration:function (s, dt) { 260 if (dt > 0) { 261 var locScale = this._container.getScale(); 262 if (locScale !== s) { 263 var scaleAction = cc.actionTween(dt, "zoomScale", locScale, s); 264 this.runAction(scaleAction); 265 } 266 } else { 267 this.setZoomScale(s); 268 } 269 }, 270 271 /** 272 * Returns the current container's minimum offset. You may want this while you animate scrolling by yourself 273 * @return {cc.Point} Returns the current container's minimum offset. 274 */ 275 minContainerOffset:function () { 276 var locContainer = this._container; 277 var locContentSize = locContainer.getContentSize(), locViewSize = this._viewSize; 278 return cc.p(locViewSize.width - locContentSize.width * locContainer.getScaleX(), 279 locViewSize.height - locContentSize.height * locContainer.getScaleY()); 280 }, 281 282 /** 283 * Returns the current container's maximum offset. You may want this while you animate scrolling by yourself 284 * @return {cc.Point} Returns the current container's maximum offset. 285 */ 286 maxContainerOffset:function () { 287 return cc.p(0.0, 0.0); 288 }, 289 290 /** 291 * Determines if a given node's bounding box is in visible bounds 292 * @param {cc.Node} node 293 * @return {Boolean} YES if it is in visible bounds 294 */ 295 isNodeVisible:function (node) { 296 var offset = this.getContentOffset(); 297 var size = this.getViewSize(); 298 var scale = this.getZoomScale(); 299 300 var viewRect = cc.rect(-offset.x / scale, -offset.y / scale, size.width / scale, size.height / scale); 301 302 return cc.rectIntersectsRect(viewRect, node.getBoundingBox()); 303 }, 304 305 /** 306 * Provided to make scroll view compatible with SWLayer's pause method 307 */ 308 pause:function (sender) { 309 this._container.pause(); 310 var selChildren = this._container.getChildren(); 311 for (var i = 0; i < selChildren.length; i++) { 312 selChildren[i].pause(); 313 } 314 this._super(); 315 }, 316 317 /** 318 * Provided to make scroll view compatible with SWLayer's resume method 319 */ 320 resume:function (sender) { 321 var selChildren = this._container.getChildren(); 322 for (var i = 0, len = selChildren.length; i < len; i++) { 323 selChildren[i].resume(); 324 } 325 this._container.resume(); 326 this._super(); 327 }, 328 329 isDragging:function () { 330 return this._dragging; 331 }, 332 isTouchMoved:function () { 333 return this._touchMoved; 334 }, 335 isBounceable:function () { 336 return this._bounceable; 337 }, 338 setBounceable:function (bounceable) { 339 this._bounceable = bounceable; 340 }, 341 342 /** 343 * <p> 344 * size to clip. CCNode boundingBox uses contentSize directly. <br/> 345 * It's semantically different what it actually means to common scroll views. <br/> 346 * Hence, this scroll view will use a separate size property. 347 * </p> 348 */ 349 getViewSize:function () { 350 return this._viewSize; 351 }, 352 353 setViewSize:function (size) { 354 this._viewSize = size; 355 cc.Node.prototype.setContentSize.call(this,size); 356 }, 357 358 getContainer:function () { 359 return this._container; 360 }, 361 362 setContainer:function (container) { 363 // Make sure that 'm_pContainer' has a non-NULL value since there are 364 // lots of logic that use 'm_pContainer'. 365 if (!container) 366 return; 367 368 this.removeAllChildren(true); 369 370 this._container = container; 371 container.ignoreAnchorPointForPosition(false); 372 container.setAnchorPoint(0, 0); 373 374 this.addChild(container); 375 this.setViewSize(this._viewSize); 376 }, 377 378 /** 379 * direction allowed to scroll. CCScrollViewDirectionBoth by default. 380 */ 381 getDirection:function () { 382 return this._direction; 383 }, 384 setDirection:function (direction) { 385 this._direction = direction; 386 }, 387 388 getDelegate:function () { 389 return this._delegate; 390 }, 391 setDelegate:function (delegate) { 392 this._delegate = delegate; 393 }, 394 395 /** override functions */ 396 // optional 397 onTouchBegan:function (touch, event) { 398 if (!this.isVisible()) 399 return false; 400 //var frameOriginal = this.getParent().convertToWorldSpace(this.getPosition()); 401 //var frame = cc.rect(frameOriginal.x, frameOriginal.y, this._viewSize.width, this._viewSize.height); 402 var frame = this._getViewRect(); 403 404 //dispatcher does not know about clipping. reject touches outside visible bounds. 405 var locContainer = this._container; 406 var locPoint = locContainer.convertToWorldSpace(locContainer.convertTouchToNodeSpace(touch)); 407 var locTouches = this._touches; 408 if (locTouches.length > 2 || this._touchMoved || !cc.rectContainsPoint(frame, locPoint)) 409 return false; 410 411 locTouches.push(touch); 412 //} 413 414 if (locTouches.length === 1) { // scrolling 415 this._touchPoint = this.convertTouchToNodeSpace(touch); 416 this._touchMoved = false; 417 this._dragging = true; //dragging started 418 this._scrollDistance.x = 0; 419 this._scrollDistance.y = 0; 420 this._touchLength = 0.0; 421 } else if (locTouches.length === 2) { 422 this._touchPoint = cc.pMidpoint(this.convertTouchToNodeSpace(locTouches[0]), 423 this.convertTouchToNodeSpace(locTouches[1])); 424 this._touchLength = cc.pDistance(locContainer.convertTouchToNodeSpace(locTouches[0]), 425 locContainer.convertTouchToNodeSpace(locTouches[1])); 426 this._dragging = false; 427 } 428 return true; 429 }, 430 431 onTouchMoved:function (touch, event) { 432 if (!this.isVisible()) 433 return; 434 435 this.setNodeDirty(); 436 437 if (this._touches.length === 1 && this._dragging) { // scrolling 438 this._touchMoved = true; 439 //var frameOriginal = this.getParent().convertToWorldSpace(this.getPosition()); 440 //var frame = cc.rect(frameOriginal.x, frameOriginal.y, this._viewSize.width, this._viewSize.height); 441 var frame = this._getViewRect(); 442 443 //var newPoint = this.convertTouchToNodeSpace(this._touches[0]); 444 var newPoint = this.convertTouchToNodeSpace(touch); 445 var moveDistance = cc.pSub(newPoint, this._touchPoint); 446 447 var dis = 0.0, locDirection = this._direction, pos; 448 if (locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL){ 449 dis = moveDistance.y; 450 pos = this._container.getPositionY(); 451 if (!(this.minContainerOffset().y <= pos && pos <= this.maxContainerOffset().y)) 452 moveDistance.y *= BOUNCE_BACK_FACTOR; 453 } else if (locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL){ 454 dis = moveDistance.x; 455 pos = this._container.getPositionX(); 456 if (!(this.minContainerOffset().x <= pos && pos <= this.maxContainerOffset().x)) 457 moveDistance.x *= BOUNCE_BACK_FACTOR; 458 }else { 459 dis = Math.sqrt(moveDistance.x * moveDistance.x + moveDistance.y * moveDistance.y); 460 461 pos = this._container.getPositionY(); 462 var _minOffset = this.minContainerOffset(), _maxOffset = this.maxContainerOffset(); 463 if (!(_minOffset.y <= pos && pos <= _maxOffset.y)) 464 moveDistance.y *= BOUNCE_BACK_FACTOR; 465 466 pos = this._container.getPositionX(); 467 if (!(_minOffset.x <= pos && pos <= _maxOffset.x)) 468 moveDistance.x *= BOUNCE_BACK_FACTOR; 469 } 470 471 if (!this._touchMoved && Math.abs(cc.convertDistanceFromPointToInch(dis)) < MOVE_INCH ){ 472 //CCLOG("Invalid movement, distance = [%f, %f], disInch = %f", moveDistance.x, moveDistance.y); 473 return; 474 } 475 476 if (!this._touchMoved){ 477 moveDistance.x = 0; 478 moveDistance.y = 0; 479 } 480 481 this._touchPoint = newPoint; 482 this._touchMoved = true; 483 484 if (this._dragging) { 485 switch (locDirection) { 486 case cc.SCROLLVIEW_DIRECTION_VERTICAL: 487 moveDistance.x = 0.0; 488 break; 489 case cc.SCROLLVIEW_DIRECTION_HORIZONTAL: 490 moveDistance.y = 0.0; 491 break; 492 default: 493 break; 494 } 495 496 var locPosition = this._container.getPosition(); 497 var newX = locPosition.x + moveDistance.x; 498 var newY = locPosition.y + moveDistance.y; 499 500 this._scrollDistance = moveDistance; 501 this.setContentOffset(cc.p(newX, newY)); 502 } 503 } else if (this._touches.length === 2 && !this._dragging) { 504 var len = cc.pDistance(this._container.convertTouchToNodeSpace(this._touches[0]), 505 this._container.convertTouchToNodeSpace(this._touches[1])); 506 this.setZoomScale(this.getZoomScale() * len / this._touchLength); 507 } 508 }, 509 510 onTouchEnded:function (touch, event) { 511 if (!this.isVisible()) 512 return; 513 514 if (this._touches.length === 1 && this._touchMoved) 515 this.schedule(this._deaccelerateScrolling); 516 517 this._touches.length = 0; 518 this._dragging = false; 519 this._touchMoved = false; 520 }, 521 522 onTouchCancelled:function (touch, event) { 523 if (!this.isVisible()) 524 return; 525 526 this._touches.length = 0; 527 this._dragging = false; 528 this._touchMoved = false; 529 }, 530 531 setContentSize: function (size, height) { 532 if (this.getContainer() !== null) { 533 if(height === undefined) 534 this.getContainer().setContentSize(size); 535 else 536 this.getContainer().setContentSize(size, height); 537 this.updateInset(); 538 } 539 }, 540 _setWidth: function (value) { 541 var container = this.getContainer(); 542 if (container !== null) { 543 container._setWidth(value); 544 this.updateInset(); 545 } 546 }, 547 _setHeight: function (value) { 548 var container = this.getContainer(); 549 if (container !== null) { 550 container._setHeight(value); 551 this.updateInset(); 552 } 553 }, 554 555 getContentSize:function () { 556 return this._container.getContentSize(); 557 }, 558 559 updateInset:function () { 560 if (this.getContainer() !== null) { 561 var locViewSize = this._viewSize; 562 var tempOffset = this.maxContainerOffset(); 563 this._maxInset.x = tempOffset.x + locViewSize.width * INSET_RATIO; 564 this._maxInset.y = tempOffset.y + locViewSize.height * INSET_RATIO; 565 tempOffset = this.minContainerOffset(); 566 this._minInset.x = tempOffset.x - locViewSize.width * INSET_RATIO; 567 this._minInset.y = tempOffset.y - locViewSize.height * INSET_RATIO; 568 } 569 }, 570 571 /** 572 * Determines whether it clips its children or not. 573 */ 574 isClippingToBounds:function () { 575 return this._clippingToBounds; 576 }, 577 578 setClippingToBounds:function (clippingToBounds) { 579 this._clippingToBounds = clippingToBounds; 580 }, 581 582 visit:function (parentCmd) { 583 // quick return if not visible 584 if (!this.isVisible()) 585 return; 586 587 this._renderCmd.visit(parentCmd); 588 }, 589 590 addChild:function (child, zOrder, tag) { 591 if (!child) 592 throw new Error("child must not nil!"); 593 594 zOrder = zOrder || child.getLocalZOrder(); 595 tag = tag || child.getTag(); 596 597 //child.ignoreAnchorPointForPosition(false); 598 //child.setAnchorPoint(0, 0); 599 if (this._container !== child) { 600 this._container.addChild(child, zOrder, tag); 601 } else { 602 cc.Layer.prototype.addChild.call(this, child, zOrder, tag); 603 } 604 }, 605 606 isTouchEnabled: function(){ 607 return this._touchListener !== null; 608 }, 609 610 setTouchEnabled:function (e) { 611 if(this._touchListener) 612 cc.eventManager.removeListener(this._touchListener); 613 this._touchListener = null; 614 if (!e) { 615 this._dragging = false; 616 this._touchMoved = false; 617 this._touches.length = 0; 618 } else { 619 var listener = cc.EventListener.create({ 620 event: cc.EventListener.TOUCH_ONE_BY_ONE 621 }); 622 if(this.onTouchBegan) 623 listener.onTouchBegan = this.onTouchBegan.bind(this); 624 if(this.onTouchMoved) 625 listener.onTouchMoved = this.onTouchMoved.bind(this); 626 if(this.onTouchEnded) 627 listener.onTouchEnded = this.onTouchEnded.bind(this); 628 if(this.onTouchCancelled) 629 listener.onTouchCancelled = this.onTouchCancelled.bind(this); 630 this._touchListener = listener; 631 cc.eventManager.addListener(listener, this); 632 } 633 }, 634 635 /** 636 * Init this object with a given size to clip its content. 637 * 638 * @param size view size 639 * @return initialized scroll view object 640 */ 641 _initWithViewSize:function (size) { 642 return null; 643 }, 644 645 /** 646 * Relocates the container at the proper offset, in bounds of max/min offsets. 647 * 648 * @param animated If YES, relocation is animated 649 */ 650 _relocateContainer:function (animated) { 651 var min = this.minContainerOffset(); 652 var max = this.maxContainerOffset(); 653 var locDirection = this._direction; 654 655 var oldPoint = this._container.getPosition(); 656 var newX = oldPoint.x; 657 var newY = oldPoint.y; 658 if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_HORIZONTAL) { 659 newX = Math.max(newX, min.x); 660 newX = Math.min(newX, max.x); 661 } 662 663 if (locDirection === cc.SCROLLVIEW_DIRECTION_BOTH || locDirection === cc.SCROLLVIEW_DIRECTION_VERTICAL) { 664 newY = Math.min(newY, max.y); 665 newY = Math.max(newY, min.y); 666 } 667 668 if (newY !== oldPoint.y || newX !== oldPoint.x) { 669 this.setContentOffset(cc.p(newX, newY), animated); 670 } 671 }, 672 /** 673 * implements auto-scrolling behavior. change SCROLL_DEACCEL_RATE as needed to choose <br/> 674 * deacceleration speed. it must be less than 1.0. 675 * 676 * @param {Number} dt delta 677 */ 678 _deaccelerateScrolling:function (dt) { 679 if (this._dragging) { 680 this.unschedule(this._deaccelerateScrolling); 681 return; 682 } 683 684 var maxInset, minInset; 685 var oldPosition = this._container.getPosition(); 686 var locScrollDistance = this._scrollDistance; 687 this._container.setPosition(oldPosition.x + locScrollDistance.x , oldPosition.y + locScrollDistance.y); 688 if (this._bounceable) { 689 maxInset = this._maxInset; 690 minInset = this._minInset; 691 } else { 692 maxInset = this.maxContainerOffset(); 693 minInset = this.minContainerOffset(); 694 } 695 696 //check to see if offset lies within the inset bounds 697 var newX = this._container.getPositionX(); 698 var newY = this._container.getPositionY(); 699 700 locScrollDistance.x = locScrollDistance.x * SCROLL_DEACCEL_RATE; 701 locScrollDistance.y = locScrollDistance.y * SCROLL_DEACCEL_RATE; 702 703 this.setContentOffset(cc.p(newX, newY)); 704 705 if ((Math.abs(locScrollDistance.x) <= SCROLL_DEACCEL_DIST && 706 Math.abs(locScrollDistance.y) <= SCROLL_DEACCEL_DIST) || 707 newY > maxInset.y || newY < minInset.y || 708 newX > maxInset.x || newX < minInset.x || 709 newX === maxInset.x || newX === minInset.x || 710 newY === maxInset.y || newY === minInset.y) { 711 this.unschedule(this._deaccelerateScrolling); 712 this._relocateContainer(true); 713 } 714 }, 715 /** 716 * This method makes sure auto scrolling causes delegate to invoke its method 717 */ 718 _performedAnimatedScroll:function (dt) { 719 if (this._dragging) { 720 this.unschedule(this._performedAnimatedScroll); 721 return; 722 } 723 724 if (this._delegate && this._delegate.scrollViewDidScroll) 725 this._delegate.scrollViewDidScroll(this); 726 }, 727 /** 728 * Expire animated scroll delegate calls 729 */ 730 _stoppedAnimatedScroll:function (node) { 731 this.unschedule(this._performedAnimatedScroll); 732 // After the animation stopped, "scrollViewDidScroll" should be invoked, this could fix the bug of lack of tableview cells. 733 if (this._delegate && this._delegate.scrollViewDidScroll) { 734 this._delegate.scrollViewDidScroll(this); 735 } 736 }, 737 738 /** 739 * Zoom handling 740 */ 741 _handleZoom:function () { 742 }, 743 744 _getViewRect:function(){ 745 var screenPos = this.convertToWorldSpace(cc.p(0,0)); 746 var locViewSize = this._viewSize; 747 748 var scaleX = this.getScaleX(); 749 var scaleY = this.getScaleY(); 750 751 for (var p = this._parent; p != null; p = p.getParent()) { 752 scaleX *= p.getScaleX(); 753 scaleY *= p.getScaleY(); 754 } 755 756 // Support negative scaling. Not doing so causes intersectsRect calls 757 // (eg: to check if the touch was within the bounds) to return false. 758 // Note, CCNode::getScale will assert if X and Y scales are different. 759 if (scaleX < 0) { 760 screenPos.x += locViewSize.width * scaleX; 761 scaleX = -scaleX; 762 } 763 if (scaleY < 0) { 764 screenPos.y += locViewSize.height * scaleY; 765 scaleY = -scaleY; 766 } 767 768 var locViewRect = this._tmpViewRect; 769 locViewRect.x = screenPos.x; 770 locViewRect.y = screenPos.y; 771 locViewRect.width = locViewSize.width * scaleX; 772 locViewRect.height = locViewSize.height * scaleY; 773 return locViewRect; 774 }, 775 776 _createRenderCmd: function(){ 777 if (cc._renderType === cc._RENDER_TYPE_CANVAS) { 778 return new cc.ScrollView.CanvasRenderCmd(this); 779 } else { 780 return new cc.ScrollView.WebGLRenderCmd(this); 781 } 782 } 783 }); 784 785 var _p = cc.ScrollView.prototype; 786 787 // Extended properties 788 /** @expose */ 789 _p.minOffset; 790 cc.defineGetterSetter(_p, "minOffset", _p.minContainerOffset); 791 /** @expose */ 792 _p.maxOffset; 793 cc.defineGetterSetter(_p, "maxOffset", _p.maxContainerOffset); 794 /** @expose */ 795 _p.bounceable; 796 cc.defineGetterSetter(_p, "bounceable", _p.isBounceable, _p.setBounceable); 797 /** @expose */ 798 _p.viewSize; 799 cc.defineGetterSetter(_p, "viewSize", _p.getViewSize, _p.setViewSize); 800 /** @expose */ 801 _p.container; 802 cc.defineGetterSetter(_p, "container", _p.getContainer, _p.setContainer); 803 /** @expose */ 804 _p.direction; 805 cc.defineGetterSetter(_p, "direction", _p.getDirection, _p.setDirection); 806 /** @expose */ 807 _p.delegate; 808 cc.defineGetterSetter(_p, "delegate", _p.getDelegate, _p.setDelegate); 809 /** @expose */ 810 _p.clippingToBounds; 811 cc.defineGetterSetter(_p, "clippingToBounds", _p.isClippingToBounds, _p.setClippingToBounds); 812 813 _p = null; 814 815 /** 816 * Returns an autoreleased scroll view object. 817 * @deprecated 818 * @param {cc.Size} size view size 819 * @param {cc.Node} container parent object 820 * @return {cc.ScrollView} scroll view object 821 */ 822 cc.ScrollView.create = function (size, container) { 823 return new cc.ScrollView(size, container); 824 };