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