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