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