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