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