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