1 /****************************************************************************
  2  Copyright (c) 2008-2010 Ricardo Quesada
  3  Copyright (c) 2011-2012 cocos2d-x.org
  4  Copyright (c) 2013-2014 Chukong Technologies Inc.
  5  Copyright (c) 2012 Neofect. All rights reserved.
  6 
  7  http://www.cocos2d-x.org
  8 
  9  Permission is hereby granted, free of charge, to any person obtaining a copy
 10  of this software and associated documentation files (the "Software"), to deal
 11  in the Software without restriction, including without limitation the rights
 12  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 13  copies of the Software, and to permit persons to whom the Software is
 14  furnished to do so, subject to the following conditions:
 15 
 16  The above copyright notice and this permission notice shall be included in
 17  all copies or substantial portions of the Software.
 18 
 19  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 20  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 21  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 22  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 23  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 24  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 25  THE SOFTWARE.
 26 
 27  Created by Jung Sang-Taik on 2012-03-16
 28  ****************************************************************************/
 29 
 30 /**
 31  * A 9-slice sprite for cocos2d.
 32  *
 33  * 9-slice scaling allows you to specify how scaling is applied
 34  * to specific areas of a sprite. With 9-slice scaling (3x3 grid),
 35  * you can ensure that the sprite does not become distorted when
 36  * scaled.
 37  *
 38  * @see http://yannickloriot.com/library/ios/cccontrolextension/Classes/CCScale9Sprite.html
 39  * @class
 40  * @extends cc.Node
 41  *
 42  * @property {cc.Size}  preferredSize   - The preferred size of the 9-slice sprite
 43  * @property {cc.Rect}  capInsets       - The cap insets of the 9-slice sprite
 44  * @property {Number}   insetLeft       - The left inset of the 9-slice sprite
 45  * @property {Number}   insetTop        - The top inset of the 9-slice sprite
 46  * @property {Number}   insetRight      - The right inset of the 9-slice sprite
 47  * @property {Number}   insetBottom     - The bottom inset of the 9-slice sprite
 48  */
 49 cc.Scale9Sprite = cc.Node.extend(/** @lends cc.Scale9Sprite# */{
 50     _spriteRect: null,
 51     _capInsetsInternal: null,
 52     _positionsAreDirty: false,
 53 
 54     _scale9Image: null,
 55     _topLeft: null,
 56     _top: null,
 57     _topRight: null,
 58     _left: null,
 59     _centre: null,
 60     _right: null,
 61     _bottomLeft: null,
 62     _bottom: null,
 63     _bottomRight: null,
 64 
 65     //cache in canvas on Canvas mode
 66     _cacheSprite: null,
 67     _cacheCanvas: null,
 68     _cacheContext: null,
 69     _cacheTexture: null,
 70     _scale9Dirty: true,
 71 
 72     _opacityModifyRGB: false,
 73 
 74     _originalSize: null,
 75     _preferredSize: null,
 76     _opacity: 0,
 77     _color: null,
 78     _capInsets: null,
 79     _insetLeft: 0,
 80     _insetTop: 0,
 81     _insetRight: 0,
 82     _insetBottom: 0,
 83 
 84     _spritesGenerated: false,
 85     _spriteFrameRotated: false,
 86     _textureLoaded:false,
 87     _loadedEventListeners: null,
 88     _className:"Scale9Sprite",
 89 
 90     /**
 91      * return  texture is loaded
 92      * @returns {boolean}
 93      */
 94     textureLoaded:function(){
 95         return this._textureLoaded;
 96     },
 97 
 98     /**
 99      * add texture loaded event listener
100      * @param {Function} callback
101      * @param {Object} target
102      */
103     addLoadedEventListener:function(callback, target){
104         this._loadedEventListeners.push({eventCallback:callback, eventTarget:target});
105     },
106 
107     _callLoadedEventCallbacks:function(){
108         this._textureLoaded = true;
109         var locListeners = this._loadedEventListeners;
110         for(var i = 0, len = locListeners.length;  i < len; i++){
111             var selCallback = locListeners[i];
112             selCallback.eventCallback.call(selCallback.eventTarget, this);
113         }
114         locListeners.length = 0;
115     },
116 
117     _updateCapInset: function () {
118         var insets, locInsetLeft = this._insetLeft, locInsetTop = this._insetTop, locInsetRight = this._insetRight;
119         var locSpriteRect = this._spriteRect, locInsetBottom = this._insetBottom;
120         if (locInsetLeft === 0 && locInsetTop === 0 && locInsetRight === 0 && locInsetBottom === 0) {
121             insets = cc.rect(0, 0, 0, 0);
122         } else {
123             insets = this._spriteFrameRotated ? cc.rect(locInsetBottom, locInsetLeft,
124                     locSpriteRect.width - locInsetRight - locInsetLeft,
125                     locSpriteRect.height - locInsetTop - locInsetBottom) :
126                 cc.rect(locInsetLeft, locInsetTop,
127                         locSpriteRect.width - locInsetLeft - locInsetRight,
128                         locSpriteRect.height - locInsetTop - locInsetBottom);
129         }
130         this.setCapInsets(insets);
131     },
132 
133     _updatePositions: function () {
134         // Check that instances are non-NULL
135         if (!((this._topLeft) && (this._topRight) && (this._bottomRight) &&
136             (this._bottomLeft) && (this._centre))) {
137             // if any of the above sprites are NULL, return
138             return;
139         }
140 
141         var size = this._contentSize;
142         var locTopLeft = this._topLeft, locTopRight = this._topRight, locBottomRight = this._bottomRight, locBottomLeft = this._bottomLeft;
143         var locCenter = this._centre, locCenterContentSize = this._centre.getContentSize();
144         var locTopLeftContentSize = locTopLeft.getContentSize();
145         var locBottomLeftContentSize = locBottomLeft.getContentSize();
146 
147         var sizableWidth = size.width - locTopLeftContentSize.width - locTopRight.getContentSize().width;
148         var sizableHeight = size.height - locTopLeftContentSize.height - locBottomRight.getContentSize().height;
149 
150         var horizontalScale = sizableWidth / locCenterContentSize.width;
151         var verticalScale = sizableHeight / locCenterContentSize.height;
152 
153         var rescaledWidth = locCenterContentSize.width * horizontalScale;
154         var rescaledHeight = locCenterContentSize.height * verticalScale;
155 
156         var leftWidth = locBottomLeftContentSize.width;
157         var bottomHeight = locBottomLeftContentSize.height;
158 
159         if (cc._renderType == cc._RENDER_TYPE_WEBGL) {
160             //browser is in canvas mode, need to manually control rounding to prevent overlapping pixels
161             var roundedRescaledWidth = Math.round(rescaledWidth);
162             if (rescaledWidth != roundedRescaledWidth) {
163                 rescaledWidth = roundedRescaledWidth;
164                 horizontalScale = rescaledWidth / locCenterContentSize.width;
165             }
166             var roundedRescaledHeight = Math.round(rescaledHeight);
167             if (rescaledHeight != roundedRescaledHeight) {
168                 rescaledHeight = roundedRescaledHeight;
169                 verticalScale = rescaledHeight / locCenterContentSize.height;
170             }
171         }
172 
173         locCenter.setScaleX(horizontalScale);
174         locCenter.setScaleY(verticalScale);
175 
176         var locLeft = this._left, locRight = this._right, locTop = this._top, locBottom = this._bottom;
177         var tempAP = cc.p(0, 0);
178         locBottomLeft.setAnchorPoint(tempAP);
179         locBottomRight.setAnchorPoint(tempAP);
180         locTopLeft.setAnchorPoint(tempAP);
181         locTopRight.setAnchorPoint(tempAP);
182         locLeft.setAnchorPoint(tempAP);
183         locRight.setAnchorPoint(tempAP);
184         locTop.setAnchorPoint(tempAP);
185         locBottom.setAnchorPoint(tempAP);
186         locCenter.setAnchorPoint(tempAP);
187 
188         // Position corners
189         locBottomLeft.setPosition(0, 0);
190         locBottomRight.setPosition(leftWidth + rescaledWidth, 0);
191         locTopLeft.setPosition(0, bottomHeight + rescaledHeight);
192         locTopRight.setPosition(leftWidth + rescaledWidth, bottomHeight + rescaledHeight);
193 
194         // Scale and position borders
195         locLeft.setPosition(0, bottomHeight);
196         locLeft.setScaleY(verticalScale);
197         locRight.setPosition(leftWidth + rescaledWidth, bottomHeight);
198         locRight.setScaleY(verticalScale);
199         locBottom.setPosition(leftWidth, 0);
200         locBottom.setScaleX(horizontalScale);
201         locTop.setPosition(leftWidth, bottomHeight + rescaledHeight);
202         locTop.setScaleX(horizontalScale);
203 
204         // Position centre
205         locCenter.setPosition(leftWidth, bottomHeight);
206     },
207 
208     _cacheScale9Sprite: function(){
209         if(!this._scale9Image)
210             return;
211         var size = this._contentSize, locCanvas = this._cacheCanvas;
212         var contentSizeChanged = false;
213         if(locCanvas.width != size.width || locCanvas.height != size.height){
214             locCanvas.width = size.width;
215             locCanvas.height = size.height;
216             this._cacheContext.translate(0, size.height);
217             contentSizeChanged = true;
218         }
219 
220         //cc._renderContext = this._cacheContext;
221         cc.view._setScaleXYForRenderTexture();
222         this._scale9Image.visit(this._cacheContext);
223         //cc._renderContext = cc._mainRenderContextBackup;
224         cc.view._resetScale();
225 
226         if(contentSizeChanged)
227             this._cacheSprite.setTextureRect(cc.rect(0,0, size.width, size.height));
228 
229         if(!this._cacheSprite.getParent())
230             this.addChild(this._cacheSprite);
231     },
232 
233     /**
234      * The constructor of cc.Scale9Sprite. Override it to extend the construction behavior, remember to call "this._super()" in the extended "ctor" function.
235      * @param {string|cc.SpriteFrame} file file name of texture or a SpriteFrame
236      * @param {cc.Rect} rect
237      * @param {cc.Rect} capInsets
238      * @returns {Scale9Sprite}
239      */
240     ctor: function (file, rect, capInsets) {
241         cc.Node.prototype.ctor.call(this);
242         this._spriteRect = cc.rect(0, 0, 0, 0);
243         this._capInsetsInternal = cc.rect(0, 0, 0, 0);
244 
245         this._originalSize = cc.size(0, 0);
246         this._preferredSize = cc.size(0, 0);
247         this._capInsets = cc.rect(0, 0, 0, 0);
248         this._loadedEventListeners = [];
249 
250         //cache
251         if(cc._renderType === cc._RENDER_TYPE_CANVAS){
252             var locCacheCanvas = this._cacheCanvas = cc.newElement('canvas');
253             locCacheCanvas.width = 1;
254             locCacheCanvas.height = 1;
255             this._cacheContext = locCacheCanvas.getContext("2d");
256             var locTexture = this._cacheTexture = new cc.Texture2D();
257             locTexture.initWithElement(locCacheCanvas);
258             locTexture.handleLoadedTexture();
259             this._cacheSprite = new cc.Sprite(locTexture);
260             this._cacheSprite.setAnchorPoint(0,0);
261             this.addChild(this._cacheSprite);
262         }
263 
264         if(file != undefined){
265             if(file instanceof cc.SpriteFrame)
266                 this.initWithSpriteFrame(file, rect);
267             else{
268                 var frame = cc.spriteFrameCache.getSpriteFrame(file);
269                 if(frame != null)
270                     this.initWithSpriteFrame(frame, rect);
271                 else
272                     this.initWithFile(file, rect, capInsets);
273             }
274         }else{
275             this.init();
276         }
277     },
278 
279     /** Original sprite's size. */
280     getOriginalSize: function () {
281         return cc.size(this._originalSize);
282     },
283 
284     //if the preferredSize component is given as -1, it is ignored
285     getPreferredSize: function () {
286         return cc.size(this._preferredSize);
287     },
288     _getPreferredWidth: function () {
289         return this._preferredSize.width;
290     },
291     _getPreferredHeight: function () {
292         return this._preferredSize.height;
293     },
294     setPreferredSize: function (preferredSize) {
295         this.setContentSize(preferredSize);
296         this._preferredSize = preferredSize;
297     },
298     _setPreferredWidth: function (value) {
299         this._setWidth(value);
300         this._preferredSize.width = value;
301     },
302     _setPreferredHeight: function (value) {
303         this._setHeight(value);
304         this._preferredSize.height = value;
305     },
306 
307     /** Opacity: conforms to CCRGBAProtocol protocol */
308     setOpacity: function (opacity) {
309         if(!this._scale9Image)
310             return;
311         cc.Node.prototype.setOpacity.call(this, opacity);
312         var scaleChildren = this._scale9Image.getChildren();
313         for (var i = 0; i < scaleChildren.length; i++) {
314             var selChild = scaleChildren[i];
315             if (selChild)
316                 selChild.setOpacity(opacity);
317         }
318         this._scale9Dirty = true;
319     },
320 
321     updateDisplayedOpacity: function(parentOpacity){
322         if(!this._scale9Image)
323             return;
324 
325         cc.Node.prototype.updateDisplayedOpacity.call(this, parentOpacity);
326         var scaleChildren = this._scale9Image.getChildren();
327         for (var i = 0; i < scaleChildren.length; i++) {
328             var selChild = scaleChildren[i];
329             if (selChild)
330                 selChild.updateDisplayedOpacity(parentOpacity);
331         }
332         this._scale9Dirty = true;
333     },
334 
335     /** Color: conforms to CCRGBAProtocol protocol */
336     setColor: function (color) {
337         if(!this._scale9Image)
338             return;
339 
340         cc.Node.prototype.setColor.call(this, color);
341         var scaleChildren = this._scale9Image.getChildren();
342         for (var i = 0; i < scaleChildren.length; i++) {
343             var selChild = scaleChildren[i];
344             if (selChild)
345                 selChild.setColor(color);
346         }
347         this._scale9Dirty = true;
348     },
349 
350     updateDisplayedColor: function(parentColor){
351         if(!this._scale9Image)
352             return;
353 
354         cc.Node.prototype.updateDisplayedColor.call(this, parentColor);
355         var scaleChildren = this._scale9Image.getChildren();
356         for (var i = 0; i < scaleChildren.length; i++) {
357             var selChild = scaleChildren[i];
358             if (selChild){
359                 if(cc._renderType === cc._RENDER_TYPE_CANVAS){
360                     cc.Node.prototype.updateDisplayedColor.call(selChild, parentColor);
361                     if(
362                         parentColor.r !== 255 ||
363                         parentColor.g !== 255 ||
364                         parentColor.b !== 255
365                     ){
366                         selChild._changeTextureColor();
367                         selChild._setNodeDirtyForCache();
368                     }
369                 }else{
370                     selChild.updateDisplayedColor(parentColor);
371                 }
372             }
373         }
374         this._scale9Dirty = true;
375     },
376 
377     getCapInsets: function () {
378         return cc.rect(this._capInsets);
379     },
380 
381     setCapInsets: function (capInsets) {
382         if(!this._scale9Image)
383             return;
384         //backup the contentSize
385         var contentSize = this._contentSize;
386         var tempWidth = contentSize.width, tempHeight = contentSize.height;
387 
388         this.updateWithBatchNode(this._scale9Image, this._spriteRect, this._spriteFrameRotated, capInsets);
389         //restore the contentSize
390         this.setContentSize(tempWidth, tempHeight);
391     },
392 
393     /**
394      * Gets the left side inset
395      * @returns {number}
396      */
397     getInsetLeft: function () {
398         return this._insetLeft;
399     },
400 
401     /**
402      * Sets the left side inset
403      * @param {Number} insetLeft
404      */
405     setInsetLeft: function (insetLeft) {
406         this._insetLeft = insetLeft;
407         this._updateCapInset();
408     },
409 
410     /**
411      * Gets the top side inset
412      * @returns {number}
413      */
414     getInsetTop: function () {
415         return this._insetTop;
416     },
417 
418     /**
419      * Sets the top side inset
420      * @param {Number} insetTop
421      */
422     setInsetTop: function (insetTop) {
423         this._insetTop = insetTop;
424         this._updateCapInset();
425     },
426 
427     /**
428      * Gets the right side inset
429      * @returns {number}
430      */
431     getInsetRight: function () {
432         return this._insetRight;
433     },
434     /**
435      * Sets the right side inset
436      * @param {Number} insetRight
437      */
438     setInsetRight: function (insetRight) {
439         this._insetRight = insetRight;
440         this._updateCapInset();
441     },
442 
443     /**
444      * Gets the bottom side inset
445      * @returns {number}
446      */
447     getInsetBottom: function () {
448         return this._insetBottom;
449     },
450     /**
451      * Sets the bottom side inset
452      * @param {number} insetBottom
453      */
454     setInsetBottom: function (insetBottom) {
455         this._insetBottom = insetBottom;
456         this._updateCapInset();
457     },
458 
459     /**
460      * Sets the untransformed size of the Scale9Sprite.
461      * @override
462      * @param {cc.Size|Number} size The untransformed size of the Scale9Sprite or The untransformed size's width of the Scale9Sprite.
463      * @param {Number} [height] The untransformed size's height of the Scale9Sprite.
464      */
465     setContentSize: function (size, height) {
466         cc.Node.prototype.setContentSize.call(this, size, height);
467         this._positionsAreDirty = true;
468     },
469 
470     _setWidth: function (value) {
471         cc.Node.prototype._setWidth.call(this, value);
472         this._positionsAreDirty = true;
473     },
474 
475     _setHeight: function (value) {
476         cc.Node.prototype._setHeight.call(this, value);
477         this._positionsAreDirty = true;
478     },
479 
480     visit: function (ctx) {
481         if (this._positionsAreDirty) {
482             this._updatePositions();
483             this._positionsAreDirty = false;
484             this._scale9Dirty = true;
485         }
486         if(this._scale9Dirty && cc._renderType === cc._RENDER_TYPE_CANVAS){
487             this._scale9Dirty = false;
488             this._cacheScale9Sprite();
489         }
490         cc.Node.prototype.visit.call(this, ctx);
491     },
492 
493     init: function () {
494         return this.initWithBatchNode(null, cc.rect(0, 0, 0, 0), false, cc.rect(0, 0, 0, 0));
495     },
496 
497     /**
498      * Initializes a 9-slice sprite with a SpriteBatchNode.
499      * @param {cc.SpriteBatchNode} batchNode
500      * @param {cc.Rect} rect
501      * @param {boolean|cc.Rect} rotated
502      * @param {cc.Rect} [capInsets]
503      * @returns {boolean}
504      */
505     initWithBatchNode: function (batchNode, rect, rotated, capInsets) {
506         if (capInsets === undefined) {
507             capInsets = rotated;
508             rotated = false;
509         }
510 
511         if (batchNode)
512             this.updateWithBatchNode(batchNode, rect, rotated, capInsets);
513         this.setCascadeColorEnabled(true);
514         this.setCascadeOpacityEnabled(true);
515         this.setAnchorPoint(0.5, 0.5);
516         this._positionsAreDirty = true;
517         return true;
518     },
519 
520     /**
521      * Initializes a 9-slice sprite with a texture file, a delimitation zone and
522      * with the specified cap insets.
523      * Once the sprite is created, you can then call its "setContentSize:" method
524      * to resize the sprite will all it's 9-slice goodness intact.
525      * It respects the anchorPoint too.
526      *
527      * @param {String} file The name of the texture file.
528      * @param {cc.Rect} rect The rectangle that describes the sub-part of the texture that
529      * is the whole image. If the shape is the whole texture, set this to the texture's full rect.
530      * @param {cc.Rect} capInsets The values to use for the cap insets.
531      */
532     initWithFile: function (file, rect, capInsets) {
533         if (file instanceof cc.Rect) {
534             file = arguments[1];
535             capInsets = arguments[0];
536             rect = cc.rect(0, 0, 0, 0);
537         } else {
538             rect = rect || cc.rect(0, 0, 0, 0);
539             capInsets = capInsets || cc.rect(0, 0, 0, 0);
540         }
541 
542         if(!file)
543             throw "cc.Scale9Sprite.initWithFile(): file should be non-null";
544 
545         var texture = cc.textureCache.getTextureForKey(file);
546         if (!texture) {
547             texture = cc.textureCache.addImage(file);
548         }
549 
550         var locLoaded = texture.isLoaded();
551         this._textureLoaded = locLoaded;
552         if(!locLoaded){
553             texture.addLoadedEventListener(function(sender){
554                 // the texture is rotated on Canvas render mode, so isRotated always is false.
555                 var preferredSize = this._preferredSize;
556                 preferredSize = cc.size(preferredSize.width, preferredSize.height);
557                 var size  = sender.getContentSize();
558                 this.updateWithBatchNode(this._scale9Image, cc.rect(0,0,size.width,size.height), false, this._capInsets);
559                 this.setPreferredSize(preferredSize);
560                 this._positionsAreDirty = true;
561                 this._callLoadedEventCallbacks();
562             }, this);
563         }
564 
565         return this.initWithBatchNode(cc.SpriteBatchNode.create(file, 9), rect, false, capInsets);
566     },
567 
568     /**
569      * Initializes a 9-slice sprite with an sprite frame and with the specified
570      * cap insets.
571      * Once the sprite is created, you can then call its "setContentSize:" method
572      * to resize the sprite will all it's 9-slice goodness interact.
573      * It respects the anchorPoint too.
574      *
575      * @param spriteFrame The sprite frame object.
576      * @param capInsets The values to use for the cap insets.
577      */
578     initWithSpriteFrame: function (spriteFrame, capInsets) {
579         if(!spriteFrame || !spriteFrame.getTexture())
580             throw "cc.Scale9Sprite.initWithSpriteFrame(): spriteFrame should be non-null and its texture should be non-null";
581 
582         capInsets = capInsets || cc.rect(0, 0, 0, 0);
583         var locLoaded = spriteFrame.textureLoaded();
584         this._textureLoaded = locLoaded;
585         if(!locLoaded){
586             spriteFrame.addLoadedEventListener(function(sender){
587                 // the texture is rotated on Canvas render mode, so isRotated always is false.
588                 var preferredSize = this._preferredSize;
589                 preferredSize = cc.size(preferredSize.width, preferredSize.height);
590                 this.updateWithBatchNode(this._scale9Image, sender.getRect(), cc._renderType == cc._RENDER_TYPE_WEBGL && sender.isRotated(), this._capInsets);
591                 this.setPreferredSize(preferredSize);
592                 this._positionsAreDirty = true;
593                 this._callLoadedEventCallbacks();
594             },this);
595         }
596         var batchNode = cc.SpriteBatchNode.create(spriteFrame.getTexture(), 9);
597         // the texture is rotated on Canvas render mode, so isRotated always is false.
598         return this.initWithBatchNode(batchNode, spriteFrame.getRect(), cc._renderType == cc._RENDER_TYPE_WEBGL && spriteFrame.isRotated(), capInsets);
599     },
600 
601     /**
602      * Initializes a 9-slice sprite with an sprite frame name and with the specified
603      * cap insets.
604      * Once the sprite is created, you can then call its "setContentSize:" method
605      * to resize the sprite will all it's 9-slice goodness interact.
606      * It respects the anchorPoint too.
607      *
608      * @param spriteFrameName The sprite frame name.
609      * @param capInsets The values to use for the cap insets.
610      */
611     initWithSpriteFrameName: function (spriteFrameName, capInsets) {
612         if(!spriteFrameName)
613             throw "cc.Scale9Sprite.initWithSpriteFrameName(): spriteFrameName should be non-null";
614         capInsets = capInsets || cc.rect(0, 0, 0, 0);
615 
616         var frame = cc.spriteFrameCache.getSpriteFrame(spriteFrameName);
617         if (frame == null) {
618             cc.log("cc.Scale9Sprite.initWithSpriteFrameName(): can't find the sprite frame by spriteFrameName");
619             return false;
620         }
621 
622         return this.initWithSpriteFrame(frame, capInsets);
623     },
624 
625     /**
626      * Creates and returns a new sprite object with the specified cap insets.
627      * You use this method to add cap insets to a sprite or to change the existing
628      * cap insets of a sprite. In both cases, you get back a new image and the
629      * original sprite remains untouched.
630      *
631      * @param {cc.Rect} capInsets The values to use for the cap insets.
632      */
633     resizableSpriteWithCapInsets: function (capInsets) {
634         var pReturn = new cc.Scale9Sprite();
635         if (pReturn && pReturn.initWithBatchNode(this._scale9Image, this._spriteRect, false, capInsets))
636             return pReturn;
637         return null;
638     },
639 
640     /** sets the premultipliedAlphaOpacity property.
641      If set to NO then opacity will be applied as: glColor(R,G,B,opacity);
642      If set to YES then opacity will be applied as: glColor(opacity, opacity, opacity, opacity );
643      Textures with premultiplied alpha will have this property by default on YES. Otherwise the default value is NO
644      @since v0.8
645      */
646     setOpacityModifyRGB: function (value) {
647         if(!this._scale9Image)
648             return;
649         this._opacityModifyRGB = value;
650         var scaleChildren = this._scale9Image.getChildren();
651         if (scaleChildren) {
652             for (var i = 0, len = scaleChildren.length; i < len; i++)
653                 scaleChildren[i].setOpacityModifyRGB(value);
654         }
655     },
656 
657     /** returns whether or not the opacity will be applied using glColor(R,G,B,opacity) or glColor(opacity, opacity, opacity, opacity);
658      @since v0.8
659      */
660     isOpacityModifyRGB: function () {
661         return this._opacityModifyRGB;
662     },
663 
664     /**
665      * Update the scale9Sprite with a SpriteBatchNode.
666      * @param {cc.SpriteBatchNode} batchNode
667      * @param {cc.Rect} originalRect
668      * @param {boolean} rotated
669      * @param {cc.Rect} capInsets
670      * @returns {boolean}
671      */
672     updateWithBatchNode: function (batchNode, originalRect, rotated, capInsets) {
673         var opacity = this.getOpacity();
674         var color = this.getColor();
675         var rect = cc.rect(originalRect.x, originalRect.y, originalRect.width, originalRect.height);
676 
677         // Release old sprites
678         this.removeAllChildren(true);
679 
680         if (this._scale9Image != batchNode)
681             this._scale9Image = batchNode;
682 
683         if(!this._scale9Image)
684             return false;
685 
686         var tmpTexture = batchNode.getTexture();
687         var locLoaded = tmpTexture.isLoaded();
688         this._textureLoaded = locLoaded;
689         if(!locLoaded){
690             tmpTexture.addLoadedEventListener(function(sender){
691                 this._positionsAreDirty = true;
692                 this._callLoadedEventCallbacks();
693             },this);
694             return true;
695         }
696         var locScale9Image = this._scale9Image;
697         locScale9Image.removeAllChildren(true);
698 
699         //this._capInsets = capInsets;
700         var locCapInsets = this._capInsets;
701         locCapInsets.x = capInsets.x;
702         locCapInsets.y = capInsets.y;
703         locCapInsets.width = capInsets.width;
704         locCapInsets.height = capInsets.height;
705         this._spriteFrameRotated = rotated;
706 
707         var selTexture = locScale9Image.getTexture();
708 
709         // If there is no given rect
710         if (cc._rectEqualToZero(rect)) {
711             // Get the texture size as original
712             var textureSize = selTexture.getContentSize();
713             rect = cc.rect(0, 0, textureSize.width, textureSize.height);
714         }
715 
716         // Set the given rect's size as original size
717         this._spriteRect = rect;
718         var locSpriteRect = this._spriteRect;
719         locSpriteRect.x = rect.x;
720         locSpriteRect.y = rect.y;
721         locSpriteRect.width = rect.width;
722         locSpriteRect.height = rect.height;
723 
724         this._originalSize.width = rect.width;
725         this._originalSize.height = rect.height;
726 
727         var locPreferredSize = this._preferredSize;
728         if(locPreferredSize.width === 0 && locPreferredSize.height === 0){
729             locPreferredSize.width = rect.width;
730             locPreferredSize.height = rect.height;
731         }
732 
733         var locCapInsetsInternal = this._capInsetsInternal;
734         if(capInsets){
735             locCapInsetsInternal.x = capInsets.x;
736             locCapInsetsInternal.y = capInsets.y;
737             locCapInsetsInternal.width = capInsets.width;
738             locCapInsetsInternal.height = capInsets.height;
739         }
740         var w = rect.width, h = rect.height;
741 
742         // If there is no specified center region
743         if (cc._rectEqualToZero(locCapInsetsInternal)) {
744             // CCLog("... cap insets not specified : using default cap insets ...");
745             locCapInsetsInternal.x = w / 3;
746             locCapInsetsInternal.y = h / 3;
747             locCapInsetsInternal.width = w / 3;
748             locCapInsetsInternal.height = h / 3;
749         }
750 
751         var left_w = locCapInsetsInternal.x, center_w = locCapInsetsInternal.width, right_w = w - (left_w + center_w);
752 
753         var top_h = locCapInsetsInternal.y, center_h = locCapInsetsInternal.height, bottom_h = h - (top_h + center_h);
754 
755         // calculate rects
756         // ... top row
757         var x = 0.0, y = 0.0;
758 
759         // top left
760         var lefttopbounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, left_w + 0.5 | 0, top_h + 0.5 | 0);
761 
762         // top center
763         x += left_w;
764         var centertopbounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, center_w + 0.5 | 0, top_h + 0.5 | 0);
765 
766         // top right
767         x += center_w;
768         var righttopbounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, right_w + 0.5 | 0, top_h + 0.5 | 0);
769 
770         // ... center row
771         x = 0.0;
772         y = 0.0;
773 
774         y += top_h;
775         // center left
776         var leftcenterbounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, left_w + 0.5 | 0, center_h + 0.5 | 0);
777 
778         // center center
779         x += left_w;
780         var centerbounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, center_w + 0.5 | 0, center_h + 0.5 | 0);
781 
782         // center right
783         x += center_w;
784         var rightcenterbounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, right_w + 0.5 | 0, center_h + 0.5 | 0);
785 
786         // ... bottom row
787         x = 0.0;
788         y = 0.0;
789         y += top_h;
790         y += center_h;
791 
792         // bottom left
793         var leftbottombounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, left_w + 0.5 | 0, bottom_h + 0.5 | 0);
794 
795         // bottom center
796         x += left_w;
797         var centerbottombounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, center_w + 0.5 | 0, bottom_h + 0.5 | 0);
798 
799         // bottom right
800         x += center_w;
801         var rightbottombounds = cc.rect(x + 0.5 | 0, y + 0.5 | 0, right_w + 0.5 | 0, bottom_h + 0.5 | 0);
802 
803         var t = cc.affineTransformMakeIdentity();
804         if (!rotated) {
805             // CCLog("!rotated");
806             t = cc.affineTransformTranslate(t, rect.x, rect.y);
807 
808             cc._rectApplyAffineTransformIn(centerbounds, t);
809             cc._rectApplyAffineTransformIn(rightbottombounds, t);
810             cc._rectApplyAffineTransformIn(leftbottombounds, t);
811             cc._rectApplyAffineTransformIn(righttopbounds, t);
812             cc._rectApplyAffineTransformIn(lefttopbounds, t);
813             cc._rectApplyAffineTransformIn(rightcenterbounds, t);
814             cc._rectApplyAffineTransformIn(leftcenterbounds, t);
815             cc._rectApplyAffineTransformIn(centerbottombounds, t);
816             cc._rectApplyAffineTransformIn(centertopbounds, t);
817 
818             // Centre
819             this._centre = new cc.Sprite();
820             this._centre.initWithTexture(selTexture, centerbounds);
821             locScale9Image.addChild(this._centre, 0, cc.Scale9Sprite.POSITIONS_CENTRE);
822 
823             // Top
824             this._top = new cc.Sprite();
825             this._top.initWithTexture(selTexture, centertopbounds);
826             locScale9Image.addChild(this._top, 1, cc.Scale9Sprite.POSITIONS_TOP);
827 
828             // Bottom
829             this._bottom = new cc.Sprite();
830             this._bottom.initWithTexture(selTexture, centerbottombounds);
831             locScale9Image.addChild(this._bottom, 1, cc.Scale9Sprite.POSITIONS_BOTTOM);
832 
833             // Left
834             this._left = new cc.Sprite();
835             this._left.initWithTexture(selTexture, leftcenterbounds);
836             locScale9Image.addChild(this._left, 1, cc.Scale9Sprite.POSITIONS_LEFT);
837 
838             // Right
839             this._right = new cc.Sprite();
840             this._right.initWithTexture(selTexture, rightcenterbounds);
841             locScale9Image.addChild(this._right, 1, cc.Scale9Sprite.POSITIONS_RIGHT);
842 
843             // Top left
844             this._topLeft = new cc.Sprite();
845             this._topLeft.initWithTexture(selTexture, lefttopbounds);
846             locScale9Image.addChild(this._topLeft, 2, cc.Scale9Sprite.POSITIONS_TOPLEFT);
847 
848             // Top right
849             this._topRight = new cc.Sprite();
850             this._topRight.initWithTexture(selTexture, righttopbounds);
851             locScale9Image.addChild(this._topRight, 2, cc.Scale9Sprite.POSITIONS_TOPRIGHT);
852 
853             // Bottom left
854             this._bottomLeft = new cc.Sprite();
855             this._bottomLeft.initWithTexture(selTexture, leftbottombounds);
856             locScale9Image.addChild(this._bottomLeft, 2, cc.Scale9Sprite.POSITIONS_BOTTOMLEFT);
857 
858             // Bottom right
859             this._bottomRight = new cc.Sprite();
860             this._bottomRight.initWithTexture(selTexture, rightbottombounds);
861             locScale9Image.addChild(this._bottomRight, 2, cc.Scale9Sprite.POSITIONS_BOTTOMRIGHT);
862         } else {
863             // set up transformation of coordinates
864             // to handle the case where the sprite is stored rotated
865             // in the spritesheet
866             // CCLog("rotated");
867             var rotatedcenterbounds = centerbounds;
868             var rotatedrightbottombounds = rightbottombounds;
869             var rotatedleftbottombounds = leftbottombounds;
870             var rotatedrighttopbounds = righttopbounds;
871             var rotatedlefttopbounds = lefttopbounds;
872             var rotatedrightcenterbounds = rightcenterbounds;
873             var rotatedleftcenterbounds = leftcenterbounds;
874             var rotatedcenterbottombounds = centerbottombounds;
875             var rotatedcentertopbounds = centertopbounds;
876 
877             t = cc.affineTransformTranslate(t, rect.height + rect.x, rect.y);
878             t = cc.affineTransformRotate(t, 1.57079633);
879 
880             centerbounds = cc.rectApplyAffineTransform(centerbounds, t);
881             rightbottombounds = cc.rectApplyAffineTransform(rightbottombounds, t);
882             leftbottombounds = cc.rectApplyAffineTransform(leftbottombounds, t);
883             righttopbounds = cc.rectApplyAffineTransform(righttopbounds, t);
884             lefttopbounds = cc.rectApplyAffineTransform(lefttopbounds, t);
885             rightcenterbounds = cc.rectApplyAffineTransform(rightcenterbounds, t);
886             leftcenterbounds = cc.rectApplyAffineTransform(leftcenterbounds, t);
887             centerbottombounds = cc.rectApplyAffineTransform(centerbottombounds, t);
888             centertopbounds = cc.rectApplyAffineTransform(centertopbounds, t);
889 
890             rotatedcenterbounds.x = centerbounds.x;
891             rotatedcenterbounds.y = centerbounds.y;
892 
893             rotatedrightbottombounds.x = rightbottombounds.x;
894             rotatedrightbottombounds.y = rightbottombounds.y;
895 
896             rotatedleftbottombounds.x = leftbottombounds.x;
897             rotatedleftbottombounds.y = leftbottombounds.y;
898 
899             rotatedrighttopbounds.x = righttopbounds.x;
900             rotatedrighttopbounds.y = righttopbounds.y;
901 
902             rotatedlefttopbounds.x = lefttopbounds.x;
903             rotatedlefttopbounds.y = lefttopbounds.y;
904 
905             rotatedrightcenterbounds.x = rightcenterbounds.x;
906             rotatedrightcenterbounds.y = rightcenterbounds.y;
907 
908             rotatedleftcenterbounds.x = leftcenterbounds.x;
909             rotatedleftcenterbounds.y = leftcenterbounds.y;
910 
911             rotatedcenterbottombounds.x = centerbottombounds.x;
912             rotatedcenterbottombounds.y = centerbottombounds.y;
913 
914             rotatedcentertopbounds.x = centertopbounds.x;
915             rotatedcentertopbounds.y = centertopbounds.y;
916 
917             // Centre
918             this._centre = new cc.Sprite();
919             this._centre.initWithTexture(selTexture, rotatedcenterbounds, true);
920             locScale9Image.addChild(this._centre, 0, cc.Scale9Sprite.POSITIONS_CENTRE);
921 
922             // Top
923             this._top = new cc.Sprite();
924             this._top.initWithTexture(selTexture, rotatedcentertopbounds, true);
925             locScale9Image.addChild(this._top, 1, cc.Scale9Sprite.POSITIONS_TOP);
926 
927             // Bottom
928             this._bottom = new cc.Sprite();
929             this._bottom.initWithTexture(selTexture, rotatedcenterbottombounds, true);
930             locScale9Image.addChild(this._bottom, 1, cc.Scale9Sprite.POSITIONS_BOTTOM);
931 
932             // Left
933             this._left = new cc.Sprite();
934             this._left.initWithTexture(selTexture, rotatedleftcenterbounds, true);
935             locScale9Image.addChild(this._left, 1, cc.Scale9Sprite.POSITIONS_LEFT);
936 
937             // Right
938             this._right = new cc.Sprite();
939             this._right.initWithTexture(selTexture, rotatedrightcenterbounds, true);
940             locScale9Image.addChild(this._right, 1, cc.Scale9Sprite.POSITIONS_RIGHT);
941 
942             // Top left
943             this._topLeft = new cc.Sprite();
944             this._topLeft.initWithTexture(selTexture, rotatedlefttopbounds, true);
945             locScale9Image.addChild(this._topLeft, 2, cc.Scale9Sprite.POSITIONS_TOPLEFT);
946 
947             // Top right
948             this._topRight = new cc.Sprite();
949             this._topRight.initWithTexture(selTexture, rotatedrighttopbounds, true);
950             locScale9Image.addChild(this._topRight, 2, cc.Scale9Sprite.POSITIONS_TOPRIGHT);
951 
952             // Bottom left
953             this._bottomLeft = new cc.Sprite();
954             this._bottomLeft.initWithTexture(selTexture, rotatedleftbottombounds, true);
955             locScale9Image.addChild(this._bottomLeft, 2, cc.Scale9Sprite.POSITIONS_BOTTOMLEFT);
956 
957             // Bottom right
958             this._bottomRight = new cc.Sprite();
959             this._bottomRight.initWithTexture(selTexture, rotatedrightbottombounds, true);
960             locScale9Image.addChild(this._bottomRight, 2, cc.Scale9Sprite.POSITIONS_BOTTOMRIGHT);
961         }
962 
963         this.setContentSize(rect.width, rect.height);
964         if(cc._renderType === cc._RENDER_TYPE_WEBGL)
965             this.addChild(locScale9Image);
966 
967         if (this._spritesGenerated) {
968             // Restore color and opacity
969             this.setOpacity(opacity);
970             this.setColor(color);
971         }
972         this._spritesGenerated = true;
973         return true;
974     },
975 
976     /**
977      * set the sprite frame of cc.Scale9Sprite
978      * @param {cc.SpriteFrame} spriteFrame
979      */
980     setSpriteFrame: function (spriteFrame) {
981         var batchNode = cc.SpriteBatchNode.create(spriteFrame.getTexture(), 9);
982         // the texture is rotated on Canvas render mode, so isRotated always is false.
983         var locLoaded = spriteFrame.textureLoaded();
984         this._textureLoaded = locLoaded;
985         if(!locLoaded){
986             spriteFrame.addLoadedEventListener(function(sender){
987                 // the texture is rotated on Canvas render mode, so isRotated always is false.
988                 var preferredSize = this._preferredSize;
989                 preferredSize = cc.size(preferredSize.width, preferredSize.height);
990                 this.updateWithBatchNode(this._scale9Image, sender.getRect(), cc._renderType == cc._RENDER_TYPE_WEBGL && sender.isRotated(), this._capInsets);
991                 this.setPreferredSize(preferredSize);
992                 this._positionsAreDirty = true;
993                 this._callLoadedEventCallbacks();
994             },this);
995         }
996         this.updateWithBatchNode(batchNode, spriteFrame.getRect(), cc._renderType == cc._RENDER_TYPE_WEBGL && spriteFrame.isRotated(), cc.rect(0, 0, 0, 0));
997 
998         // Reset insets
999         this._insetLeft = 0;
1000         this._insetTop = 0;
1001         this._insetRight = 0;
1002         this._insetBottom = 0;
1003     }
1004 });
1005 
1006 var _p = cc.Scale9Sprite.prototype;
1007 
1008 // Extended properties
1009 /** @expose */
1010 _p.preferredSize;
1011 cc.defineGetterSetter(_p, "preferredSize", _p.getPreferredSize, _p.setPreferredSize);
1012 /** @expose */
1013 _p.capInsets;
1014 cc.defineGetterSetter(_p, "capInsets", _p.getCapInsets, _p.setCapInsets);
1015 /** @expose */
1016 _p.insetLeft;
1017 cc.defineGetterSetter(_p, "insetLeft", _p.getInsetLeft, _p.setInsetLeft);
1018 /** @expose */
1019 _p.insetTop;
1020 cc.defineGetterSetter(_p, "insetTop", _p.getInsetTop, _p.setInsetTop);
1021 /** @expose */
1022 _p.insetRight;
1023 cc.defineGetterSetter(_p, "insetRight", _p.getInsetRight, _p.setInsetRight);
1024 /** @expose */
1025 _p.insetBottom;
1026 cc.defineGetterSetter(_p, "insetBottom", _p.getInsetBottom, _p.setInsetBottom);
1027 
1028 _p = null;
1029 
1030 /**
1031  * Creates a 9-slice sprite with a texture file, a delimitation zone and
1032  * with the specified cap insets.
1033  * @deprecated
1034  * @param {String|cc.SpriteFrame} file file name of texture or a cc.Sprite object
1035  * @param {cc.Rect} rect the rect of the texture
1036  * @param {cc.Rect} capInsets the cap insets of cc.Scale9Sprite
1037  * @returns {cc.Scale9Sprite}
1038  */
1039 cc.Scale9Sprite.create = function (file, rect, capInsets) {
1040     return new cc.Scale9Sprite(file, rect, capInsets);
1041 };
1042 
1043 /**
1044  * @deprecated
1045  * @param spriteFrame
1046  * @param capInsets
1047  * @returns {Scale9Sprite}
1048  */
1049 cc.Scale9Sprite.createWithSpriteFrame = function (spriteFrame, capInsets) {
1050     return new cc.Scale9Sprite(spriteFrame, capInsets);
1051 };
1052 
1053 /**
1054  * @deprecated
1055  * @param spriteFrameName
1056  * @param capInsets
1057  * @returns {Scale9Sprite}
1058  */
1059 cc.Scale9Sprite.createWithSpriteFrameName = function (spriteFrameName, capInsets) {
1060     return new cc.Scale9Sprite(spriteFrameName, capInsets);
1061 };
1062 
1063 /**
1064  * @ignore
1065  */
1066 cc.Scale9Sprite.POSITIONS_CENTRE = 0;
1067 cc.Scale9Sprite.POSITIONS_TOP = 1;
1068 cc.Scale9Sprite.POSITIONS_LEFT = 2;
1069 cc.Scale9Sprite.POSITIONS_RIGHT = 3;
1070 cc.Scale9Sprite.POSITIONS_BOTTOM = 4;
1071 cc.Scale9Sprite.POSITIONS_TOPRIGHT = 5;
1072 cc.Scale9Sprite.POSITIONS_TOPLEFT = 6;
1073 cc.Scale9Sprite.POSITIONS_BOTTOMRIGHT = 7;
1074