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