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 }