1 /**
  2  * CCControlButton.m
  3  *
  4  * Copyright (c) 2010-2012 cocos2d-x.org
  5  * Copyright 2011 Yannick Loriot.
  6  * http://yannickloriot.com
  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 cc.CONTROL_ZOOM_ACTION_TAG = 0xCCCB0001;
 28 
 29 /** @class CCControlButton Button control for Cocos2D. */
 30 cc.ControlButton = cc.Control.extend({
 31     _doesAdjustBackgroundImage:false,
 32     _zoomOnTouchDown:false,
 33     _preferredSize: null,
 34     _labelAnchorPoint: null,
 35     _currentTitle: null,
 36     _currentTitleColor: null,
 37     _titleLabel:null,
 38     _backgroundSprite:null,
 39     _opacity:0,
 40     _isPushed:false,
 41     _titleDispatchTable:null,
 42     _titleColorDispatchTable:null,
 43     _titleLabelDispatchTable:null,
 44     _backgroundSpriteDispatchTable:null,
 45     _parentInited:false,
 46 
 47     _marginV:0,
 48     _marginH:0,
 49 
 50     ctor:function () {
 51         cc.Control.prototype.ctor.call(this);
 52         this._preferredSize = new cc.Size(0, 0);
 53         this._labelAnchorPoint = new cc.Point(0, 0);
 54         this._currentTitle = "";
 55         this._currentTitleColor = cc.white();
 56         this._titleDispatchTable = {};
 57         this._titleColorDispatchTable = {};
 58         this._titleLabelDispatchTable = {};
 59         this._backgroundSpriteDispatchTable = {};
 60     },
 61 
 62     init:function () {
 63         return this.initWithLabelAndBackgroundSprite(cc.LabelTTF.create("", "Arial", 12), cc.Scale9Sprite.create());
 64     },
 65 
 66     needsLayout:function () {
 67         if (!this._parentInited) {
 68             return;
 69         }
 70         // Hide the background and the label
 71         if(this._titleLabel)
 72             this._titleLabel.setVisible(false);
 73         if(this._backgroundSprite)
 74             this._backgroundSprite.setVisible(false);
 75 
 76         // Update anchor of all labels
 77         this.setLabelAnchorPoint(this._labelAnchorPoint);
 78 
 79         // Update the label to match with the current state
 80         //CC_SAFE_RELEASE(this._currentTitle)
 81         var locState = this._state;
 82 
 83         this._currentTitle = this.getTitleForState(locState);
 84         this._currentTitleColor = this.getTitleColorForState(locState);
 85         this._titleLabel = this.getTitleLabelForState(locState);
 86 
 87         var label = this._titleLabel;
 88         if (label && label.setString)
 89             label.setString(this._currentTitle);
 90         if (label && label.RGBAProtocol)
 91             label.setColor(this._currentTitleColor);
 92 
 93         var locContentSize = this.getContentSize();
 94         if(label)
 95             label.setPosition(locContentSize.width / 2, locContentSize.height / 2);
 96 
 97         // Update the background sprite
 98         this._backgroundSprite = this.getBackgroundSpriteForState(locState);
 99         var locBackgroundSprite = this._backgroundSprite;
100         if(locBackgroundSprite)
101             locBackgroundSprite.setPosition(locContentSize.width / 2, locContentSize.height / 2);
102 
103         // Get the title label size
104         var titleLabelSize = label ? label.getBoundingBox()._size : cc.size(0, 0);
105 
106         // Adjust the background image if necessary
107         if (this._doesAdjustBackgroundImage) {
108             // Add the margins
109             if(locBackgroundSprite)
110                 locBackgroundSprite.setContentSize(titleLabelSize.width + this._marginH * 2, titleLabelSize.height + this._marginV * 2);
111         } else {
112             //TODO: should this also have margins if one of the preferred sizes is relaxed?
113             if(locBackgroundSprite){
114                 var preferredSize = locBackgroundSprite.getPreferredSize();
115                 preferredSize = cc.size(preferredSize.width, preferredSize.height);
116                 if (preferredSize.width <= 0)
117                     preferredSize.width = titleLabelSize.width;
118                 if (preferredSize.height <= 0)
119                     preferredSize.height = titleLabelSize.height;
120 
121                 locBackgroundSprite.setContentSize(preferredSize);
122             }
123         }
124 
125         // Set the content size
126         var rectTitle = label? label.getBoundingBox():cc.rect(0,0,0,0);
127         var rectBackground = locBackgroundSprite? locBackgroundSprite.getBoundingBox():cc.rect(0,0,0,0);
128         var maxRect = cc.rectUnion(rectTitle, rectBackground);
129         this.setContentSize(maxRect.width, maxRect.height);
130         locContentSize = this.getContentSize();
131         if(label){
132             label.setPosition(locContentSize.width / 2, locContentSize.height / 2);
133             label.setVisible(true);
134         }
135         if(locBackgroundSprite){
136             locBackgroundSprite.setPosition(locContentSize.width / 2, locContentSize.height / 2);
137             locBackgroundSprite.setVisible(true);
138         }
139     },
140 
141     initWithLabelAndBackgroundSprite:function (label, backgroundSprite) {
142         if(!label || !label.RGBAProtocol)
143             throw "cc.ControlButton.initWithLabelAndBackgroundSprite(): label should be non-null";
144         if(!backgroundSprite)
145             throw "cc.ControlButton.initWithLabelAndBackgroundSprite(): backgroundSprite should be non-null";
146         if (cc.Control.prototype.init.call(this, true)) {
147             this._parentInited = true;
148 
149             // Initialize the button state tables
150             this._titleDispatchTable = {};
151             this._titleColorDispatchTable = {};
152             this._titleLabelDispatchTable = {};
153             this._backgroundSpriteDispatchTable = {};
154 
155             this.setTouchEnabled(true);
156             this._isPushed = false;
157             this._zoomOnTouchDown = true;
158 
159             this._currentTitle = null;
160 
161             // Adjust the background image by default
162             this.setAdjustBackgroundImage(true);
163             this.setPreferredSize(cc.size(0,0));
164 
165             // Zooming button by default
166             this._zoomOnTouchDown = true;
167 
168             // Set the default anchor point
169             this.ignoreAnchorPointForPosition(false);
170             this.setAnchorPoint(0.5, 0.5);
171 
172             // Set the nodes
173             this._titleLabel = label;
174             this._backgroundSprite = backgroundSprite;
175 
176             // Set the default color and opacity
177             this.setOpacity(255);
178             this.setOpacityModifyRGB(true);
179 
180             // Initialize the dispatch table
181             var tempString = label.getString();
182             //tempString.autorelease();
183             this.setTitleForState(tempString, cc.CONTROL_STATE_NORMAL);
184             this.setTitleColorForState(label.getColor(), cc.CONTROL_STATE_NORMAL);
185             this.setTitleLabelForState(label, cc.CONTROL_STATE_NORMAL);
186             this.setBackgroundSpriteForState(backgroundSprite, cc.CONTROL_STATE_NORMAL);
187 
188             this._state = cc.CONTROL_STATE_NORMAL;
189 
190             //default margins
191             this._marginH = 24;
192             this._marginV = 12;
193 
194             this._labelAnchorPoint = new cc.Point(0.5, 0.5);
195 
196             this.setPreferredSize(cc.SizeZero());
197 
198             // Layout update
199             this.needsLayout();
200             return true;
201         }//couldn't init the CCControl
202         else
203             return false;
204     },
205 
206     initWithTitleAndFontNameAndFontSize:function (title, fontName, fontSize) {
207         var label = cc.LabelTTF.create(title, fontName, fontSize);
208         return this.initWithLabelAndBackgroundSprite(label, cc.Scale9Sprite.create());
209     },
210 
211     initWithBackgroundSprite:function (sprite) {
212         var label = cc.LabelTTF.create("", "Arial", 30);//
213         return this.initWithLabelAndBackgroundSprite(label, sprite);
214     },
215 
216     /**
217      * Adjust the background image. YES by default. If the property is set to NO, the background will use the prefered size of the background image.
218      * @return {Boolean}
219      */
220     doesAdjustBackgroundImage:function () {
221         return this._doesAdjustBackgroundImage;
222     },
223 
224     setAdjustBackgroundImage:function (adjustBackgroundImage) {
225         this._doesAdjustBackgroundImage = adjustBackgroundImage;
226         this.needsLayout();
227     },
228 
229     /** Adjust the button zooming on touchdown. Default value is YES. */
230     getZoomOnTouchDown:function () {
231         return this._zoomOnTouchDown;
232     },
233 
234     setZoomOnTouchDown:function (zoomOnTouchDown) {
235         return this._zoomOnTouchDown = zoomOnTouchDown;
236     },
237 
238     /** The prefered size of the button, if label is larger it will be expanded. */
239     getPreferredSize:function () {
240         return this._preferredSize;
241     },
242 
243     setPreferredSize:function (size) {
244         if (size.width === 0 && size.height === 0) {
245             this._doesAdjustBackgroundImage = true;
246         } else {
247             this._doesAdjustBackgroundImage = false;
248             var locTable = this._backgroundSpriteDispatchTable;
249             for (var itemKey in locTable)
250                 locTable[itemKey].setPreferredSize(size);
251         }
252         this._preferredSize = size;
253         this.needsLayout();
254     },
255 
256     getLabelAnchorPoint:function () {
257         return this._labelAnchorPoint;
258     },
259     setLabelAnchorPoint:function (labelAnchorPoint) {
260         this._labelAnchorPoint = labelAnchorPoint;
261         if(this._titleLabel)
262             this._titleLabel.setAnchorPoint(labelAnchorPoint);
263     },
264 
265     /**
266      * The current title that is displayed on the button.
267      * @return {string}
268      */
269     _getCurrentTitle:function () {
270         return this._currentTitle;
271     },
272 
273     /** The current color used to display the title. */
274     _getCurrentTitleColor:function () {
275         return this._currentTitleColor;
276     },
277 
278     /* Override setter to affect a background sprite too */
279     getOpacity:function () {
280         return this._opacity;
281     },
282 
283     setOpacity:function (opacity) {
284         // XXX fixed me if not correct
285         cc.Control.prototype.setOpacity.call(this, opacity);
286         /*this._opacity = opacity;
287         var controlChildren = this.getChildren();
288         for (var i = 0; i < controlChildren.length; i++) {
289             var selChild = controlChildren[i];
290             if (selChild && selChild.RGBAProtocol)
291                 selChild.setOpacity(opacity);
292         }*/
293         var locTable = this._backgroundSpriteDispatchTable;
294         for (var itemKey in locTable)
295             locTable[itemKey].setOpacity(opacity);
296     },
297 
298     setColor:function(color){
299         cc.Control.prototype.setColor.call(this,color);
300         var locTable = this._backgroundSpriteDispatchTable;
301         for(var key in locTable)
302             locTable[key].setColor(color);
303     },
304 
305     getColor:function(){
306       return this._realColor;
307     },
308 
309 
310     /** Flag to know if the button is currently pushed.  */
311     isPushed:function () {
312         return this._isPushed;
313     },
314 
315     /* Define the button margin for Top/Bottom edge */
316     _getVerticalMargin:function () {
317         return this._marginV;
318     },
319     /* Define the button margin for Left/Right edge */
320     _getHorizontalOrigin:function () {
321         return this._marginH;
322     },
323 
324     /**
325      * set the margins at once (so we only have to do one call of needsLayout)
326      * @param {Number} marginH
327      * @param {Number} marginV
328      */
329     setMargins:function (marginH, marginV) {
330         this._marginV = marginV;
331         this._marginH = marginH;
332         this.needsLayout();
333     },
334 
335     setEnabled:function (enabled) {
336         cc.Control.prototype.setEnabled.call(this, enabled);
337         this.needsLayout();
338     },
339     setSelected:function (enabled) {
340         cc.Control.prototype.setSelected.call(this, enabled);
341         this.needsLayout();
342     },
343 
344     setHighlighted:function (enabled) {
345         this._state = enabled?cc.CONTROL_STATE_HIGHLIGHTED:cc.CONTROL_STATE_NORMAL;
346 
347         cc.Control.prototype.setHighlighted.call(this, enabled);
348         var action = this.getActionByTag(cc.CONTROL_ZOOM_ACTION_TAG);
349         if (action)
350             this.stopAction(action);
351 
352         this.needsLayout();
353         if (this._zoomOnTouchDown) {
354             var scaleValue = (this.isHighlighted() && this.isEnabled() && !this.isSelected()) ? 1.1 : 1.0;
355             var zoomAction = cc.ScaleTo.create(0.05, scaleValue);
356             zoomAction.setTag(cc.CONTROL_ZOOM_ACTION_TAG);
357             this.runAction(zoomAction);
358         }
359     },
360 
361     onTouchBegan:function (touch, event) {
362         if (!this.isTouchInside(touch) || !this.isEnabled()|| !this.isVisible()||!this.hasVisibleParents())
363             return false;
364 
365         this._isPushed = true;
366         this.setHighlighted(true);
367         this.sendActionsForControlEvents(cc.CONTROL_EVENT_TOUCH_DOWN);
368         return true;
369     },
370 
371     onTouchMoved:function (touch, event) {
372         if (!this._enabled || !this._isPushed || this._selected) {
373             if (this._highlighted)
374                 this.setHighlighted(false);
375             return;
376         }
377 
378         var isTouchMoveInside = this.isTouchInside(touch);
379         if (isTouchMoveInside && !this._highlighted) {
380             this.setHighlighted(true);
381             this.sendActionsForControlEvents(cc.CONTROL_EVENT_TOUCH_DRAG_ENTER);
382         } else if (isTouchMoveInside && this._highlighted) {
383             this.sendActionsForControlEvents(cc.CONTROL_EVENT_TOUCH_DRAG_INSIDE);
384         } else if (!isTouchMoveInside && this._highlighted) {
385             this.setHighlighted(false);
386             this.sendActionsForControlEvents(cc.CONTROL_EVENT_TOUCH_DRAG_EXIT);
387         } else if (!isTouchMoveInside && !this._highlighted) {
388             this.sendActionsForControlEvents(cc.CONTROL_EVENT_TOUCH_DRAG_OUTSIDE);
389         }
390     },
391     onTouchEnded:function (touch, event) {
392         this._isPushed = false;
393         this.setHighlighted(false);
394 
395         if (this.isTouchInside(touch)) {
396             this.sendActionsForControlEvents(cc.CONTROL_EVENT_TOUCH_UP_INSIDE);
397         } else {
398             this.sendActionsForControlEvents(cc.CONTROL_EVENT_TOUCH_UP_OUTSIDE);
399         }
400     },
401 
402     onTouchCancelled:function (touch, event) {
403         this._isPushed = false;
404         this.setHighlighted(false);
405         this.sendActionsForControlEvents(cc.CONTROL_EVENT_TOUCH_CANCEL);
406     },
407 
408     /**
409      * Returns the title used for a state.
410      *
411      * @param {Number} state The state that uses the title. Possible values are described in "CCControlState".
412      * @return {string} The title for the specified state.
413      */
414     getTitleForState:function (state) {
415         var locTable = this._titleDispatchTable;
416         if (locTable) {
417             if (locTable[state])
418                 return locTable[state];
419             return locTable[cc.CONTROL_STATE_NORMAL];
420         }
421         return "";
422     },
423 
424     /**
425      * <p>
426      * Sets the title string to use for the specified state.                                                  <br/>
427      * If a property is not specified for a state, the default is to use the CCButtonStateNormal value.
428      * </p>
429      * @param {string} title The title string to use for the specified state.
430      * @param {Number} state The state that uses the specified title. The values are described in "CCControlState".
431      */
432     setTitleForState:function (title, state) {
433         this._titleDispatchTable[state] = title || "";
434 
435         // If the current state if equal to the given state we update the layout
436         if (this.getState() == state)
437             this.needsLayout();
438     },
439 
440     /**
441      * Returns the title color used for a state.
442      *
443      * @param {Number} state The state that uses the specified color. The values are described in "CCControlState".
444      * @return {cc.Color3B} The color of the title for the specified state.
445      */
446     getTitleColorForState: function (state) {
447         var colorObject = this._titleColorDispatchTable[state];
448         if (colorObject)
449             return colorObject;
450         colorObject = this._titleColorDispatchTable[cc.CONTROL_STATE_NORMAL];
451         if (colorObject)
452             return colorObject;
453         return cc.white();
454     },
455 
456     /**
457      * Sets the color of the title to use for the specified state.
458      *
459      * @param {cc.Color3B} color The color of the title to use for the specified state.
460      * @param {Number} state The state that uses the specified color. The values are described in "CCControlState".
461      */
462     setTitleColorForState:function (color, state) {
463         //ccColor3B* colorValue=&color;
464         this._titleColorDispatchTable[state] = color;
465 
466         // If the current state if equal to the given state we update the layout
467         if (this.getState() == state)
468             this.needsLayout();
469     },
470 
471     /**
472      * Returns the title label used for a state.
473      *
474      * @param state The state that uses the title label. Possible values are described in "CCControlState".
475      * @return {cc.Node} the title label used for a state.
476      */
477     getTitleLabelForState:function (state) {
478         var locTable = this._titleLabelDispatchTable;
479         if (locTable.hasOwnProperty(state) && locTable[state])
480             return locTable[state];
481 
482         return locTable[cc.CONTROL_STATE_NORMAL];
483     },
484 
485     /**
486      * <p>Sets the title label to use for the specified state.                                          <br/>
487      * If a property is not specified for a state, the default is to use the CCButtonStateNormal value. </p>
488      *
489      * @param {cc.Node} titleLabel The title label to use for the specified state.
490      * @param {Number} state The state that uses the specified title. The values are described in "CCControlState".
491      */
492     setTitleLabelForState:function (titleLabel, state) {
493         var locTable = this._titleLabelDispatchTable;
494         if (locTable.hasOwnProperty(state)) {
495             var previousLabel = locTable[state];
496             if (previousLabel)
497                 this.removeChild(previousLabel, true);
498         }
499 
500         locTable[state] = titleLabel;
501         titleLabel.setVisible(false);
502         titleLabel.setAnchorPoint(0.5, 0.5);
503         this.addChild(titleLabel, 1);
504 
505         // If the current state if equal to the given state we update the layout
506         if (this.getState() == state)
507             this.needsLayout();
508     },
509 
510     /**
511      * Sets the title TTF filename to use for the specified state.
512      * @param {string} fntFile
513      * @param {Number} state
514      */
515     setTitleTTFForState:function (fntFile, state) {
516         var title = this.getTitleForState(state);
517         if (!title)
518             title = "";
519         this.setTitleLabelForState(cc.LabelTTF.create(title, fntFile, 12), state);
520     },
521 
522     /**
523      * return the title TTF filename to use for the specified state.
524      * @param {Number} state
525      * @returns {string}
526      */
527     getTitleTTFForState:function (state) {
528         var labelTTF = this.getTitleLabelForState(state);
529         if ((labelTTF != null) && (labelTTF instanceof  cc.LabelTTF)) {
530             return labelTTF.getFontName();
531         } else {
532             return "";
533         }
534     },
535 
536     /**
537      * @param {Number} size
538      * @param {Number} state
539      */
540     setTitleTTFSizeForState:function (size, state) {
541         var labelTTF = this.getTitleLabelForState(state);
542         if ((labelTTF != null) && (labelTTF instanceof  cc.LabelTTF)) {
543             labelTTF.setFontSize(size);
544         }
545     },
546 
547     /**
548      * return the font size of LabelTTF to use for the specified state
549      * @param {Number} state
550      * @returns {Number}
551      */
552     getTitleTTFSizeForState:function (state) {
553         var labelTTF = this.getTitleLabelForState(state);
554         if ((labelTTF != null) && (labelTTF instanceof  cc.LabelTTF)) {
555             return labelTTF.getFontSize();
556         }
557         return 0;
558     },
559 
560     /**
561      * Sets the font of the label, changes the label to a CCLabelBMFont if necessary.
562      * @param {string} fntFile The name of the font to change to
563      * @param {Number} state The state that uses the specified fntFile. The values are described in "CCControlState".
564      */
565     setTitleBMFontForState:function (fntFile, state) {
566         var title = this.getTitleForState(state);
567         if (!title)
568             title = "";
569         this.setTitleLabelForState(cc.LabelBMFont.create(title, fntFile), state);
570     },
571 
572     getTitleBMFontForState:function (state) {
573         var labelBMFont = this.getTitleLabelForState(state);
574         if ((labelBMFont != null) && (labelBMFont instanceof  cc.LabelBMFont)) {
575             return labelBMFont.getFntFile();
576         }
577         return "";
578     },
579 
580     /**
581      * Returns the background sprite used for a state.
582      *
583      * @param {Number} state The state that uses the background sprite. Possible values are described in "CCControlState".
584      */
585     getBackgroundSpriteForState:function (state) {
586         var locTable = this._backgroundSpriteDispatchTable;
587         if (locTable.hasOwnProperty(state) && locTable[state]) {
588             return locTable[state];
589         }
590         return locTable[cc.CONTROL_STATE_NORMAL];
591     },
592 
593     /**
594      * Sets the background sprite to use for the specified button state.
595      *
596      * @param {Scale9Sprite} sprite The background sprite to use for the specified state.
597      * @param {Number} state The state that uses the specified image. The values are described in "CCControlState".
598      */
599     setBackgroundSpriteForState:function (sprite, state) {
600         var locTable = this._backgroundSpriteDispatchTable;
601         if (locTable.hasOwnProperty(state)) {
602             var previousSprite = locTable[state];
603             if (previousSprite)
604                 this.removeChild(previousSprite, true);
605         }
606 
607         locTable[state] = sprite;
608         sprite.setVisible(false);
609         sprite.setAnchorPoint(0.5, 0.5);
610         this.addChild(sprite);
611 
612         var locPreferredSize = this._preferredSize;
613         if (locPreferredSize.width !== 0 || locPreferredSize.height !== 0) {
614             sprite.setPreferredSize(locPreferredSize);
615         }
616 
617         // If the current state if equal to the given state we update the layout
618         if (this._state === state)
619             this.needsLayout();
620     },
621 
622     /**
623      * Sets the background spriteFrame to use for the specified button state.
624      *
625      * @param {SpriteFrame} spriteFrame The background spriteFrame to use for the specified state.
626      * @param {Number} state The state that uses the specified image. The values are described in "CCControlState".
627      */
628     setBackgroundSpriteFrameForState:function (spriteFrame, state) {
629         var sprite = cc.Scale9Sprite.createWithSpriteFrame(spriteFrame);
630         this.setBackgroundSpriteForState(sprite, state);
631     }
632 });
633 
634 cc.ControlButton.create = function(label, backgroundSprite) {
635     var controlButton;
636     if (arguments.length == 0) {
637         controlButton = new cc.ControlButton();
638         if (controlButton && controlButton.init()) {
639             return controlButton;
640         }
641         return null;
642     } else if (arguments.length == 1) {
643         controlButton = new cc.ControlButton();
644         controlButton.initWithBackgroundSprite(arguments[0]);
645     } else if (arguments.length == 2) {
646         controlButton = new cc.ControlButton();
647         controlButton.initWithLabelAndBackgroundSprite(label, backgroundSprite);
648     } else if (arguments.length == 3) {
649         controlButton = new cc.ControlButton();
650         controlButton.initWithTitleAndFontNameAndFontSize(arguments[0], arguments[1], arguments[2]);
651     }
652     return controlButton;
653 };
654 
655 
656