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