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 /** 68 * Constructor of cc.Menu 69 * @constructor 70 * @function 71 * @param {...cc.MenuItem|null} menuItems 72 * 73 * @example 74 * // Example 75 * //there is no limit on how many menu item you can pass in 76 * var myMenu1 = cc.Menu.create(menuitem1, menuitem2, menuitem3); 77 * var myMenu2 = cc.Menu.create([menuitem1, menuitem2, menuitem3]); 78 */ 79 ctor: function (menuItems) { 80 cc.LayerRGBA.prototype.ctor.call(this); 81 this._color = cc.color.WHITE; 82 this.enabled = false; 83 this._opacity = 255; 84 this._selectedItem = null; 85 this._state = -1; 86 87 this._touchListener = cc.EventListener.create({ 88 event: cc.EventListener.TOUCH_ONE_BY_ONE, 89 swallowTouches: true, 90 onTouchBegan: this._onTouchBegan, 91 onTouchMoved: this._onTouchMoved, 92 onTouchEnded: this._onTouchEnded, 93 onTouchCancelled: this._onTouchCancelled 94 }); 95 96 if ((arguments.length > 0) && (arguments[arguments.length - 1] == null)) 97 cc.log("parameters should not be ending with null in Javascript"); 98 99 var argc = arguments.length, items; 100 if (argc == 0) { 101 items = []; 102 } else if (argc == 1) { 103 if (menuItems instanceof Array) { 104 items = menuItems; 105 } 106 else items = [menuItems]; 107 } 108 else if (argc > 1) { 109 items = []; 110 for (var i = 0; i < argc; i++) { 111 if (arguments[i]) 112 items.push(arguments[i]); 113 } 114 } 115 this.initWithArray(items); 116 }, 117 118 onEnter: function () { 119 var locListener = this._touchListener; 120 if (!locListener._isRegistered()) 121 cc.eventManager.addListener(locListener, this); 122 cc.Node.prototype.onEnter.call(this); 123 }, 124 125 /** 126 * @return {cc.Color} 127 */ 128 getColor: function () { 129 var locColor = this._color; 130 return cc.color(locColor.r, locColor.g, locColor.b, locColor.a); 131 }, 132 133 /** 134 * @param {cc.Color} color 135 */ 136 setColor: function (color) { 137 var locColor = this._color; 138 locColor.r = color.r; 139 locColor.g = color.g; 140 locColor.b = color.b; 141 142 var locChildren = this._children; 143 if (locChildren && locChildren.length > 0) { 144 for (var i = 0; i < locChildren.length; i++) { 145 locChildren[i].setColor(color); 146 } 147 } 148 149 if (color.a !== undefined && !color.a_undefined) { 150 this.setOpacity(color.a); 151 } 152 }, 153 154 /** 155 * @return {Number} 156 */ 157 getOpacity: function () { 158 return this._opacity; 159 }, 160 161 /** 162 * @param {Number} opa 163 */ 164 setOpacity: function (opa) { 165 this._opacity = opa; 166 var locChildren = this._children; 167 if (locChildren && locChildren.length > 0) { 168 for (var i = 0; i < locChildren.length; i++) 169 locChildren[i].setOpacity(opa); 170 } 171 this._color.a = opa; 172 }, 173 174 /** 175 * return whether or not the menu will receive events 176 * @return {Boolean} 177 */ 178 isEnabled: function () { 179 return this.enabled; 180 }, 181 182 /** 183 * set whether or not the menu will receive events 184 * @param {Boolean} enabled 185 */ 186 setEnabled: function (enabled) { 187 this.enabled = enabled; 188 }, 189 190 /** 191 * initializes a cc.Menu with it's items 192 * @param {Array} args 193 * @return {Boolean} 194 */ 195 initWithItems: function (args) { 196 var pArray = []; 197 if (args) { 198 for (var i = 0; i < args.length; i++) { 199 if (args[i]) 200 pArray.push(args[i]); 201 } 202 } 203 204 return this.initWithArray(pArray); 205 }, 206 207 /** 208 * initializes a cc.Menu with a Array of cc.MenuItem objects 209 * @param {Array} arrayOfItems 210 * @return {Boolean} 211 */ 212 initWithArray: function (arrayOfItems) { 213 if (cc.LayerRGBA.prototype.init.call(this)) { 214 this.enabled = true; 215 216 // menu in the center of the screen 217 var winSize = cc.winSize; 218 this.setPosition(winSize.width / 2, winSize.height / 2); 219 this.setContentSize(winSize); 220 this.setAnchorPoint(0.5, 0.5); 221 this.ignoreAnchorPointForPosition(true); 222 223 if (arrayOfItems) { 224 for (var i = 0; i < arrayOfItems.length; i++) 225 this.addChild(arrayOfItems[i], i); 226 } 227 228 this._selectedItem = null; 229 this._state = cc.MENU_STATE_WAITING; 230 231 // enable cascade color and opacity on menus 232 this.cascadeColor = true; 233 this.cascadeOpacity = true; 234 235 return true; 236 } 237 return false; 238 }, 239 240 /** 241 * @param {cc.Node} child 242 * @param {Number|Null} [zOrder=] 243 * @param {Number|Null} [tag=] 244 */ 245 addChild: function (child, zOrder, tag) { 246 if (!(child instanceof cc.MenuItem)) 247 throw "cc.Menu.addChild() : Menu only supports MenuItem objects as children"; 248 cc.Layer.prototype.addChild.call(this, child, zOrder, tag); 249 }, 250 251 /** 252 * align items vertically with default padding 253 */ 254 alignItemsVertically: function () { 255 this.alignItemsVerticallyWithPadding(cc.DEFAULT_PADDING); 256 }, 257 258 /** 259 * align items vertically with specified padding 260 * @param {Number} padding 261 */ 262 alignItemsVerticallyWithPadding: function (padding) { 263 var height = -padding, locChildren = this._children, len, i, locScaleY, locHeight, locChild; 264 if (locChildren && locChildren.length > 0) { 265 for (i = 0, len = locChildren.length; i < len; i++) 266 height += locChildren[i].height * locChildren[i].scaleY + padding; 267 268 var y = height / 2.0; 269 270 for (i = 0, len = locChildren.length; i < len; i++) { 271 locChild = locChildren[i]; 272 locHeight = locChild.height; 273 locScaleY = locChild.scaleY; 274 locChild.setPosition(0, y - locHeight * locScaleY / 2); 275 y -= locHeight * locScaleY + padding; 276 } 277 } 278 }, 279 280 /** 281 * align items horizontally with default padding 282 */ 283 alignItemsHorizontally: function () { 284 this.alignItemsHorizontallyWithPadding(cc.DEFAULT_PADDING); 285 }, 286 287 /** 288 * align items horizontally with specified padding 289 * @param {Number} padding 290 */ 291 alignItemsHorizontallyWithPadding: function (padding) { 292 var width = -padding, locChildren = this._children, i, len, locScaleX, locWidth, locChild; 293 if (locChildren && locChildren.length > 0) { 294 for (i = 0, len = locChildren.length; i < len; i++) 295 width += locChildren[i].width * locChildren[i].scaleX + padding; 296 297 var x = -width / 2.0; 298 299 for (i = 0, len = locChildren.length; i < len; i++) { 300 locChild = locChildren[i]; 301 locScaleX = locChild.scaleX; 302 locWidth = locChildren[i].width; 303 locChild.setPosition(x + locWidth * locScaleX / 2, 0); 304 x += locWidth * locScaleX + padding; 305 } 306 } 307 }, 308 309 /** 310 * align items in columns 311 * @example 312 * // Example 313 * menu.alignItemsInColumns(3,2,3)// this will create 3 columns, with 3 items for first column, 2 items for second and 3 for third 314 * 315 * menu.alignItemsInColumns(3,3)//this creates 2 columns, each have 3 items 316 */ 317 alignItemsInColumns: function (/*Multiple Arguments*/) { 318 if ((arguments.length > 0) && (arguments[arguments.length - 1] == null)) 319 cc.log("parameters should not be ending with null in Javascript"); 320 321 var rows = []; 322 for (var i = 0; i < arguments.length; i++) { 323 rows.push(arguments[i]); 324 } 325 var height = -5; 326 var row = 0; 327 var rowHeight = 0; 328 var columnsOccupied = 0; 329 var rowColumns, tmp, len; 330 var locChildren = this._children; 331 if (locChildren && locChildren.length > 0) { 332 for (i = 0, len = locChildren.length; i < len; i++) { 333 if (row >= rows.length) 334 continue; 335 336 rowColumns = rows[row]; 337 // can not have zero columns on a row 338 if (!rowColumns) 339 continue; 340 341 tmp = locChildren[i].height; 342 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 343 344 ++columnsOccupied; 345 if (columnsOccupied >= rowColumns) { 346 height += rowHeight + 5; 347 348 columnsOccupied = 0; 349 rowHeight = 0; 350 ++row; 351 } 352 } 353 } 354 // check if too many rows/columns for available menu items 355 //cc.assert(!columnsOccupied, ""); //? 356 var winSize = cc.director.getWinSize(); 357 358 row = 0; 359 rowHeight = 0; 360 rowColumns = 0; 361 var w = 0.0; 362 var x = 0.0; 363 var y = (height / 2); 364 365 if (locChildren && locChildren.length > 0) { 366 for (i = 0, len = locChildren.length; i < len; i++) { 367 var child = locChildren[i]; 368 if (rowColumns == 0) { 369 rowColumns = rows[row]; 370 w = winSize.width / (1 + rowColumns); 371 x = w; 372 } 373 374 tmp = child._getHeight(); 375 rowHeight = ((rowHeight >= tmp || isNaN(tmp)) ? rowHeight : tmp); 376 child.setPosition(x - winSize.width / 2, y - tmp / 2); 377 378 x += w; 379 ++columnsOccupied; 380 381 if (columnsOccupied >= rowColumns) { 382 y -= rowHeight + 5; 383 columnsOccupied = 0; 384 rowColumns = 0; 385 rowHeight = 0; 386 ++row; 387 } 388 } 389 } 390 }, 391 /** 392 * align menu items in rows 393 * @example 394 * // Example 395 * menu.alignItemsInRows(5,3)//this will align items to 2 rows, first row with 5 items, second row with 3 396 * 397 * menu.alignItemsInRows(4,4,4,4)//this creates 4 rows each have 4 items 398 */ 399 alignItemsInRows: function (/*Multiple arguments*/) { 400 if ((arguments.length > 0) && (arguments[arguments.length - 1] == null)) 401 cc.log("parameters should not be ending with null in Javascript"); 402 var columns = [], i; 403 for (i = 0; i < arguments.length; i++) { 404 columns.push(arguments[i]); 405 } 406 var columnWidths = []; 407 var columnHeights = []; 408 409 var width = -10; 410 var columnHeight = -5; 411 var column = 0; 412 var columnWidth = 0; 413 var rowsOccupied = 0; 414 var columnRows, child, len, tmp; 415 416 var locChildren = this._children; 417 if (locChildren && locChildren.length > 0) { 418 for (i = 0, len = locChildren.length; i < len; i++) { 419 child = locChildren[i]; 420 // check if too many menu items for the amount of rows/columns 421 if (column >= columns.length) 422 continue; 423 424 columnRows = columns[column]; 425 // can't have zero rows on a column 426 if (!columnRows) 427 continue; 428 429 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 430 tmp = child.width; 431 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 432 433 columnHeight += (child.height + 5); 434 ++rowsOccupied; 435 436 if (rowsOccupied >= columnRows) { 437 columnWidths.push(columnWidth); 438 columnHeights.push(columnHeight); 439 width += columnWidth + 10; 440 441 rowsOccupied = 0; 442 columnWidth = 0; 443 columnHeight = -5; 444 ++column; 445 } 446 } 447 } 448 // check if too many rows/columns for available menu items. 449 //cc.assert(!rowsOccupied, ""); 450 var winSize = cc.director.getWinSize(); 451 452 column = 0; 453 columnWidth = 0; 454 columnRows = 0; 455 var x = -width / 2; 456 var y = 0.0; 457 458 if (locChildren && locChildren.length > 0) { 459 for (i = 0, len = locChildren.length; i < len; i++) { 460 child = locChildren[i]; 461 if (columnRows == 0) { 462 columnRows = columns[column]; 463 y = columnHeights[column]; 464 } 465 466 // columnWidth = fmaxf(columnWidth, [item contentSize].width); 467 tmp = child._getWidth(); 468 columnWidth = ((columnWidth >= tmp || isNaN(tmp)) ? columnWidth : tmp); 469 470 child.setPosition(x + columnWidths[column] / 2, y - winSize.height / 2); 471 472 y -= child.height + 10; 473 ++rowsOccupied; 474 475 if (rowsOccupied >= columnRows) { 476 x += columnWidth + 5; 477 rowsOccupied = 0; 478 columnRows = 0; 479 columnWidth = 0; 480 ++column; 481 } 482 } 483 } 484 }, 485 486 /** 487 * @param {cc.Node} child 488 * @param {boolean} cleanup 489 */ 490 removeChild: function (child, cleanup) { 491 if (child == null) 492 return; 493 if (!(child instanceof cc.MenuItem)) { 494 cc.log("cc.Menu.removeChild():Menu only supports MenuItem objects as children"); 495 return; 496 } 497 498 if (this._selectedItem == child) 499 this._selectedItem = null; 500 cc.Node.prototype.removeChild.call(this, child, cleanup); 501 }, 502 503 _onTouchBegan: function (touch, event) { 504 var target = event.getCurrentTarget(); 505 if (target._state != cc.MENU_STATE_WAITING || !target._visible || !target.enabled) 506 return false; 507 508 for (var c = target.parent; c != null; c = c.parent) { 509 if (!c.isVisible()) 510 return false; 511 } 512 513 target._selectedItem = target._itemForTouch(touch); 514 if (target._selectedItem) { 515 target._state = cc.MENU_STATE_TRACKING_TOUCH; 516 target._selectedItem.selected(); 517 return true; 518 } 519 return false; 520 }, 521 522 _onTouchEnded: function (touch, event) { 523 var target = event.getCurrentTarget(); 524 if (target._state !== cc.MENU_STATE_TRACKING_TOUCH) { 525 cc.log("cc.Menu.onTouchEnded(): invalid state"); 526 return; 527 } 528 if (target._selectedItem) { 529 target._selectedItem.unselected(); 530 target._selectedItem.activate(); 531 } 532 target._state = cc.MENU_STATE_WAITING; 533 }, 534 535 _onTouchCancelled: function (touch, event) { 536 var target = event.getCurrentTarget(); 537 if (target._state !== cc.MENU_STATE_TRACKING_TOUCH) { 538 cc.log("cc.Menu.onTouchCancelled(): invalid state"); 539 return; 540 } 541 if (this._selectedItem) 542 target._selectedItem.unselected(); 543 target._state = cc.MENU_STATE_WAITING; 544 }, 545 546 _onTouchMoved: function (touch, event) { 547 var target = event.getCurrentTarget(); 548 if (target._state !== cc.MENU_STATE_TRACKING_TOUCH) { 549 cc.log("cc.Menu.onTouchMoved(): invalid state"); 550 return; 551 } 552 var currentItem = target._itemForTouch(touch); 553 if (currentItem != target._selectedItem) { 554 if (target._selectedItem) 555 target._selectedItem.unselected(); 556 target._selectedItem = currentItem; 557 if (target._selectedItem) 558 target._selectedItem.selected(); 559 } 560 }, 561 562 /** 563 * custom on exit 564 */ 565 onExit: function () { 566 if (this._state == cc.MENU_STATE_TRACKING_TOUCH) { 567 if (this._selectedItem) { 568 this._selectedItem.unselected(); 569 this._selectedItem = null; 570 } 571 this._state = cc.MENU_STATE_WAITING; 572 } 573 cc.Node.prototype.onExit.call(this); 574 }, 575 576 setOpacityModifyRGB: function (value) { 577 }, 578 579 isOpacityModifyRGB: function () { 580 return false; 581 }, 582 583 _itemForTouch: function (touch) { 584 var touchLocation = touch.getLocation(); 585 var itemChildren = this._children, locItemChild; 586 if (itemChildren && itemChildren.length > 0) { 587 for (var i = 0; i < itemChildren.length; i++) { 588 locItemChild = itemChildren[i]; 589 if (locItemChild.isVisible() && locItemChild.isEnabled()) { 590 var local = locItemChild.convertToNodeSpace(touchLocation); 591 var r = locItemChild.rect(); 592 r.x = 0; 593 r.y = 0; 594 if (cc.rectContainsPoint(r, local)) 595 return locItemChild; 596 } 597 } 598 } 599 return null; 600 } 601 }); 602 603 var _p = cc.Menu.prototype; 604 605 // Extended properties 606 /** @expose */ 607 _p.enabled; 608 609 /** 610 * create a new menu 611 * @param {...cc.MenuItem|null} menuItems 612 * @return {cc.Menu} 613 * @example 614 * // Example 615 * //there is no limit on how many menu item you can pass in 616 * var myMenu = cc.Menu.create(menuitem1, menuitem2, menuitem3); 617 */ 618 cc.Menu.create = function (menuItems) { 619 var argc = arguments.length; 620 if ((argc > 0) && (arguments[argc - 1] == null)) 621 cc.log("parameters should not be ending with null in Javascript"); 622 623 var ret; 624 if (argc == 0) 625 ret = new cc.Menu(); 626 else if (argc == 1) 627 ret = new cc.Menu(menuItems); 628 else 629 ret = new cc.Menu(Array.prototype.slice.call(arguments, 0)); 630 return ret; 631 }; 632