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 * ccui.Layout is the base class of ccui.PageView and ccui.ScrollView, it does layout by layout manager 28 * and clips area by its _clippingStencil when clippingEnabled is true. 29 * @class 30 * @extends ccui.Widget 31 * 32 * @property {Boolean} clippingEnabled - Indicate whether clipping is enabled 33 * @property {ccui.Layout.CLIPPING_STENCIL|ccui.Layout.CLIPPING_SCISSOR} clippingType 34 * @property {ccui.Layout.ABSOLUTE|ccui.Layout.LINEAR_VERTICAL|ccui.Layout.LINEAR_HORIZONTAL|ccui.Layout.RELATIVE} layoutType 35 * 36 */ 37 ccui.Layout = ccui.Widget.extend(/** @lends ccui.Layout# */{ 38 _clippingEnabled: false, 39 _backGroundScale9Enabled: null, 40 _backGroundImage: null, 41 _backGroundImageFileName: null, 42 _backGroundImageCapInsets: null, 43 _colorType: null, 44 _bgImageTexType: ccui.Widget.LOCAL_TEXTURE, 45 _colorRender: null, 46 _gradientRender: null, 47 _color: null, 48 _startColor: null, 49 _endColor: null, 50 _alongVector: null, 51 _opacity: 255, 52 _backGroundImageTextureSize: null, 53 _layoutType: null, 54 _doLayoutDirty: true, 55 _clippingRectDirty: true, 56 _clippingType: null, 57 _clippingStencil: null, 58 _handleScissor: false, 59 _scissorRectDirty: false, 60 _clippingRect: null, 61 _clippingParent: null, 62 _className: "Layout", 63 _backGroundImageColor: null, 64 _finalPositionX: 0, 65 _finalPositionY: 0, 66 67 //clipping 68 _currentStencilEnabled: 0, 69 _currentStencilWriteMask: 0, 70 _currentStencilFunc: 0, 71 _currentStencilRef:0, 72 _currentStencilValueMask:0, 73 _currentStencilFail:0, 74 _currentStencilPassDepthFail:0, 75 _currentStencilPassDepthPass:0, 76 _currentDepthWriteMask:0, 77 78 _currentAlphaTestEnabled:0, 79 _currentAlphaTestFunc:0, 80 _currentAlphaTestRef:0, 81 82 _backGroundImageOpacity:0, 83 84 _mask_layer_le: 0, 85 86 _loopFocus: false, //whether enable loop focus or not 87 __passFocusToChild: false, //on default, it will pass the focus to the next nearest widget 88 _isFocusPassing:false, //when finding the next focused widget, use this variable to pass focus between layout & widget 89 90 /** 91 * Allocates and initializes an UILayout. 92 * Constructor of ccui.Layout 93 * @function 94 * @example 95 * // example 96 * var uiLayout = new ccui.Layout(); 97 */ 98 ctor: function () { 99 this._layoutType = ccui.Layout.ABSOLUTE; 100 this._widgetType = ccui.Widget.TYPE_CONTAINER; 101 this._clippingType = ccui.Layout.CLIPPING_STENCIL; 102 this._colorType = ccui.Layout.BG_COLOR_NONE; 103 104 ccui.Widget.prototype.ctor.call(this); 105 this._backGroundImageCapInsets = cc.rect(0, 0, 0, 0); 106 107 this._color = cc.color(255, 255, 255, 255); 108 this._startColor = cc.color(255, 255, 255, 255); 109 this._endColor = cc.color(255, 255, 255, 255); 110 this._alongVector = cc.p(0, -1); 111 this._backGroundImageTextureSize = cc.size(0, 0); 112 113 this._clippingRect = cc.rect(0, 0, 0, 0); 114 this._backGroundImageColor = cc.color(255, 255, 255, 255); 115 }, 116 117 /** 118 * Calls its parent's onEnter, and calls its clippingStencil's onEnter if clippingStencil isn't null. 119 * @override 120 */ 121 onEnter: function(){ 122 ccui.Widget.prototype.onEnter.call(this); 123 if (this._clippingStencil) 124 this._clippingStencil.onEnter(); 125 this._doLayoutDirty = true; 126 this._clippingRectDirty = true; 127 }, 128 129 /** 130 * Calls its parent's onExit, and calls its clippingStencil's onExit if clippingStencil isn't null. 131 * @override 132 */ 133 onExit: function(){ 134 ccui.Widget.prototype.onExit.call(this); 135 if (this._clippingStencil) 136 this._clippingStencil.onExit(); 137 }, 138 139 /** 140 * If a layout is loop focused which means that the focus movement will be inside the layout 141 * @param {Boolean} loop pass true to let the focus movement loop inside the layout 142 */ 143 setLoopFocus: function(loop){ 144 this._loopFocus = loop; 145 }, 146 147 /** 148 * Gets whether enable focus loop 149 * @returns {boolean} If focus loop is enabled, then it will return true, otherwise it returns false. The default value is false. 150 */ 151 isLoopFocus: function(){ 152 return this._loopFocus; 153 }, 154 155 /** 156 * Specifies whether the layout pass its focus to its child 157 * @param pass To specify whether the layout pass its focus to its child 158 */ 159 setPassFocusToChild: function(pass){ 160 this.__passFocusToChild = pass; 161 }, 162 163 /** 164 * Returns whether the layout will pass the focus to its children or not. The default value is true 165 * @returns {boolean} To query whether the layout will pass the focus to its children or not. The default value is true 166 */ 167 isPassFocusToChild: function(){ 168 return this.__passFocusToChild; 169 }, 170 171 /** 172 * When a widget is in a layout, you could call this method to get the next focused widget within a specified direction. 173 * If the widget is not in a layout, it will return itself 174 * @param {Number} direction the direction to look for the next focused widget in a layout 175 * @param {ccui.Widget} current the current focused widget 176 * @returns {ccui.Widget} return the index of widget in the layout 177 */ 178 findNextFocusedWidget: function(direction, current){ 179 if (this._isFocusPassing || this.isFocused()) { 180 var parent = this.getParent(); 181 this._isFocusPassing = false; 182 183 if (this.__passFocusToChild) { 184 var w = this._passFocusToChild(direction, current); 185 if (w instanceof ccui.Layout && parent) { 186 parent._isFocusPassing = true; 187 return parent.findNextFocusedWidget(direction, this); 188 } 189 return w; 190 } 191 192 if (null == parent) 193 return this; 194 parent._isFocusPassing = true; 195 return parent.findNextFocusedWidget(direction, this); 196 } else if(current.isFocused() || current instanceof ccui.Layout) { 197 if (this._layoutType == ccui.Layout.LINEAR_HORIZONTAL) { 198 switch (direction){ 199 case ccui.Widget.LEFT: 200 return this._getPreviousFocusedWidget(direction, current); 201 break; 202 case ccui.Widget.RIGHT: 203 return this._getNextFocusedWidget(direction, current); 204 break; 205 case ccui.Widget.DOWN: 206 case ccui.Widget.UP: 207 if (this._isLastWidgetInContainer(this, direction)){ 208 if (this._isWidgetAncestorSupportLoopFocus(current, direction)) 209 return this.findNextFocusedWidget(direction, this); 210 return current; 211 } else { 212 return this.findNextFocusedWidget(direction, this); 213 } 214 break; 215 default: 216 cc.assert(0, "Invalid Focus Direction"); 217 return current; 218 } 219 } else if (this._layoutType == ccui.Layout.LINEAR_VERTICAL) { 220 switch (direction){ 221 case ccui.Widget.LEFT: 222 case ccui.Widget.RIGHT: 223 if (this._isLastWidgetInContainer(this, direction)) { 224 if (this._isWidgetAncestorSupportLoopFocus(current, direction)) 225 return this.findNextFocusedWidget(direction, this); 226 return current; 227 } 228 else 229 return this.findNextFocusedWidget(direction, this); 230 break; 231 case ccui.Widget.DOWN: 232 return this._getNextFocusedWidget(direction, current); 233 break; 234 case ccui.Widget.UP: 235 return this._getPreviousFocusedWidget(direction, current); 236 break; 237 default: 238 cc.assert(0, "Invalid Focus Direction"); 239 return current; 240 } 241 } else { 242 cc.assert(0, "Un Supported Layout type, please use VBox and HBox instead!!!"); 243 return current; 244 } 245 } else 246 return current; 247 }, 248 249 /** 250 * To specify a user-defined functor to decide which child widget of the layout should get focused 251 * @function 252 * @param {Number} direction 253 * @param {ccui.Widget} current 254 */ 255 onPassFocusToChild: null, 256 257 /** 258 * override "init" method of widget. please do not call this function by yourself, you should pass the parameters to constructor to initialize it. 259 * @returns {boolean} 260 * @override 261 */ 262 init: function () { 263 if (ccui.Widget.prototype.init.call(this)) { 264 this.ignoreContentAdaptWithSize(false); 265 this.setContentSize(cc.size(0, 0)); 266 this.setAnchorPoint(0, 0); 267 this.onPassFocusToChild = this._findNearestChildWidgetIndex.bind(this); 268 return true; 269 } 270 return false; 271 }, 272 273 __stencilDraw: function(ctx){ //Only for Canvas 274 var locContext = ctx || cc._renderContext; 275 var stencil = this._clippingStencil; 276 var locEGL_ScaleX = cc.view.getScaleX(), locEGL_ScaleY = cc.view.getScaleY(); 277 for (var i = 0; i < stencil._buffer.length; i++) { 278 var element = stencil._buffer[i]; 279 var vertices = element.verts; 280 var firstPoint = vertices[0]; 281 locContext.beginPath(); 282 locContext.moveTo(firstPoint.x * locEGL_ScaleX, -firstPoint.y * locEGL_ScaleY); 283 for (var j = 1, len = vertices.length; j < len; j++) 284 locContext.lineTo(vertices[j].x * locEGL_ScaleX, -vertices[j].y * locEGL_ScaleY); 285 } 286 }, 287 288 /** 289 * Adds a widget to the container. 290 * @param {ccui.Widget} widget 291 * @param {Number} [zOrder] 292 * @param {Number|string} [tag] tag or name 293 * @override 294 */ 295 addChild: function (widget, zOrder, tag) { 296 if ((widget instanceof ccui.Widget)) { 297 this._supplyTheLayoutParameterLackToChild(widget); 298 } 299 ccui.Widget.prototype.addChild.call(this, widget, zOrder, tag); 300 this._doLayoutDirty = true; 301 }, 302 303 /** 304 * Removes child widget from ccui.Layout, and sets the layout dirty flag to true. 305 * @param {ccui.Widget} widget 306 * @param {Boolean} [cleanup=true] 307 * @override 308 */ 309 removeChild: function (widget, cleanup) { 310 ccui.Widget.prototype.removeChild.call(this, widget, cleanup); 311 this._doLayoutDirty = true; 312 }, 313 314 /** 315 * Removes all children from the container with a cleanup, and sets the layout dirty flag to true. 316 * @param {Boolean} cleanup 317 */ 318 removeAllChildren: function (cleanup) { 319 ccui.Widget.prototype.removeAllChildren.call(this, cleanup); 320 this._doLayoutDirty = true; 321 }, 322 323 /** 324 * Removes all children from the container, do a cleanup to all running actions depending on the cleanup parameter, 325 * and sets the layout dirty flag to true. 326 * @param {Boolean} cleanup true if all running actions on all children nodes should be cleanup, false otherwise. 327 */ 328 removeAllChildrenWithCleanup: function(cleanup){ 329 ccui.Widget.prototype.removeAllChildrenWithCleanup.call(this, cleanup); 330 this._doLayoutDirty = true; 331 }, 332 333 /** 334 * Gets if layout is clipping enabled. 335 * @returns {Boolean} if layout is clipping enabled. 336 */ 337 isClippingEnabled: function () { 338 return this._clippingEnabled; 339 }, 340 341 /** 342 * <p> 343 * Calls adaptRenderers (its subclass will override it.) and do layout. 344 * If clippingEnabled is true, it will clip/scissor area. 345 * </p> 346 * @override 347 * @param {CanvasRenderingContext2D|WebGLRenderingContext} ctx 348 */ 349 visit: function (ctx) { 350 if (!this._visible) 351 return; 352 this._adaptRenderers(); 353 this._doLayout(); 354 355 if (this._clippingEnabled) { 356 switch (this._clippingType) { 357 case ccui.Layout.CLIPPING_STENCIL: 358 this._stencilClippingVisit(ctx); 359 break; 360 case ccui.Layout.CLIPPING_SCISSOR: 361 this._scissorClippingVisit(ctx); 362 break; 363 default: 364 break; 365 } 366 } else 367 ccui.Widget.prototype.visit.call(this, ctx); 368 }, 369 370 _stencilClippingVisit: null, 371 372 _stencilClippingVisitForWebGL: function (ctx) { 373 var gl = ctx || cc._renderContext; 374 375 if (!this._clippingStencil || !this._clippingStencil.isVisible()) 376 return; 377 378 // all the _stencilBits are in use? 379 if (ccui.Layout._layer + 1 == cc.stencilBits) { 380 // warn once 381 ccui.Layout._visit_once = true; 382 if (ccui.Layout._visit_once) { 383 cc.log("Nesting more than " + cc.stencilBits + "stencils is not supported. Everything will be drawn without stencil for this node and its childs."); 384 ccui.Layout._visit_once = false; 385 } 386 // draw everything, as if there where no stencil 387 cc.Node.prototype.visit.call(this, ctx); 388 return; 389 } 390 391 ccui.Layout._layer++; 392 393 var mask_layer = 0x1 << ccui.Layout._layer; 394 var mask_layer_l = mask_layer - 1; 395 var mask_layer_le = mask_layer | mask_layer_l; 396 397 // manually save the stencil state 398 var currentStencilEnabled = gl.isEnabled(gl.STENCIL_TEST); 399 var currentStencilWriteMask = gl.getParameter(gl.STENCIL_WRITEMASK); 400 var currentStencilFunc = gl.getParameter(gl.STENCIL_FUNC); 401 var currentStencilRef = gl.getParameter(gl.STENCIL_REF); 402 var currentStencilValueMask = gl.getParameter(gl.STENCIL_VALUE_MASK); 403 var currentStencilFail = gl.getParameter(gl.STENCIL_FAIL); 404 var currentStencilPassDepthFail = gl.getParameter(gl.STENCIL_PASS_DEPTH_FAIL); 405 var currentStencilPassDepthPass = gl.getParameter(gl.STENCIL_PASS_DEPTH_PASS); 406 407 gl.enable(gl.STENCIL_TEST); 408 409 gl.stencilMask(mask_layer); 410 411 var currentDepthWriteMask = gl.getParameter(gl.DEPTH_WRITEMASK); 412 413 gl.depthMask(false); 414 415 gl.stencilFunc(gl.NEVER, mask_layer, mask_layer); 416 gl.stencilOp(gl.ZERO, gl.KEEP, gl.KEEP); 417 418 // draw a fullscreen solid rectangle to clear the stencil buffer 419 cc.kmGLMatrixMode(cc.KM_GL_PROJECTION); 420 cc.kmGLPushMatrix(); 421 cc.kmGLLoadIdentity(); 422 cc.kmGLMatrixMode(cc.KM_GL_MODELVIEW); 423 cc.kmGLPushMatrix(); 424 cc.kmGLLoadIdentity(); 425 cc._drawingUtil.drawSolidRect(cc.p(-1,-1), cc.p(1,1), cc.color(255, 255, 255, 255)); 426 cc.kmGLMatrixMode(cc.KM_GL_PROJECTION); 427 cc.kmGLPopMatrix(); 428 cc.kmGLMatrixMode(cc.KM_GL_MODELVIEW); 429 cc.kmGLPopMatrix(); 430 431 gl.stencilFunc(gl.NEVER, mask_layer, mask_layer); 432 gl.stencilOp(gl.REPLACE, gl.KEEP, gl.KEEP); 433 434 cc.kmGLPushMatrix(); 435 this.transform(); 436 this._clippingStencil.visit(); 437 438 gl.depthMask(currentDepthWriteMask); 439 gl.stencilFunc(gl.EQUAL, mask_layer_le, mask_layer_le); 440 gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); 441 442 // draw (according to the stencil test func) this node and its childs 443 var i = 0; // used by _children 444 var j = 0; // used by _protectedChildren 445 446 this.sortAllChildren(); 447 this.sortAllProtectedChildren(); 448 var locChildren = this._children, locProtectChildren = this._protectedChildren; 449 var iLen = locChildren.length, jLen = locProtectChildren.length, child; 450 for( ; i < iLen; i++ ){ 451 child = locChildren[i]; 452 if ( child && child.getLocalZOrder() < 0 ) 453 child.visit(); 454 else 455 break; 456 } 457 for( ; j < jLen; j++ ) { 458 child = locProtectChildren[j]; 459 if ( child && child.getLocalZOrder() < 0 ) 460 child.visit(); 461 else 462 break; 463 } 464 this.draw(); 465 for (; i < iLen; i++) 466 locChildren[i].visit(); 467 for (; j < jLen; j++) 468 locProtectChildren[j].visit(); 469 470 // manually restore the stencil state 471 gl.stencilFunc(currentStencilFunc, currentStencilRef, currentStencilValueMask); 472 gl.stencilOp(currentStencilFail, currentStencilPassDepthFail, currentStencilPassDepthPass); 473 gl.stencilMask(currentStencilWriteMask); 474 if (!currentStencilEnabled) 475 gl.disable(gl.STENCIL_TEST); 476 ccui.Layout._layer--; 477 478 cc.kmGLPopMatrix(); 479 }, 480 481 _stencilClippingVisitForCanvas: function (ctx) { 482 // return fast (draw nothing, or draw everything if in inverted mode) if: 483 // - nil stencil node 484 // - or stencil node invisible: 485 if (!this._clippingStencil || !this._clippingStencil.isVisible()) { 486 return; 487 } 488 var context = ctx || cc._renderContext; 489 // Composition mode, costy but support texture stencil 490 if (this._clippingStencil instanceof cc.Sprite) { 491 // Cache the current canvas, for later use (This is a little bit heavy, replace this solution with other walkthrough) 492 var canvas = context.canvas; 493 var locCache = ccui.Layout._getSharedCache(); 494 locCache.width = canvas.width; 495 locCache.height = canvas.height; 496 var locCacheCtx = locCache.getContext("2d"); 497 locCacheCtx.drawImage(canvas, 0, 0); 498 499 context.save(); 500 // Draw everything first using node visit function 501 cc.ProtectedNode.prototype.visit.call(this, context); 502 503 context.globalCompositeOperation = "destination-in"; 504 505 this.transform(context); 506 this._clippingStencil.visit(); 507 508 context.restore(); 509 510 // Redraw the cached canvas, so that the cliped area shows the background etc. 511 context.save(); 512 context.setTransform(1, 0, 0, 1, 0, 0); 513 context.globalCompositeOperation = "destination-over"; 514 context.drawImage(locCache, 0, 0); 515 context.restore(); 516 } else { // Clip mode, fast, but only support cc.DrawNode 517 var i, children = this._children, locChild; 518 519 context.save(); 520 this.transform(context); 521 this._clippingStencil.visit(context); 522 context.clip(); 523 524 // Clip mode doesn't support recusive stencil, so once we used a clip stencil, 525 // so if it has ClippingNode as a child, the child must uses composition stencil. 526 this.sortAllChildren(); 527 this.sortAllProtectedChildren(); 528 529 var j, locProtectChildren = this._protectedChildren; 530 var iLen = children.length, jLen = locProtectChildren.length; 531 532 // draw children zOrder < 0 533 for (i = 0; i < iLen; i++) { 534 locChild = children[i]; 535 if (locChild && locChild._localZOrder < 0) 536 locChild.visit(context); 537 else 538 break; 539 } 540 for (j = 0; j < jLen; j++) { 541 locChild = locProtectChildren[j]; 542 if (locChild && locChild._localZOrder < 0) 543 locChild.visit(context); 544 else 545 break; 546 } 547 //this.draw(context); 548 for (; i < iLen; i++) 549 children[i].visit(context); 550 for (; j < jLen; j++) 551 locProtectChildren[j].visit(context); 552 553 context.restore(); 554 } 555 }, 556 557 _scissorClippingVisit: null, 558 _scissorClippingVisitForWebGL: function (ctx) { 559 var clippingRect = this._getClippingRect(); 560 var gl = ctx || cc._renderContext; 561 if (this._handleScissor) { 562 gl.enable(gl.SCISSOR_TEST); 563 } 564 cc.view.setScissorInPoints(clippingRect.x, clippingRect.y, clippingRect.width, clippingRect.height); 565 cc.Node.prototype.visit.call(this); 566 if (this._handleScissor) { 567 gl.disable(gl.SCISSOR_TEST); 568 } 569 }, 570 571 /** 572 * Changes if layout can clip it's content and locChild. 573 * If you really need this, please enable it. But it would reduce the rendering efficiency. 574 * @param {Boolean} able clipping enabled. 575 */ 576 setClippingEnabled: function (able) { 577 if (able == this._clippingEnabled) 578 return; 579 this._clippingEnabled = able; 580 switch (this._clippingType) { 581 case ccui.Layout.CLIPPING_STENCIL: 582 if (able){ 583 this._clippingStencil = cc.DrawNode.create(); 584 if(cc._renderType === cc._RENDER_TYPE_CANVAS) 585 this._clippingStencil.draw = this.__stencilDraw.bind(this); 586 if (this._running) 587 this._clippingStencil.onEnter(); 588 this._setStencilClippingSize(this._contentSize); 589 } else { 590 if (this._running && this._clippingStencil) 591 this._clippingStencil.onExit(); 592 this._clippingStencil = null; 593 } 594 break; 595 default: 596 break; 597 } 598 }, 599 600 /** 601 * Sets clipping type to ccui.Layout 602 * @param {ccui.Layout.CLIPPING_STENCIL|ccui.Layout.CLIPPING_SCISSOR} type 603 */ 604 setClippingType: function (type) { 605 if (type == this._clippingType) 606 return; 607 var clippingEnabled = this.isClippingEnabled(); 608 this.setClippingEnabled(false); 609 this._clippingType = type; 610 this.setClippingEnabled(clippingEnabled); 611 }, 612 613 /** 614 * Gets clipping type of ccui.Layout 615 * @returns {ccui.Layout.CLIPPING_STENCIL|ccui.Layout.CLIPPING_SCISSOR} 616 */ 617 getClippingType: function () { 618 return this._clippingType; 619 }, 620 621 _setStencilClippingSize: function (size) { 622 if (this._clippingEnabled && this._clippingType == ccui.Layout.CLIPPING_STENCIL) { 623 var rect = []; 624 rect[0] = cc.p(0, 0); 625 rect[1] = cc.p(size.width, 0); 626 rect[2] = cc.p(size.width, size.height); 627 rect[3] = cc.p(0, size.height); 628 var green = cc.color.GREEN; 629 this._clippingStencil.clear(); 630 this._clippingStencil.drawPoly(rect, 4, green, 0, green); 631 } 632 }, 633 634 _getClippingRect: function () { 635 if (this._clippingRectDirty) { 636 var worldPos = this.convertToWorldSpace(cc.p(0, 0)); 637 var t = this.nodeToWorldTransform(); 638 var scissorWidth = this._contentSize.width * t.a; 639 var scissorHeight = this._contentSize.height * t.d; 640 var parentClippingRect; 641 var parent = this; 642 643 while (parent) { 644 parent = parent.getParent(); 645 if (parent && parent instanceof ccui.Layout && parent.isClippingEnabled()) { 646 this._clippingParent = parent; 647 break; 648 } 649 } 650 651 if (this._clippingParent) { 652 parentClippingRect = this._clippingParent._getClippingRect(); 653 var finalX = worldPos.x - (scissorWidth * this._anchorPoint.x); 654 var finalY = worldPos.y - (scissorHeight * this._anchorPoint.y); 655 var finalWidth = scissorWidth; 656 var finalHeight = scissorHeight; 657 658 var leftOffset = worldPos.x - parentClippingRect.x; 659 if (leftOffset < 0) { 660 finalX = parentClippingRect.x; 661 finalWidth += leftOffset; 662 } 663 var rightOffset = (worldPos.x + scissorWidth) - (parentClippingRect.x + parentClippingRect.width); 664 if (rightOffset > 0) 665 finalWidth -= rightOffset; 666 var topOffset = (worldPos.y + scissorHeight) - (parentClippingRect.y + parentClippingRect.height); 667 if (topOffset > 0) 668 finalHeight -= topOffset; 669 var bottomOffset = worldPos.y - parentClippingRect.y; 670 if (bottomOffset < 0) { 671 finalY = parentClippingRect.x; 672 finalHeight += bottomOffset; 673 } 674 if (finalWidth < 0) 675 finalWidth = 0; 676 if (finalHeight < 0) 677 finalHeight = 0; 678 this._clippingRect.x = finalX; 679 this._clippingRect.y = finalY; 680 this._clippingRect.width = finalWidth; 681 this._clippingRect.height = finalHeight; 682 } else { 683 this._clippingRect.x = worldPos.x - (scissorWidth * this._anchorPoint.x); 684 this._clippingRect.y = worldPos.y - (scissorHeight * this._anchorPoint.y); 685 this._clippingRect.width = scissorWidth; 686 this._clippingRect.height = scissorHeight; 687 } 688 this._clippingRectDirty = false; 689 } 690 return this._clippingRect; 691 }, 692 693 _onSizeChanged: function () { 694 ccui.Widget.prototype._onSizeChanged.call(this); 695 var locContentSize = this._contentSize; 696 this._setStencilClippingSize(locContentSize); 697 this._doLayoutDirty = true; 698 this._clippingRectDirty = true; 699 if (this._backGroundImage) { 700 this._backGroundImage.setPosition(locContentSize.width * 0.5, locContentSize.height * 0.5); 701 if (this._backGroundScale9Enabled && this._backGroundImage instanceof ccui.Scale9Sprite) 702 this._backGroundImage.setPreferredSize(locContentSize); 703 } 704 if (this._colorRender) 705 this._colorRender.setContentSize(locContentSize); 706 if (this._gradientRender) 707 this._gradientRender.setContentSize(locContentSize); 708 }, 709 710 /** 711 * Sets background image use scale9 renderer. 712 * @param {Boolean} able true that use scale9 renderer, false otherwise. 713 */ 714 setBackGroundImageScale9Enabled: function (able) { 715 if (this._backGroundScale9Enabled == able) 716 return; 717 this.removeProtectedChild(this._backGroundImage); 718 this._backGroundImage = null; 719 this._backGroundScale9Enabled = able; 720 this._addBackGroundImage(); 721 this.setBackGroundImage(this._backGroundImageFileName, this._bgImageTexType); 722 this.setBackGroundImageCapInsets(this._backGroundImageCapInsets); 723 }, 724 725 /** 726 * Get whether background image is use scale9 renderer. 727 * @returns {Boolean} 728 */ 729 isBackGroundImageScale9Enabled: function () { 730 return this._backGroundScale9Enabled; 731 }, 732 733 /** 734 * Sets a background image for layout 735 * @param {String} fileName 736 * @param {ccui.Widget.LOCAL_TEXTURE|ccui.Widget.PLIST_TEXTURE} texType 737 */ 738 setBackGroundImage: function (fileName, texType) { 739 if (!fileName) 740 return; 741 texType = texType || ccui.Widget.LOCAL_TEXTURE; 742 if (this._backGroundImage == null) 743 this._addBackGroundImage(); 744 this._backGroundImageFileName = fileName; 745 this._bgImageTexType = texType; 746 var locBackgroundImage = this._backGroundImage; 747 if (this._backGroundScale9Enabled) { 748 var bgiScale9 = locBackgroundImage; 749 switch (this._bgImageTexType) { 750 case ccui.Widget.LOCAL_TEXTURE: 751 bgiScale9.initWithFile(fileName); 752 break; 753 case ccui.Widget.PLIST_TEXTURE: 754 bgiScale9.initWithSpriteFrameName(fileName); 755 break; 756 default: 757 break; 758 } 759 bgiScale9.setPreferredSize(this._contentSize); 760 } else { 761 var sprite = locBackgroundImage; 762 switch (this._bgImageTexType){ 763 case ccui.Widget.LOCAL_TEXTURE: 764 //SetTexture cannot load resource 765 sprite.initWithFile(fileName); 766 break; 767 case ccui.Widget.PLIST_TEXTURE: 768 //SetTexture cannot load resource 769 sprite.initWithSpriteFrameName(fileName); 770 break; 771 default: 772 break; 773 } 774 } 775 this._backGroundImageTextureSize = locBackgroundImage.getContentSize(); 776 locBackgroundImage.setPosition(this._contentSize.width * 0.5, this._contentSize.height * 0.5); 777 this._updateBackGroundImageColor(); 778 779 /*//async load callback 780 var self = this; 781 if(!locBackgroundImage.texture || !locBackgroundImage.texture.isLoaded()){ 782 locBackgroundImage.addLoadedEventListener(function(){ 783 self._backGroundImageTextureSize = locBackgroundImage.getContentSize(); 784 locBackgroundImage.setPosition(self._contentSize.width * 0.5, self._contentSize.height * 0.5); 785 self._updateBackGroundImageColor(); 786 787 self._imageRendererAdaptDirty = true; 788 self._findLayout(); 789 }); 790 }*/ 791 }, 792 793 /** 794 * Sets a background image CapInsets for layout, if the background image is a scale9 render. 795 * @param {cc.Rect} capInsets capinsets of background image. 796 */ 797 setBackGroundImageCapInsets: function (capInsets) { 798 if(!capInsets) 799 return; 800 var locInsets = this._backGroundImageCapInsets; 801 locInsets.x = capInsets.x; 802 locInsets.y = capInsets.y; 803 locInsets.width = capInsets.width; 804 locInsets.height = capInsets.height; 805 if (this._backGroundScale9Enabled) 806 this._backGroundImage.setCapInsets(capInsets); 807 }, 808 809 /** 810 * Gets background image capinsets of ccui.Layout. 811 * @returns {cc.Rect} 812 */ 813 getBackGroundImageCapInsets: function () { 814 return cc.rect(this._backGroundImageCapInsets); 815 }, 816 817 _supplyTheLayoutParameterLackToChild: function (locChild) { 818 if (!locChild) { 819 return; 820 } 821 switch (this._layoutType) { 822 case ccui.Layout.ABSOLUTE: 823 break; 824 case ccui.Layout.LINEAR_HORIZONTAL: 825 case ccui.Layout.LINEAR_VERTICAL: 826 var layoutParameter = locChild.getLayoutParameter(ccui.LayoutParameter.LINEAR); 827 if (!layoutParameter) 828 locChild.setLayoutParameter(ccui.LinearLayoutParameter.create()); 829 break; 830 case ccui.Layout.RELATIVE: 831 var layoutParameter = locChild.getLayoutParameter(ccui.LayoutParameter.RELATIVE); 832 if (!layoutParameter) 833 locChild.setLayoutParameter(ccui.RelativeLayoutParameter.create()); 834 break; 835 default: 836 break; 837 } 838 }, 839 840 _addBackGroundImage: function () { 841 if (this._backGroundScale9Enabled) { 842 this._backGroundImage = ccui.Scale9Sprite.create(); 843 this._backGroundImage.setPreferredSize(this._contentSize); 844 } else 845 this._backGroundImage = cc.Sprite.create(); 846 this.addProtectedChild(this._backGroundImage, ccui.Layout.BACKGROUND_IMAGE_ZORDER, -1); 847 this._backGroundImage.setPosition(this._contentSize.width / 2.0, this._contentSize.height / 2.0); 848 }, 849 850 /** 851 * Remove the background image of ccui.Layout. 852 */ 853 removeBackGroundImage: function () { 854 if (!this._backGroundImage) 855 return; 856 this.removeProtectedChild(this._backGroundImage); 857 this._backGroundImage = null; 858 this._backGroundImageFileName = ""; 859 this._backGroundImageTextureSize.width = 0; 860 this._backGroundImageTextureSize.height = 0; 861 }, 862 863 /** 864 * Sets Color Type for ccui.Layout. 865 * @param {ccui.Layout.BG_COLOR_NONE|ccui.Layout.BG_COLOR_SOLID|ccui.Layout.BG_COLOR_GRADIENT} type 866 */ 867 setBackGroundColorType: function (type) { 868 if (this._colorType == type) 869 return; 870 switch (this._colorType) { 871 case ccui.Layout.BG_COLOR_NONE: 872 if (this._colorRender) { 873 this.removeProtectedChild(this._colorRender); 874 this._colorRender = null; 875 } 876 if (this._gradientRender) { 877 this.removeProtectedChild(this._gradientRender); 878 this._gradientRender = null; 879 } 880 break; 881 case ccui.Layout.BG_COLOR_SOLID: 882 if (this._colorRender) { 883 this.removeProtectedChild(this._colorRender); 884 this._colorRender = null; 885 } 886 break; 887 case ccui.Layout.BG_COLOR_GRADIENT: 888 if (this._gradientRender) { 889 this.removeProtectedChild(this._gradientRender); 890 this._gradientRender = null; 891 } 892 break; 893 default: 894 break; 895 } 896 this._colorType = type; 897 switch (this._colorType) { 898 case ccui.Layout.BG_COLOR_NONE: 899 break; 900 case ccui.Layout.BG_COLOR_SOLID: 901 this._colorRender = new cc.LayerColor(); 902 this._colorRender.setContentSize(this._contentSize); 903 this._colorRender.setOpacity(this._opacity); 904 this._colorRender.setColor(this._color); 905 this.addProtectedChild(this._colorRender, ccui.Layout.BACKGROUND_RENDERER_ZORDER, -1); 906 break; 907 case ccui.Layout.BG_COLOR_GRADIENT: 908 this._gradientRender = new cc.LayerGradient(cc.color(255, 0, 0, 255), cc.color(0, 255, 0, 255)); 909 this._gradientRender.setContentSize(this._contentSize); 910 this._gradientRender.setOpacity(this._opacity); 911 this._gradientRender.setStartColor(this._startColor); 912 this._gradientRender.setEndColor(this._endColor); 913 this._gradientRender.setVector(this._alongVector); 914 this.addProtectedChild(this._gradientRender, ccui.Layout.BACKGROUND_RENDERER_ZORDER, -1); 915 break; 916 default: 917 break; 918 } 919 }, 920 921 /** 922 * Get background color type of ccui.Layout. 923 * @returns {ccui.Layout.BG_COLOR_NONE|ccui.Layout.BG_COLOR_SOLID|ccui.Layout.BG_COLOR_GRADIENT} 924 */ 925 getBackGroundColorType: function () { 926 return this._colorType; 927 }, 928 929 /** 930 * Sets background color for layout, if color type is Layout.COLOR_SOLID 931 * @param {cc.Color} color 932 * @param {cc.Color} [endColor] 933 */ 934 setBackGroundColor: function (color, endColor) { 935 if (!endColor) { 936 this._color.r = color.r; 937 this._color.g = color.g; 938 this._color.b = color.b; 939 if (this._colorRender) 940 this._colorRender.setColor(color); 941 } else { 942 this._startColor.r = color.r; 943 this._startColor.g = color.g; 944 this._startColor.b = color.b; 945 if (this._gradientRender) 946 this._gradientRender.setStartColor(color); 947 948 this._endColor.r = endColor.r; 949 this._endColor.g = endColor.g; 950 this._endColor.b = endColor.b; 951 if (this._gradientRender) 952 this._gradientRender.setEndColor(endColor); 953 } 954 }, 955 956 /** 957 * Gets background color of ccui.Layout, if color type is Layout.COLOR_SOLID. 958 * @returns {cc.Color} 959 */ 960 getBackGroundColor: function () { 961 var tmpColor = this._color; 962 return cc.color(tmpColor.r, tmpColor.g, tmpColor.b, tmpColor.a); 963 }, 964 965 /** 966 * Gets background start color of ccui.Layout 967 * @returns {cc.Color} 968 */ 969 getBackGroundStartColor: function () { 970 var tmpColor = this._startColor; 971 return cc.color(tmpColor.r, tmpColor.g, tmpColor.b, tmpColor.a); 972 }, 973 974 /** 975 * Gets background end color of ccui.Layout 976 * @returns {cc.Color} 977 */ 978 getBackGroundEndColor: function () { 979 var tmpColor = this._endColor; 980 return cc.color(tmpColor.r, tmpColor.g, tmpColor.b, tmpColor.a); 981 }, 982 983 /** 984 * Sets background opacity to ccui.Layout. 985 * @param {number} opacity 986 */ 987 setBackGroundColorOpacity: function (opacity) { 988 this._opacity = opacity; 989 switch (this._colorType) { 990 case ccui.Layout.BG_COLOR_NONE: 991 break; 992 case ccui.Layout.BG_COLOR_SOLID: 993 this._colorRender.setOpacity(opacity); 994 break; 995 case ccui.Layout.BG_COLOR_GRADIENT: 996 this._gradientRender.setOpacity(opacity); 997 break; 998 default: 999 break; 1000 } 1001 }, 1002 1003 /** 1004 * Get background opacity value of ccui.Layout. 1005 * @returns {Number} 1006 */ 1007 getBackGroundColorOpacity: function () { 1008 return this._opacity; 1009 }, 1010 1011 /** 1012 * Sets background color vector for layout, if color type is Layout.COLOR_GRADIENT 1013 * @param {cc.Point} vector 1014 */ 1015 setBackGroundColorVector: function (vector) { 1016 this._alongVector.x = vector.x; 1017 this._alongVector.y = vector.y; 1018 if (this._gradientRender) { 1019 this._gradientRender.setVector(vector); 1020 } 1021 }, 1022 1023 /** 1024 * Gets background color vector of ccui.Layout, if color type is Layout.COLOR_GRADIENT 1025 * @returns {cc.Point} 1026 */ 1027 getBackGroundColorVector: function () { 1028 return this._alongVector; 1029 }, 1030 1031 /** 1032 * Sets backGround image color 1033 * @param {cc.Color} color 1034 */ 1035 setBackGroundImageColor: function (color) { 1036 this._backGroundImageColor.r = color.r; 1037 this._backGroundImageColor.g = color.g; 1038 this._backGroundImageColor.b = color.b; 1039 1040 this._updateBackGroundImageColor(); 1041 }, 1042 1043 /** 1044 * Sets backGround image Opacity 1045 * @param {Number} opacity 1046 */ 1047 setBackGroundImageOpacity: function (opacity) { 1048 this._backGroundImageColor.a = opacity; 1049 this.getBackGroundImageColor(); 1050 }, 1051 1052 /** 1053 * Gets backGround image color 1054 * @returns {cc.Color} 1055 */ 1056 getBackGroundImageColor: function () { 1057 var color = this._backGroundImageColor; 1058 return cc.color(color.r, color.g, color.b, color.a); 1059 }, 1060 1061 /** 1062 * Gets backGround image opacity 1063 * @returns {Number} 1064 */ 1065 getBackGroundImageOpacity: function () { 1066 return this._backGroundImageColor.a; 1067 }, 1068 1069 _updateBackGroundImageColor: function () { 1070 if(this._backGroundImage) 1071 this._backGroundImage.setColor(this._backGroundImageColor); 1072 }, 1073 1074 /** 1075 * Gets background image texture size. 1076 * @returns {cc.Size} 1077 */ 1078 getBackGroundImageTextureSize: function () { 1079 return this._backGroundImageTextureSize; 1080 }, 1081 1082 /** 1083 * Sets LayoutType to ccui.Layout, LayoutManager will do layout by layout type.. 1084 * @param {ccui.Layout.ABSOLUTE|ccui.Layout.LINEAR_VERTICAL|ccui.Layout.LINEAR_HORIZONTAL|ccui.Layout.RELATIVE} type 1085 */ 1086 setLayoutType: function (type) { 1087 this._layoutType = type; 1088 var layoutChildrenArray = this._children; 1089 var locChild = null; 1090 for (var i = 0; i < layoutChildrenArray.length; i++) { 1091 locChild = layoutChildrenArray[i]; 1092 if(locChild instanceof ccui.Widget) 1093 this._supplyTheLayoutParameterLackToChild(locChild); 1094 } 1095 this._doLayoutDirty = true; 1096 }, 1097 1098 /** 1099 * Gets LayoutType of ccui.Layout. 1100 * @returns {null} 1101 */ 1102 getLayoutType: function () { 1103 return this._layoutType; 1104 }, 1105 1106 /** 1107 * request do layout, it will do layout at visit calls 1108 */ 1109 requestDoLayout: function () { 1110 this._doLayoutDirty = true; 1111 }, 1112 1113 _doLayout: function () { 1114 if (!this._doLayoutDirty) 1115 return; 1116 1117 this.sortAllChildren(); 1118 1119 var executant = ccui.getLayoutManager(this._layoutType); 1120 if (executant) 1121 executant._doLayout(this); 1122 this._doLayoutDirty = false; 1123 }, 1124 1125 _getLayoutContentSize: function(){ 1126 return this.getContentSize(); 1127 }, 1128 1129 _getLayoutElements: function(){ 1130 return this.getChildren(); 1131 }, 1132 1133 //clipping 1134 _onBeforeVisitStencil: function(){ 1135 //TODO NEW RENDERER 1136 }, 1137 1138 _drawFullScreenQuadClearStencil:function(){ 1139 //TODO NEW RENDERER 1140 }, 1141 1142 _onAfterDrawStencil: function(){ 1143 //TODO NEW RENDERER 1144 }, 1145 1146 _onAfterVisitStencil: function(){ 1147 //TODO NEW RENDERER 1148 }, 1149 1150 _onAfterVisitScissor: function(){ 1151 //TODO NEW RENDERER 1152 }, 1153 1154 _onAfterVisitScissor: function(){ 1155 //TODO NEW RENDERER 1156 }, 1157 1158 _updateBackGroundImageOpacity: function(){ 1159 if (this._backGroundImage) 1160 this._backGroundImage.setOpacity(this._backGroundImageOpacity); 1161 }, 1162 1163 _updateBackGroundImageRGBA: function(){ 1164 if (this._backGroundImage) { 1165 this._backGroundImage.setColor(this._backGroundImageColor); 1166 this._backGroundImage.setOpacity(this._backGroundImageOpacity); 1167 } 1168 }, 1169 1170 /** 1171 * Gets the content size of the layout, it will accumulate all its children's content size 1172 * @returns {cc.Size} 1173 * @private 1174 */ 1175 _getLayoutAccumulatedSize: function(){ 1176 var children = this.getChildren(); 1177 var layoutSize = cc.size(0, 0); 1178 var widgetCount = 0, locSize; 1179 for(var i = 0, len = children.length; i < len; i++) { 1180 var layout = children[i]; 1181 if (null != layout && layout instanceof ccui.Layout){ 1182 locSize = layout._getLayoutAccumulatedSize(); 1183 layoutSize.width += locSize.width; 1184 layoutSize.height += locSize.height; 1185 } else { 1186 if (layout instanceof ccui.Widget) { 1187 widgetCount++; 1188 var m = layout.getLayoutParameter().getMargin(); 1189 locSize = layout.getContentSize(); 1190 layoutSize.width += locSize.width + (m.right + m.left) * 0.5; 1191 layoutSize.height += locSize.height + (m.top + m.bottom) * 0.5; 1192 } 1193 } 1194 } 1195 1196 //substract extra size 1197 var type = this.getLayoutType(); 1198 if (type == ccui.Layout.LINEAR_HORIZONTAL) 1199 layoutSize.height = layoutSize.height - layoutSize.height/widgetCount * (widgetCount-1); 1200 1201 if (type == ccui.Layout.LINEAR_VERTICAL) 1202 layoutSize.width = layoutSize.width - layoutSize.width/widgetCount * (widgetCount-1); 1203 return layoutSize; 1204 }, 1205 1206 /** 1207 * When the layout get focused, it the layout pass the focus to its child, it will use this method to determine which child <br/> 1208 * will get the focus. The current algorithm to determine which child will get focus is nearest-distance-priority algorithm 1209 * @param {Number} direction next focused widget direction 1210 * @param {ccui.Widget} baseWidget 1211 * @returns {Number} 1212 * @private 1213 */ 1214 _findNearestChildWidgetIndex: function(direction, baseWidget){ 1215 if (baseWidget == null || baseWidget == this) 1216 return this._findFirstFocusEnabledWidgetIndex(); 1217 1218 var index = 0, locChildren = this.getChildren(); 1219 var count = locChildren.length, widgetPosition; 1220 1221 var distance = cc.FLT_MAX, found = 0; 1222 if (direction == ccui.Widget.LEFT || direction == ccui.Widget.RIGHT || direction == ccui.Widget.DOWN || direction == ccui.Widget.UP) { 1223 widgetPosition = this._getWorldCenterPoint(baseWidget); 1224 while (index < count) { 1225 var w = locChildren[index]; 1226 if (w && w instanceof ccui.Widget && w.isFocusEnabled()) { 1227 var length = (w instanceof ccui.Layout)? w._calculateNearestDistance(baseWidget) 1228 : cc.pLength(cc.pSub(this._getWorldCenterPoint(w), widgetPosition)); 1229 if (length < distance){ 1230 found = index; 1231 distance = length; 1232 } 1233 } 1234 index++; 1235 } 1236 return found; 1237 } 1238 cc.log("invalid focus direction!"); 1239 return 0; 1240 }, 1241 1242 /** 1243 * When the layout get focused, it the layout pass the focus to its child, it will use this method to determine which child 1244 * will get the focus. The current algorithm to determine which child will get focus is farthest-distance-priority algorithm 1245 * @param {Number} direction next focused widget direction 1246 * @param {ccui.Widget} baseWidget 1247 * @returns {Number} The index of child widget in the container 1248 * @private 1249 */ 1250 _findFarthestChildWidgetIndex: function(direction, baseWidget){ 1251 if (baseWidget == null || baseWidget == this) 1252 return this._findFirstFocusEnabledWidgetIndex(); 1253 1254 var index = 0, locChildren = this.getChildren(); 1255 var count = locChildren.length; 1256 1257 var distance = -cc.FLT_MAX, found = 0; 1258 if (direction == ccui.Widget.LEFT || direction == ccui.Widget.RIGHT || direction == ccui.Widget.DOWN || direction == ccui.Widget.UP) { 1259 var widgetPosition = this._getWorldCenterPoint(baseWidget); 1260 while (index < count) { 1261 var w = locChildren[index]; 1262 if (w && w instanceof ccui.Widget && w.isFocusEnabled()) { 1263 var length = (w instanceof ccui.Layout)?w._calculateFarthestDistance(baseWidget) 1264 : cc.pLength(cc.pSub(this._getWorldCenterPoint(w), widgetPosition)); 1265 if (length > distance){ 1266 found = index; 1267 distance = length; 1268 } 1269 } 1270 index++; 1271 } 1272 return found; 1273 } 1274 cc.log("invalid focus direction!!!"); 1275 return 0; 1276 }, 1277 1278 /** 1279 * calculate the nearest distance between the baseWidget and the children of the layout 1280 * @param {ccui.Widget} baseWidget the base widget which will be used to calculate the distance between the layout's children and itself 1281 * @returns {Number} return the nearest distance between the baseWidget and the layout's children 1282 * @private 1283 */ 1284 _calculateNearestDistance: function(baseWidget){ 1285 var distance = cc.FLT_MAX; 1286 var widgetPosition = this._getWorldCenterPoint(baseWidget); 1287 var locChildren = this._children; 1288 1289 for (var i = 0, len = locChildren.length; i < len; i++) { 1290 var widget = locChildren[i], length; 1291 if (widget instanceof ccui.Layout) 1292 length = widget._calculateNearestDistance(baseWidget); 1293 else { 1294 if (widget instanceof ccui.Widget && widget.isFocusEnabled()) 1295 length = cc.pLength(cc.pSub(this._getWorldCenterPoint(widget), widgetPosition)); 1296 else 1297 continue; 1298 } 1299 if (length < distance) 1300 distance = length; 1301 } 1302 return distance; 1303 }, 1304 1305 /** 1306 * calculate the farthest distance between the baseWidget and the children of the layout 1307 * @param baseWidget 1308 * @returns {number} 1309 * @private 1310 */ 1311 _calculateFarthestDistance:function(baseWidget){ 1312 var distance = -cc.FLT_MAX; 1313 var widgetPosition = this._getWorldCenterPoint(baseWidget); 1314 var locChildren = this._children; 1315 1316 for (var i = 0, len = locChildren.length; i < len; i++) { 1317 var layout = locChildren[i]; 1318 var length; 1319 if (layout instanceof ccui.Layout) 1320 length = layout._calculateFarthestDistance(baseWidget); 1321 else { 1322 if (layout instanceof ccui.Widget && layout.isFocusEnabled()) { 1323 var wPosition = this._getWorldCenterPoint(layout); 1324 length = cc.pLength(cc.pSub(wPosition, widgetPosition)); 1325 } else 1326 continue; 1327 } 1328 1329 if (length > distance) 1330 distance = length; 1331 } 1332 return distance; 1333 }, 1334 1335 /** 1336 * when a layout pass the focus to it's child, use this method to determine which algorithm to use, nearest or farthest distance algorithm or not 1337 * @param direction 1338 * @param baseWidget 1339 * @private 1340 */ 1341 _findProperSearchingFunctor: function(direction, baseWidget){ 1342 if (baseWidget == null) 1343 return; 1344 1345 var previousWidgetPosition = this._getWorldCenterPoint(baseWidget); 1346 var widgetPosition = this._getWorldCenterPoint(this._findFirstNonLayoutWidget()); 1347 if (direction == ccui.Widget.LEFT) { 1348 this.onPassFocusToChild = (previousWidgetPosition.x > widgetPosition.x) ? this._findNearestChildWidgetIndex.bind(this) 1349 : this._findFarthestChildWidgetIndex.bind(this); 1350 } else if (direction == ccui.Widget.RIGHT) { 1351 this.onPassFocusToChild = (previousWidgetPosition.x > widgetPosition.x) ? this._findFarthestChildWidgetIndex.bind(this) 1352 : this._findNearestChildWidgetIndex.bind(this); 1353 }else if(direction == ccui.Widget.DOWN) { 1354 this.onPassFocusToChild = (previousWidgetPosition.y > widgetPosition.y) ? this._findNearestChildWidgetIndex.bind(this) 1355 : this._findFarthestChildWidgetIndex.bind(this); 1356 }else if(direction == ccui.Widget.UP) { 1357 this.onPassFocusToChild = (previousWidgetPosition.y < widgetPosition.y) ? this._findNearestChildWidgetIndex.bind(this) 1358 : this._findFarthestChildWidgetIndex.bind(this); 1359 }else 1360 cc.log("invalid direction!"); 1361 }, 1362 1363 /** 1364 * find the first non-layout widget in this layout 1365 * @returns {ccui.Widget} 1366 * @private 1367 */ 1368 _findFirstNonLayoutWidget:function(){ 1369 var locChildren = this._children; 1370 for(var i = 0, len = locChildren.length; i < len; i++) { 1371 var child = locChildren[i]; 1372 if (child instanceof ccui.Layout){ 1373 var widget = child._findFirstNonLayoutWidget(); 1374 if(widget) 1375 return widget; 1376 } else{ 1377 if (child instanceof cc.Widget) 1378 return child; 1379 } 1380 } 1381 return null; 1382 }, 1383 1384 /** 1385 * find the first focus enabled widget index in the layout, it will recursive searching the child widget 1386 * @returns {number} 1387 * @private 1388 */ 1389 _findFirstFocusEnabledWidgetIndex: function(){ 1390 var index = 0, locChildren = this.getChildren(); 1391 var count = locChildren.length; 1392 while (index < count) { 1393 var w = locChildren[index]; 1394 if (w && w instanceof ccui.Widget && w.isFocusEnabled()) 1395 return index; 1396 index++; 1397 } 1398 return 0; 1399 }, 1400 1401 /** 1402 * find a focus enabled child Widget in the layout by index 1403 * @param index 1404 * @returns {*} 1405 * @private 1406 */ 1407 _findFocusEnabledChildWidgetByIndex: function(index){ 1408 var widget = this._getChildWidgetByIndex(index); 1409 if (widget){ 1410 if (widget.isFocusEnabled()) 1411 return widget; 1412 index = index + 1; 1413 return this._findFocusEnabledChildWidgetByIndex(index); 1414 } 1415 return null; 1416 }, 1417 1418 /** 1419 * get the center point of a widget in world space 1420 * @param {ccui.Widget} widget 1421 * @returns {cc.Point} 1422 * @private 1423 */ 1424 _getWorldCenterPoint: function(widget){ 1425 //FIXEDME: we don't need to calculate the content size of layout anymore 1426 var widgetSize = widget instanceof ccui.Layout ? widget._getLayoutAccumulatedSize() : widget.getContentSize(); 1427 return widget.convertToWorldSpace(cc.p(widgetSize.width /2, widgetSize.height /2)); 1428 }, 1429 1430 /** 1431 * this method is called internally by nextFocusedWidget. When the dir is Right/Down, then this method will be called 1432 * @param {Number} direction 1433 * @param {ccui.Widget} current the current focused widget 1434 * @returns {ccui.Widget} the next focused widget 1435 * @private 1436 */ 1437 _getNextFocusedWidget: function(direction, current){ 1438 var nextWidget = null, locChildren = this._children; 1439 var previousWidgetPos = locChildren.indexOf(current); 1440 previousWidgetPos = previousWidgetPos + 1; 1441 if (previousWidgetPos < locChildren.length) { 1442 nextWidget = this._getChildWidgetByIndex(previousWidgetPos); 1443 //handle widget 1444 if (nextWidget) { 1445 if (nextWidget.isFocusEnabled()) { 1446 if (nextWidget instanceof ccui.Layout) { 1447 nextWidget._isFocusPassing = true; 1448 return nextWidget.findNextFocusedWidget(direction, nextWidget); 1449 } else { 1450 this.dispatchFocusEvent(current, nextWidget); 1451 return nextWidget; 1452 } 1453 } else 1454 return this._getNextFocusedWidget(direction, nextWidget); 1455 } else 1456 return current; 1457 } else { 1458 if (this._loopFocus) { 1459 if (this._checkFocusEnabledChild()) { 1460 previousWidgetPos = 0; 1461 nextWidget = this._getChildWidgetByIndex(previousWidgetPos); 1462 if (nextWidget.isFocusEnabled()) { 1463 if (nextWidget instanceof ccui.Layout) { 1464 nextWidget._isFocusPassing = true; 1465 return nextWidget.findNextFocusedWidget(direction, nextWidget); 1466 } else { 1467 this.dispatchFocusEvent(current, nextWidget); 1468 return nextWidget; 1469 } 1470 } else 1471 return this._getNextFocusedWidget(direction, nextWidget); 1472 } else { 1473 if (current instanceof ccui.Layout) 1474 return current; 1475 else 1476 return this._focusedWidget; 1477 } 1478 } else{ 1479 if (this._isLastWidgetInContainer(current, direction)){ 1480 if (this._isWidgetAncestorSupportLoopFocus(this, direction)) 1481 return this.findNextFocusedWidget(direction, this); 1482 if (current instanceof ccui.Layout) 1483 return current; 1484 else 1485 return this._focusedWidget; 1486 } else 1487 return this.findNextFocusedWidget(direction, this); 1488 } 1489 } 1490 }, 1491 1492 /** 1493 * this method is called internally by nextFocusedWidget. When the dir is Left/Up, then this method will be called 1494 * @param direction 1495 * @param {ccui.Widget} current the current focused widget 1496 * @returns {ccui.Widget} the next focused widget 1497 * @private 1498 */ 1499 _getPreviousFocusedWidget: function(direction, current){ 1500 var nextWidget = null, locChildren = this._children; 1501 var previousWidgetPos = locChildren.indexOf(current); 1502 previousWidgetPos = previousWidgetPos - 1; 1503 if (previousWidgetPos >= 0){ 1504 nextWidget = this._getChildWidgetByIndex(previousWidgetPos); 1505 if (nextWidget.isFocusEnabled()) { 1506 if (nextWidget instanceof ccui.Layout){ 1507 nextWidget._isFocusPassing = true; 1508 return nextWidget.findNextFocusedWidget(direction, nextWidget); 1509 } 1510 this.dispatchFocusEvent(current, nextWidget); 1511 return nextWidget; 1512 } else 1513 return this._getPreviousFocusedWidget(direction, nextWidget); //handling the disabled widget, there is no actual focus lose or get, so we don't need any envet 1514 }else { 1515 if (this._loopFocus){ 1516 if (this._checkFocusEnabledChild()) { 1517 previousWidgetPos = locChildren.length -1; 1518 nextWidget = this._getChildWidgetByIndex(previousWidgetPos); 1519 if (nextWidget.isFocusEnabled()){ 1520 if (nextWidget instanceof ccui.Layout){ 1521 nextWidget._isFocusPassing = true; 1522 return nextWidget.findNextFocusedWidget(direction, nextWidget); 1523 } else { 1524 this.dispatchFocusEvent(current, nextWidget); 1525 return nextWidget; 1526 } 1527 } else 1528 return this._getPreviousFocusedWidget(direction, nextWidget); 1529 } else 1530 return (current instanceof ccui.Layout) ? current : this._focusedWidget; 1531 } else { 1532 if (this._isLastWidgetInContainer(current, direction)) { 1533 if (this._isWidgetAncestorSupportLoopFocus(this, direction)) 1534 return this.findNextFocusedWidget(direction, this); 1535 return (current instanceof ccui.Layout) ? current : this._focusedWidget; 1536 } else 1537 return this.findNextFocusedWidget(direction, this); 1538 } 1539 } 1540 }, 1541 1542 /** 1543 * find the nth element in the _children array. Only the Widget descendant object will be returned 1544 * @param {Number} index 1545 * @returns {ccui.Widget} 1546 * @private 1547 */ 1548 _getChildWidgetByIndex: function (index) { 1549 var locChildren = this._children; 1550 var size = locChildren.length, count = 0, oldIndex = index; 1551 while (index < size) { 1552 var firstChild = locChildren[index]; 1553 if (firstChild && firstChild instanceof ccui.Widget) 1554 return firstChild; 1555 count++; 1556 index++; 1557 } 1558 1559 var begin = 0; 1560 while (begin < oldIndex) { 1561 var child = locChildren[begin]; 1562 if (child && child instanceof ccui.Widget) 1563 return child; 1564 count++; 1565 begin++; 1566 } 1567 return null; 1568 }, 1569 1570 /** 1571 * whether it is the last element according to all their parents 1572 * @param {ccui.Widget} widget 1573 * @param {Number} direction 1574 * @returns {Boolean} 1575 * @private 1576 */ 1577 _isLastWidgetInContainer:function(widget, direction){ 1578 var parent = widget.getParent(); 1579 if (parent instanceof ccui.Layout) 1580 return true; 1581 1582 var container = parent.getChildren(); 1583 var index = container.indexOf(widget); 1584 if (parent.getLayoutType() == ccui.Layout.LINEAR_HORIZONTAL) { 1585 if (direction == ccui.Widget.LEFT) { 1586 if (index == 0) 1587 return this._isLastWidgetInContainer(parent, direction); 1588 else 1589 return false; 1590 } 1591 if (direction == ccui.Widget.RIGHT) { 1592 if (index == container.length - 1) 1593 return this._isLastWidgetInContainer(parent, direction); 1594 else 1595 return false; 1596 } 1597 if (direction == ccui.Widget.DOWN) 1598 return this._isLastWidgetInContainer(parent, direction); 1599 1600 if (direction == ccui.Widget.UP) 1601 return this._isLastWidgetInContainer(parent, direction); 1602 } else if(parent.getLayoutType() == ccui.Layout.LINEAR_VERTICAL){ 1603 if (direction == ccui.Widget.UP){ 1604 if (index == 0) 1605 return this._isLastWidgetInContainer(parent, direction); 1606 else 1607 return false; 1608 } 1609 if (direction == ccui.Widget.DOWN) { 1610 if (index == container.length - 1) 1611 return this._isLastWidgetInContainer(parent, direction); 1612 else 1613 return false; 1614 } 1615 if (direction == ccui.Widget.LEFT) 1616 return this._isLastWidgetInContainer(parent, direction); 1617 1618 if (direction == ccui.Widget.RIGHT) 1619 return this._isLastWidgetInContainer(parent, direction); 1620 } else { 1621 cc.log("invalid layout Type"); 1622 return false; 1623 } 1624 }, 1625 1626 /** 1627 * Lookup any parent widget with a layout type as the direction, if the layout is loop focused, then return true, otherwise it returns false. 1628 * @param {ccui.Widget} widget 1629 * @param {Number} direction 1630 * @returns {Boolean} 1631 * @private 1632 */ 1633 _isWidgetAncestorSupportLoopFocus: function(widget, direction){ 1634 var parent = widget.getParent(); 1635 if (parent == null) 1636 return false; 1637 if (parent.isLoopFocus()) { 1638 var layoutType = parent.getLayoutType(); 1639 if (layoutType == ccui.Layout.LINEAR_HORIZONTAL) { 1640 if (direction == ccui.Widget.LEFT || direction == ccui.Widget.RIGHT) 1641 return true; 1642 else 1643 return this._isWidgetAncestorSupportLoopFocus(parent, direction); 1644 } 1645 if (layoutType == ccui.Layout.LINEAR_VERTICAL){ 1646 if (direction == ccui.Widget.DOWN || direction == ccui.Widget.UP) 1647 return true; 1648 else 1649 return this._isWidgetAncestorSupportLoopFocus(parent, direction); 1650 } else 1651 cc.assert(0, "invalid layout type"); 1652 } else 1653 return this._isWidgetAncestorSupportLoopFocus(parent, direction); 1654 }, 1655 1656 /** 1657 * pass the focus to the layout's next focus enabled child 1658 * @param {Number} direction 1659 * @param {ccui.Widget} current 1660 * @returns {ccui.Widget} 1661 * @private 1662 */ 1663 _passFocusToChild: function(direction, current){ 1664 if (this._checkFocusEnabledChild()) { 1665 var previousWidget = ccui.Widget.getCurrentFocusedWidget(); 1666 this._findProperSearchingFunctor(direction, previousWidget); 1667 var index = this.onPassFocusToChild(direction, previousWidget); 1668 1669 var widget = this._getChildWidgetByIndex(index); 1670 if (widget instanceof ccui.Layout) { 1671 widget._isFocusPassing = true; 1672 return widget.findNextFocusedWidget(direction, widget); 1673 } else { 1674 this.dispatchFocusEvent(current, widget); 1675 return widget; 1676 } 1677 }else 1678 return this; 1679 }, 1680 1681 /** 1682 * If there are no focus enabled child in the layout, it will return false, otherwise it returns true 1683 * @returns {boolean} 1684 * @private 1685 */ 1686 _checkFocusEnabledChild: function(){ 1687 var locChildren = this._children; 1688 for(var i = 0, len = locChildren.length; i < len; i++){ 1689 var widget = locChildren[i]; 1690 if (widget && widget instanceof ccui.Widget && widget.isFocusEnabled()) 1691 return true; 1692 } 1693 return false; 1694 }, 1695 1696 /** 1697 * Returns the "class name" of widget. 1698 * @returns {string} 1699 */ 1700 getDescription: function () { 1701 return "Layout"; 1702 }, 1703 1704 _createCloneInstance: function () { 1705 return ccui.Layout.create(); 1706 }, 1707 1708 _copyClonedWidgetChildren: function (model) { 1709 ccui.Widget.prototype._copyClonedWidgetChildren.call(this, model); 1710 }, 1711 1712 _copySpecialProperties: function (layout) { 1713 if(!(layout instanceof ccui.Layout)) 1714 return; 1715 this.setBackGroundImageScale9Enabled(layout._backGroundScale9Enabled); 1716 this.setBackGroundImage(layout._backGroundImageFileName, layout._bgImageTexType); 1717 this.setBackGroundImageCapInsets(layout._backGroundImageCapInsets); 1718 this.setBackGroundColorType(layout._colorType); 1719 this.setBackGroundColor(layout._color); 1720 this.setBackGroundColor(layout._startColor, layout._endColor); 1721 this.setBackGroundColorOpacity(layout._opacity); 1722 this.setBackGroundColorVector(layout._alongVector); 1723 this.setLayoutType(layout._layoutType); 1724 this.setClippingEnabled(layout._clippingEnabled); 1725 this.setClippingType(layout._clippingType); 1726 this._loopFocus = layout._loopFocus; 1727 this.__passFocusToChild = layout.__passFocusToChild; 1728 } 1729 }); 1730 ccui.Layout._init_once = null; 1731 ccui.Layout._visit_once = null; 1732 ccui.Layout._layer = -1; 1733 ccui.Layout._sharedCache = null; 1734 1735 if (cc._renderType == cc._RENDER_TYPE_WEBGL) { 1736 //WebGL 1737 ccui.Layout.prototype._stencilClippingVisit = ccui.Layout.prototype._stencilClippingVisitForWebGL; 1738 ccui.Layout.prototype._scissorClippingVisit = ccui.Layout.prototype._scissorClippingVisitForWebGL; 1739 } else { 1740 ccui.Layout.prototype._stencilClippingVisit = ccui.Layout.prototype._stencilClippingVisitForCanvas; 1741 ccui.Layout.prototype._scissorClippingVisit = ccui.Layout.prototype._stencilClippingVisitForCanvas; 1742 } 1743 ccui.Layout._getSharedCache = function () { 1744 return (cc.ClippingNode._sharedCache) || (cc.ClippingNode._sharedCache = cc.newElement("canvas")); 1745 }; 1746 1747 var _p = ccui.Layout.prototype; 1748 1749 // Extended properties 1750 /** @expose */ 1751 _p.clippingEnabled; 1752 cc.defineGetterSetter(_p, "clippingEnabled", _p.isClippingEnabled, _p.setClippingEnabled); 1753 /** @expose */ 1754 _p.clippingType; 1755 cc.defineGetterSetter(_p, "clippingType", null, _p.setClippingType); 1756 /** @expose */ 1757 _p.layoutType; 1758 cc.defineGetterSetter(_p, "layoutType", _p.getLayoutType, _p.setLayoutType); 1759 1760 _p = null; 1761 1762 /** 1763 * allocates and initializes a UILayout. 1764 * @deprecated since v3.0, please use new ccui.Layout() instead. 1765 * @return {ccui.Layout} 1766 * @example 1767 * // example 1768 * var uiLayout = ccui.Layout.create(); 1769 */ 1770 ccui.Layout.create = function () { 1771 return new ccui.Layout(); 1772 }; 1773 1774 // Constants 1775 1776 //layoutBackGround color type 1777 /** 1778 * The None of ccui.Layout's background color type 1779 * @constant 1780 * @type {number} 1781 */ 1782 ccui.Layout.BG_COLOR_NONE = 0; 1783 /** 1784 * The solid of ccui.Layout's background color type, it will use a LayerColor to draw the background. 1785 * @constant 1786 * @type {number} 1787 */ 1788 ccui.Layout.BG_COLOR_SOLID = 1; 1789 /** 1790 * The gradient of ccui.Layout's background color type, it will use a LayerGradient to draw the background. 1791 * @constant 1792 * @type {number} 1793 */ 1794 ccui.Layout.BG_COLOR_GRADIENT = 2; 1795 1796 //Layout type 1797 /** 1798 * The absolute of ccui.Layout's layout type. 1799 * @type {number} 1800 * @constant 1801 */ 1802 ccui.Layout.ABSOLUTE = 0; 1803 /** 1804 * The vertical of ccui.Layout's layout type. 1805 * @type {number} 1806 * @constant 1807 */ 1808 ccui.Layout.LINEAR_VERTICAL = 1; 1809 /** 1810 * The horizontal of ccui.Layout's layout type. 1811 * @type {number} 1812 * @constant 1813 */ 1814 ccui.Layout.LINEAR_HORIZONTAL = 2; 1815 /** 1816 * The relative of ccui.Layout's layout type. 1817 * @type {number} 1818 * @constant 1819 */ 1820 ccui.Layout.RELATIVE = 3; 1821 1822 //Layout clipping type 1823 /** 1824 * The stencil of ccui.Layout's clipping type. 1825 * @type {number} 1826 * @constant 1827 */ 1828 ccui.Layout.CLIPPING_STENCIL = 0; 1829 /** 1830 * The scissor of ccui.Layout's clipping type. 1831 * @type {number} 1832 * @constant 1833 */ 1834 ccui.Layout.CLIPPING_SCISSOR = 1; 1835 1836 /** 1837 * The zOrder value of ccui.Layout's image background. 1838 * @type {number} 1839 * @constant 1840 */ 1841 ccui.Layout.BACKGROUND_IMAGE_ZORDER = -2; 1842 /** 1843 * The zOrder value of ccui.Layout's color background. 1844 * @type {number} 1845 * @constant 1846 */ 1847 ccui.Layout.BACKGROUND_RENDERER_ZORDER = -2;