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