1 /** 2 * Sprite by Grant Skinner. Dec 5, 2010 3 * Visit www.gskinner.com/blog for documentation, updates and more free code. 4 * 5 * 6 * Copyright (c) 2010 Grant Skinner 7 * 8 * Permission is hereby granted, free of charge, to any person 9 * obtaining a copy of this software and associated documentation 10 * files (the "Software"), to deal in the Software without 11 * restriction, including without limitation the rights to use, 12 * copy, modify, merge, publish, distribute, sublicense, and/or sell 13 * copies of the Software, and to permit persons to whom the 14 * Software is furnished to do so, subject to the following 15 * conditions: 16 * 17 * The above copyright notice and this permission notice shall be 18 * included in all copies or substantial portions of the Software. 19 * 20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 * OTHER DEALINGS IN THE SOFTWARE. 28 **/ 29 30 31 32 /** 33 * Constructs a new Sprite instance. 34 * @class Sprites are nestable display lists that allow you to work with compound display elements. For example you could group arm, leg, torso and head Bitmaps together into a Person Sprite, and transform them as a group, while still being able to move the individual parts relative to each other. Children of sprites have their transform and alpha properties concatenated with their parent Sprite. For example, a Shape with x=100 and alpha=0.5, placed in a Sprite with x=50 and alpha=0.7 will be rendered to the canvas at x=150 and alpha=0.35. Sprites have some overhead, so you generally shouldn't create a Sprite to hold a single child. 35 * @augments DisplayObject 36 **/ 37 function Sprite() { 38 this.init(); 39 this.prototype = new DisplayObject(); 40 41 // public properties: 42 /** The array of children in the display list. You should usually use the child management methods, rather than accessing this directly, but it is included for advanced users. **/ 43 this.children = null; 44 /** Indicates whether the children of this Sprite should be tested in getObjectsUnderPoint() and getObjectUnderPoint() calls. **/ 45 this.mouseChildren = false; 46 47 // constructor: 48 /** @private **/ 49 this._init = this.init; 50 /** @private **/ 51 this.init = function() { 52 this._init(); 53 this.children = []; 54 } 55 56 // public methods: 57 /** @private **/ 58 this._draw = this.draw; 59 this.draw = function(ctx,ignoreCache) { 60 if (this.children.length == 0) { return false; } 61 if (!this._draw(ctx,ignoreCache)) { return false; } 62 var l=this.children.length; 63 // GDS: this fixes issues with display list changes that occur during a draw, but may have performance implications: 64 var list = this.children.slice(0); 65 for (var i=0; i<l; i++) { 66 var child = list[i]; 67 if (child == null) { continue; } 68 if (child.tick) { child.tick(); } 69 child.updateContext(ctx); 70 child.draw(ctx); 71 child.revertContext(); 72 } 73 } 74 75 /** 76 * Adds a child to the top of the display list. Returns the child that was added. 77 * @param child The display object to add. 78 **/ 79 this.addChild = function(child) { 80 if (child.parent && child.parent != this) { child.parent.removeChild(child); } 81 child.parent = this; 82 this.children.push(child); 83 return child; 84 } 85 86 /** 87 * Adds a child to the display list at the specified index, bumping children at equal or greater indexes up one, and setting its parent to this Sprite. The index must be between 0 and numChildren. For example, to add myShape under otherShape in the display list, you could use: sprite.addChildAt(myShape, sprite.getChildIndex(otherShape)). This would also bump otherShape's index up by one. Returns the child that was added. 88 * @param child The display object to add. 89 * @param index The index to add the child at. 90 **/ 91 this.addChildAt = function(child, index) { 92 if (child.parent && child.parent != this) { child.parent.removeChild(child); } 93 child.parent = this; 94 this.children.splice(index, 0, child); 95 return child; 96 } 97 98 /** 99 * Removes the specified child from the display list. Note that it is faster to use removeChildAt() if the index is already known. Returns true if the child was removed, or false if it was not in the display list. 100 * @param child The child to remove. 101 **/ 102 this.removeChild = function(child) { 103 return this.removeChildAt(this.children.indexOf(child)); 104 } 105 106 /** 107 * Removes all children from the display list. 108 **/ 109 this.removeAllChildren = function() { 110 while (this.children.length) { this.removeChildAt(0); } 111 } 112 113 /** 114 * Returns the child at the specified index. 115 * @param index The index of the child to return. 116 **/ 117 this.getChildAt = function(index) { 118 return this.children[index]; 119 } 120 121 // GDS: add example to docs: 122 /** 123 * Performs an array sort operation on the child list. 124 * @sortFunction the function to use to sort the child list. See javascript's Array.sort documentation for details. 125 **/ 126 this.sortChildren = function(sortFunction) { 127 this.children.sort(sortFunction); 128 } 129 130 /** 131 * Removes the child at the specified index from the display list, and sets its parent to null. Returns true if the child was removed, or false if the index was out of range. 132 * @param The index of the child to remove. 133 **/ 134 this.removeChildAt = function(index) { 135 if (index < 0 || index > this.children.length-1) { return false; } 136 var child = this.children[index]; 137 if (child != null) { child.parent = null; } 138 this.children.splice(index, 1); 139 return true; 140 } 141 142 /** 143 * Returns the index of the specified child in the display list, or -1 if it is not in the display list. 144 * @param The child to return the index of. 145 **/ 146 this.getChildIndex = function(child) { 147 return this.children.indexOf(child); 148 } 149 150 /** 151 * Returns the number of children in the display list. 152 **/ 153 this.getNumChildren = function() { 154 return this.children.length; 155 } 156 157 this.clone = function() { 158 var o = new Sprite(); 159 this.cloneProps(o); 160 return o; 161 } 162 163 this.toString = function() { 164 return "[Sprite (name="+ this.name +")]"; 165 } 166 167 // private properties: 168 /** @private **/ 169 this._getObjectsUnderPoint = function(x,y,ctx,arr) { 170 171 if (visible = false || ctx == null || !(this.mouseChildren||this.mouseEnabled) || this.children.length == 0) { return null; } 172 173 var canvas = ctx.canvas; 174 var w = canvas.width; 175 176 // if we have a cache handy, we can use it to do a quick check: 177 if (this.cacheCanvas) { 178 this._draw(ctx); 179 if (this._testHit(x,y,ctx)) { 180 canvas.width = 0; 181 canvas.width = w; 182 if (this.mouseEnabled) { 183 if (arr) { arr.push(this); } 184 return this; 185 } 186 } else { 187 return null; 188 } 189 } 190 191 // draw children one at a time, and check if we get a hit: 192 var a = ctx.globalAlpha; 193 var l = this.children.length; 194 for (var i=l-1;i>=0;i--) { 195 var child = this.children[i]; 196 if (child == null || !(child.mouseEnabled || this.mouseEnabled)) { continue; } 197 198 child.updateContext(ctx,true); 199 200 if (child instanceof Sprite) { 201 var result = child._getObjectsUnderPoint(x,y,ctx,mouseEnabled ? null : arr); 202 child.revertContext(); 203 if (mouseEnabled) { 204 result = child; 205 if (arr) { arr.push(result); } 206 } 207 if (result != null && arr == null) { return result; } 208 continue; 209 } 210 211 child.draw(ctx); 212 child.revertContext(); 213 if (!this._testHit(x,y,ctx)) { continue; } 214 canvas.width = 0; 215 canvas.width = w; 216 if (this.mouseEnabled) { 217 if (arr) { arr.push(this); } 218 return this; 219 } else if (arr) { arr.push(child); } 220 else { 221 return child; 222 } 223 } 224 return null; 225 } 226 227 /** @private **/ 228 this._testHit = function(x,y,ctx) { 229 try { 230 var hit = ctx.getImageData(x,y,1,1).data[3] > 1; 231 } catch (e) { 232 throw "An error has occured. This is likely due to security restrictions on using getObjectsUnderPoint on a canvas with local or cross-domain images."; 233 } 234 return hit; 235 } 236 237 /** @private **/ 238 this._cloneProps = this.cloneProps; 239 /** @private **/ 240 this.cloneProps = function(o) { 241 this._cloneProps(o); 242 o.mouseChildren = this.mouseChildren; 243 } 244 }