1 /**************************************************************************** 2 Copyright (c) 2010-2012 cocos2d-x.org 3 Copyright (c) 2008-2010 Ricardo Quesada 4 Copyright (c) 2011 Zynga Inc. 5 6 http://www.cocos2d-x.org 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy 9 of this software and associated documentation files (the "Software"), to deal 10 in the Software without restriction, including without limitation the rights 11 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 copies of the Software, and to permit persons to whom the Software is 13 furnished to do so, subject to the following conditions: 14 15 The above copyright notice and this permission notice shall be included in 16 all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 THE SOFTWARE. 25 ****************************************************************************/ 26 27 /** 28 * @constant 29 * @type Number 30 */ 31 cc.MENU_STATE_WAITING = 0; 32 /** 33 * @constant 34 * @type Number 35 */ 36 cc.MENU_STATE_TRACKING_TOUCH = 1; 37 /** 38 * @constant 39 * @type Number 40 */ 41 cc.MENU_HANDLER_PRIORITY = -128; 42 /** 43 * @constant 44 * @type Number 45 */ 46 cc.DEFAULT_PADDING = 5; 47 48 /** 49 * <p> Features and Limitation:<br/> 50 * - You can add MenuItem objects in runtime using addChild:<br/> 51 * - But the only accepted children are MenuItem objects</p> 52 * @class 53 * @extends cc.LayerRGBA 54 * 55 * @property {Boolean} enabled - Indicates whether or not the menu is enabled 56 */ 57 cc.Menu = cc.LayerRGBA.extend(/** @lends cc.Menu# */{ 58 enabled:false, 59 60 _color:null, 61 _opacity:0, 62 _selectedItem:null, 63 _state:-1, 64 _touchListener: null, 65 _className:"Menu", 66 67 ctor:function(){ 68 cc.LayerRGBA.prototype.ctor.call(this); 69 this._color = cc.color.WHITE; 70 this.enabled = false; 71 this._opacity = 255; 72 this._selectedItem = null; 73 this._state = -1; 74 75 this._touchListener = cc.EventListener.create({ 76 event: cc.EventListener.TOUCH_ONE_BY_ONE, 77 swallowTouches: true, 78 onTouchBegan: this._onTouchBegan, 79 onTouchMoved: this._onTouchMoved, 80 onTouchEnded: this._onTouchEnded, 81 onTouchCancelled: this._onTouchCancelled 82 }); 83 }, 84 85 onEnter: function(){ 86 var locListener = this._touchListener; 87 if(!locListener._isRegistered()) 88 cc.eventManager.addListener(locListener, this); 89 cc.Node.prototype.onEnter.call(this); 90 }, 91 92 /** 93 * @return {cc.Color} 94 */ 95 getColor:function () { 96 var locColor = this._color; 97 return cc.color(locColor.r, locColor.g, locColor.b, locColor.a); 98 }, 99 100 /** 101 * @param {cc.Color} color 102 */ 103 setColor:function (color) { 104 var locColor = this._color; 105 locColor.r = color.r; 106 locColor.g = color.g; 107 locColor.b = color.b; 108 109 var locChildren = this._children; 110 if (locChildren && locChildren.length > 0) { 111 for (var i = 0; i < locChildren.length; i++){ 112 locChildren[i].setColor(color); 113 } 114 } 115 116 if (color.a !== undefined && !color.a_undefined) { 117 this.setOpacity(color.a); 118 } 119 }, 120 121 /** 122 * @return {Number} 123 */ 124 getOpacity:function () { 125 return this._opacity; 126 }, 127 128 /** 129 * @param {Number} opa 130 */ 131 setOpacity:function (opa) { 132 this._opacity = opa; 133 var locChildren = this._children; 134 if (locChildren && locChildren.length > 0) { 135 for (var i = 0; i < locChildren.length; i++) 136 locChildren[i].setOpacity(opa); 137 } 138 this._color.a = opa; 139 }, 140 141 /** 142 * return whether or not the menu will receive events 143 * @return {Boolean} 144 */ 145 isEnabled:function () { 146 return this.enabled; 147 }, 148 149 /** 150 * set whether or not the menu will receive events 151 * @param {Boolean} enabled 152 */ 153 setEnabled:function (enabled) { 154 this.enabled = enabled; 155 }, 156 157 /** 158 * initializes a cc.Menu with it's items 159 * @param {Array} args 160 * @return {Boolean} 161 */ 162 initWithItems:function (args) { 163 var pArray = []; 164 if (args) { 165 for (var i = 0; i < args.length; i++) { 166 if (args[i]) 167 pArray.push(args[i]); 168 } 169 } 170 171 return this.initWithArray(pArray); 172 }, 173 174 /** 175 * initializes a cc.Menu with a Array of cc.MenuItem objects 176 * @param {Array} arrayOfItems 177 * @return {Boolean} 178 */ 179 initWithArray:function (arrayOfItems) { 180 if (this.init()) { 181 this.enabled = true; 182 183 // menu in the center of the screen 184 var winSize = cc.director.getWinSize(); 185 this.attr({ 186 x: winSize.width / 2, 187 y: winSize.height / 2, 188 size: winSize, 189 anchorX: 0.5, 190 anchorY: 0.5, 191 ignoreAnchor: true 192 }); 193 194 if (arrayOfItems) { 195 for (var i = 0; i < arrayOfItems.length; i++) 196 this.addChild(arrayOfItems[i],i); 197 } 198 199 this._selectedItem = null; 200 this._state = cc.MENU_STATE_WAITING; 201 202 // enable cascade color and opacity on menus 203 this.cascadeColor = true; 204 this.cascadeOpacity = true; 205 206 return true; 207 } 208 return false; 209 }, 210 211 /** 212 * @param {cc.Node} child 213 * @param {Number|Null} [zOrder=] 214 * @param {Number|Null} [tag=] 215 */ 216 addChild:function (child, zOrder, tag) { 217 if(!(child instanceof cc.MenuItem)) 218 throw "cc.Menu.addChild() : Menu only supports MenuItem objects as children"; 219 cc.Layer.prototype.addChild.call(this, child, zOrder, tag); 220 }, 221 222 /** 223 * align items vertically with default padding 224 */ 225 alignItemsVertically:function () { 226 this.alignItemsVerticallyWithPadding(cc.DEFAULT_PADDING); 227 }, 228 229 /** 230 * align items vertically with specified padding 231 * @param {Number} padding 232 */ 233 alignItemsVerticallyWithPadding:function (padding) { 234 var height = -padding, locChildren = this._children, len, i, locScaleY, locHeight, locChild; 235 if (locChildren && locChildren.length > 0) { 236 for (i = 0, len = locChildren.length; i < len; i++) 237 height += locChildren[i].height * locChildren[i].scaleY + padding; 238 239 var y = height / 2.0; 240 241 for (i = 0, len = locChildren.length; i < len; i++) { 242 locChild = locChildren[i]; 243 locHeight = locChild.height; 244 locScaleY = locChild.scaleY; 245 locChild.setPosition(0, y - locHeight * locScaleY / 2); 246 y -= locHeight * locScaleY + padding; 247 } 248 } 249 }, 250 251 /** 252 * align items horizontally with default padding 253 */ 254 alignItemsHorizontally:function () { 255 this.alignItemsHorizontallyWithPadding(cc.DEFAULT_PADDING); 256 }, 257 258 /** 259 * align items horizontally with specified padding 260 * @param {Number} padding 261 */ 262 alignItemsHorizontallyWithPadding:function (padding) { 263 var width = -padding, locChildren = this._children, i, len, locScaleX, locWidth, locChild; 264 if (locChildren && locChildren.length > 0) { 265 for (i = 0, len = locChildren.length; i < len; i++) 266 width += locChildren[i].width * locChildren[i].scaleX + padding; 267 268 var x = -width / 2.0; 269 270 for (i = 0, len = locChildren.length; i < len; i++) { 271 locChild = locChildren[i]; 272 locScaleX = locChild.scaleX; 273 locWidth = locChildren[i].width; 274 locChild.setPosition(x + locWidth * locScaleX / 2, 0); 275 x += locWidth * locScaleX + padding; 276 } 277 } 278 }, 279 280 /** 281 * align items in columns 282 * @example 283 * // Example 284 * menu.alignItemsInColumns(3,2,3)// this will create 3 columns, with 3 items for first column, 2 items for second and 3 for third 285 * 286 * menu.alignItemsInColumns(3,3)//this creates 2 columns, each have 3 items 287 */ 288 alignItemsInColumns:function (/*Multiple Arguments*/) { 289 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 290 cc.log("parameters should not be ending with null in Javascript"); 291 292 var rows = []; 293 for (var i = 0; i < arguments.length; i++) { 294 rows.push(arguments[i]); 295 } 296 var height = -5; 297 var row = 0; 298 var rowHeight = 0; 299 var columnsOccupied = 0; 300 var rowColumns, tmp, len; 301 var locChildren = this._children; 302 if (locChildren && locChildren.length > 0) { 303 for (i = 0, len = locChildren.length; i < len; i++) { 304 if(row >= rows.length) 305 continue; 306 307 rowColumns = rows[row]; 308 // can not have zero columns on a row 309 if(!rowColumns) 310 continue; 311 312 tmp = locChildren[i].height; 313 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 314 315 ++columnsOccupied; 316 if (columnsOccupied >= rowColumns) { 317 height += rowHeight + 5; 318 319 columnsOccupied = 0; 320 rowHeight = 0; 321 ++row; 322 } 323 } 324 } 325 // check if too many rows/columns for available menu items 326 //cc.assert(!columnsOccupied, ""); //? 327 var winSize = cc.director.getWinSize(); 328 329 row = 0; 330 rowHeight = 0; 331 rowColumns = 0; 332 var w = 0.0; 333 var x = 0.0; 334 var y = (height / 2); 335 336 if (locChildren && locChildren.length > 0) { 337 for (i = 0, len = locChildren.length; i < len; i++) { 338 var child = locChildren[i]; 339 if (rowColumns == 0) { 340 rowColumns = rows[row]; 341 w = winSize.width / (1 + rowColumns); 342 x = w; 343 } 344 345 tmp = child._getHeight(); 346 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 347 child.setPosition(x - winSize.width / 2, y - tmp / 2); 348 349 x += w; 350 ++columnsOccupied; 351 352 if (columnsOccupied >= rowColumns) { 353 y -= rowHeight + 5; 354 columnsOccupied = 0; 355 rowColumns = 0; 356 rowHeight = 0; 357 ++row; 358 } 359 } 360 } 361 }, 362 /** 363 * align menu items in rows 364 * @example 365 * // Example 366 * menu.alignItemsInRows(5,3)//this will align items to 2 rows, first row with 5 items, second row with 3 367 * 368 * menu.alignItemsInRows(4,4,4,4)//this creates 4 rows each have 4 items 369 */ 370 alignItemsInRows:function (/*Multiple arguments*/) { 371 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 372 cc.log("parameters should not be ending with null in Javascript"); 373 var columns = [], i; 374 for (i = 0; i < arguments.length; i++) { 375 columns.push(arguments[i]); 376 } 377 var columnWidths = []; 378 var columnHeights = []; 379 380 var width = -10; 381 var columnHeight = -5; 382 var column = 0; 383 var columnWidth = 0; 384 var rowsOccupied = 0; 385 var columnRows, child, len, tmp; 386 387 var locChildren = this._children; 388 if (locChildren && locChildren.length > 0) { 389 for (i = 0, len = locChildren.length; i < len; i++) { 390 child = locChildren[i]; 391 // check if too many menu items for the amount of rows/columns 392 if(column >= columns.length) 393 continue; 394 395 columnRows = columns[column]; 396 // can't have zero rows on a column 397 if(!columnRows) 398 continue; 399 400 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 401 tmp = child.width; 402 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 403 404 columnHeight += (child.height + 5); 405 ++rowsOccupied; 406 407 if (rowsOccupied >= columnRows) { 408 columnWidths.push(columnWidth); 409 columnHeights.push(columnHeight); 410 width += columnWidth + 10; 411 412 rowsOccupied = 0; 413 columnWidth = 0; 414 columnHeight = -5; 415 ++column; 416 } 417 } 418 } 419 // check if too many rows/columns for available menu items. 420 //cc.assert(!rowsOccupied, ""); 421 var winSize = cc.director.getWinSize(); 422 423 column = 0; 424 columnWidth = 0; 425 columnRows = 0; 426 var x = -width / 2; 427 var y = 0.0; 428 429 if (locChildren && locChildren.length > 0) { 430 for (i = 0, len = locChildren.length; i < len; i++) { 431 child = locChildren[i]; 432 if (columnRows == 0) { 433 columnRows = columns[column]; 434 y = columnHeights[column]; 435 } 436 437 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 438 tmp = child._getWidth(); 439 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 440 441 child.setPosition(x + columnWidths[column] / 2, y - winSize.height / 2); 442 443 y -= child.height + 10; 444 ++rowsOccupied; 445 446 if (rowsOccupied >= columnRows) { 447 x += columnWidth + 5; 448 rowsOccupied = 0; 449 columnRows = 0; 450 columnWidth = 0; 451 ++column; 452 } 453 } 454 } 455 }, 456 457 /** 458 * @param {cc.Node} child 459 * @param {boolean} cleanup 460 */ 461 removeChild:function(child, cleanup){ 462 if(child == null) 463 return; 464 if(!(child instanceof cc.MenuItem)){ 465 cc.log("cc.Menu.removeChild():Menu only supports MenuItem objects as children"); 466 return; 467 } 468 469 if (this._selectedItem == child) 470 this._selectedItem = null; 471 cc.Node.prototype.removeChild.call(this, child, cleanup); 472 }, 473 474 _onTouchBegan:function (touch, event) { 475 var target = event.getCurrentTarget(); 476 if (target._state != cc.MENU_STATE_WAITING || !target._visible || !target.enabled) 477 return false; 478 479 for (var c = target.parent; c != null; c = c.parent) { 480 if (!c.isVisible()) 481 return false; 482 } 483 484 target._selectedItem = target._itemForTouch(touch); 485 if (target._selectedItem) { 486 target._state = cc.MENU_STATE_TRACKING_TOUCH; 487 target._selectedItem.selected(); 488 return true; 489 } 490 return false; 491 }, 492 493 _onTouchEnded:function (touch, event) { 494 var target = event.getCurrentTarget(); 495 if(target._state !== cc.MENU_STATE_TRACKING_TOUCH){ 496 cc.log("cc.Menu.onTouchEnded(): invalid state"); 497 return; 498 } 499 if (target._selectedItem) { 500 target._selectedItem.unselected(); 501 target._selectedItem.activate(); 502 } 503 target._state = cc.MENU_STATE_WAITING; 504 }, 505 506 _onTouchCancelled:function (touch, event) { 507 var target = event.getCurrentTarget(); 508 if(target._state !== cc.MENU_STATE_TRACKING_TOUCH){ 509 cc.log("cc.Menu.onTouchCancelled(): invalid state"); 510 return; 511 } 512 if (this._selectedItem) 513 target._selectedItem.unselected(); 514 target._state = cc.MENU_STATE_WAITING; 515 }, 516 517 _onTouchMoved:function (touch, event) { 518 var target = event.getCurrentTarget(); 519 if(target._state !== cc.MENU_STATE_TRACKING_TOUCH){ 520 cc.log("cc.Menu.onTouchMoved(): invalid state"); 521 return; 522 } 523 var currentItem = target._itemForTouch(touch); 524 if (currentItem != target._selectedItem) { 525 if (target._selectedItem) 526 target._selectedItem.unselected(); 527 target._selectedItem = currentItem; 528 if (target._selectedItem) 529 target._selectedItem.selected(); 530 } 531 }, 532 533 /** 534 * custom on exit 535 */ 536 onExit:function () { 537 if (this._state == cc.MENU_STATE_TRACKING_TOUCH) { 538 if(this._selectedItem){ 539 this._selectedItem.unselected(); 540 this._selectedItem = null; 541 } 542 this._state = cc.MENU_STATE_WAITING; 543 } 544 cc.Node.prototype.onExit.call(this); 545 }, 546 547 setOpacityModifyRGB:function (value) { 548 }, 549 550 isOpacityModifyRGB:function () { 551 return false; 552 }, 553 554 _itemForTouch:function (touch) { 555 var touchLocation = touch.getLocation(); 556 var itemChildren = this._children, locItemChild; 557 if (itemChildren && itemChildren.length > 0) { 558 for (var i = 0; i < itemChildren.length; i++) { 559 locItemChild = itemChildren[i]; 560 if (locItemChild.isVisible() && locItemChild.isEnabled()) { 561 var local = locItemChild.convertToNodeSpace(touchLocation); 562 var r = locItemChild.rect(); 563 r.x = 0; 564 r.y = 0; 565 if (cc.rectContainsPoint(r, local)) 566 return locItemChild; 567 } 568 } 569 } 570 return null; 571 } 572 }); 573 574 window._p = cc.Menu.prototype; 575 576 // Extended properties 577 /** @expose */ 578 _p.enabled; 579 delete window._p; 580 581 /** 582 * create a new menu 583 * @param {...cc.MenuItem|null} menuItems 584 * @return {cc.Menu} 585 * @example 586 * // Example 587 * //there is no limit on how many menu item you can pass in 588 * var myMenu = cc.Menu.create(menuitem1, menuitem2, menuitem3); 589 */ 590 cc.Menu.create = function (menuItems) { 591 if((arguments.length > 0) && (arguments[arguments.length-1] == null)) 592 cc.log("parameters should not be ending with null in Javascript"); 593 594 var ret = new cc.Menu(); 595 596 if (arguments.length == 0) { 597 ret.initWithItems(null, null); 598 } else if (arguments.length == 1) { 599 if (arguments[0] instanceof Array) { 600 ret.initWithArray(arguments[0]); 601 return ret; 602 } 603 } 604 ret.initWithItems(arguments); 605 return ret; 606 }; 607