1 /**
  2 * DisplayObject by Grant Skinner. Dec 5, 2010
  3 * Visit http://easeljs.com/ for documentation, updates and examples.
  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 * DisplayObject is an abstract class that should not be constructed directly. Instead construct subclasses such as Sprite, Bitmap, and Shape.
 34 * @class DisplayObject is the base class for all display classes in the CanvasDisplay library. It defines the core properties and methods that are shared between all display objects. It should not be instantiated directly.
 35 */
 36 function DisplayObject() {
 37   this.initialize();
 38 this.prototype;
 39 
 40 /** Suppresses errors generated when using features like hitTest, onPress/onClick, and getObjectsUnderPoint with cross domain content. */
 41 DisplayObject.suppressCrossDomainErrors = false;
 42 
 43 /** @private */
 44 DisplayObject._hitTestCanvas = document.createElement("canvas");
 45 DisplayObject._hitTestCanvas.width = DisplayObject._hitTestCanvas.height = 1;
 46 /** @private */
 47 DisplayObject._hitTestContext = DisplayObject._hitTestCanvas.getContext("2d");
 48 /** @private */
 49 DisplayObject._workingMatrix = new Matrix2D();
 50 
 51 // public properties:
 52 	/** The alpha (transparency) for this display object. 0 is fully transparent, 1 is fully opaque. */
 53 	this.alpha = 1;
 54 	/** If a cache is active, this returns the canvas that holds the cached version of this display object. See cache() for more information. READ-ONLY. */
 55 	this.cacheCanvas = null;
 56 	/** Unique ID for this display object. Makes display objects easier for some uses. */
 57 	this.id = -1;
 58 	/** Indicates whether to include this object when running Stage.getObjectsUnderPoint(). Setting this to true for Sprites will cause the Sprite to be returned (not its children) regardless of whether it's mouseChildren property is true. */
 59 	this.mouseEnabled = true;
 60 	/** An optional name for this display object. Included in toString(). Useful for debugging. */
 61 	this.name = null;
 62 	/** A reference to the Sprite or Stage object that contains this display object, or null if it has not been added to one. READ-ONLY. */
 63 	this.parent = null;
 64 	/** The x offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate around it's center, you would set regX and regY to 50. */
 65 	this.regX = 0;
 66 	/** The y offset for this display object's registration point. For example, to make a 100x100px Bitmap rotate around it's center, you would set regX and regY to 50. */
 67 	this.regY = 0;
 68 	/** The rotation in degrees for this display object. */
 69 	this.rotation = 0;
 70 	/** The factor to stretch this display object horizontally. For example, setting scaleX to 2 will stretch the display object to twice it's nominal width. */
 71 	this.scaleX = 1;
 72 	/** The factor to stretch this display object vertically. For example, setting scaleY to 0.5 will stretch the display object to half it's nominal height. */
 73 	this.scaleY = 1;
 74 	/** The factor to skew this display object horizontally. */
 75 	this.skewX = 0;
 76 	/** The factor to skew this display object vertically. */
 77 	this.skewY = 0;
 78 	/** A shadow object that defines the shadow to render on this display object. Set to null to remove a shadow. If null, this property is inherited from the parent container. */
 79 	this.shadow = null;
 80 	/** Indicates whether this display object should be rendered to the canvas and included when running Stage.getObjectsUnderPoint(). */
 81 	this.visible = true;
 82 	/** The x (horizontal) position of the display object, relative to its parent. */
 83 	this.x = 0;
 84 	/** The y (vertical) position of the display object, relative to its parent. */
 85 	this.y = 0;
 86 	/** The composite operation indicates how the pixels of this display object will be composited with the elements behind it. If null, this property is inherited from the parent container. For more information, read the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#compositing">whatwg spec on compositing</a>. */
 87 	this.compositeOperation = null;
 88 	/** Indicates whether the display object should have it's x & y position rounded prior to drawing it to stage. This only applies if the enclosing stage has snapPixelsEnabled set to true, and the display object's composite transform does not include any scaling, rotation, or skewing. The snapToPixel property is true by default for Bitmap and BitmapSequence instances, and false for all other display objects. */
 89 	this.snapToPixel = false;
 90 	/** The onPress callback is called when the user presses down on their mouse over this display object. The handler is passed a single param containing the corresponding MouseEvent instance. You can subscribe to the onMouseMove and onMouseUp callbacks of the event object to receive these events until the user releases the mouse button. If an onPress handler is set on a container, it will receive the event if any of its children are clicked. */
 91 	this.onPress = null;
 92 	/** The onClick callback is called when the user presses down on and then releases the mouse button over this display object. The handler is passed a single param containing the corresponding MouseEvent instance. If an onClick handler is set on a container, it will receive the event if any of its children are clicked. */
 93 	this.onClick = null;
 94 	
 95 // private properties:
 96 	/** @private */
 97 	this._cacheOffsetX = 0;
 98 	/** @private */
 99 	this._cacheOffsetY = 0;
100 	/** @private */
101 	this._cacheDraw = false;
102 	/** @private */
103 	this._activeContext = null;
104 	/** @private */
105 	this._restoreContext = false;
106 	/** @private */
107 	this._revertShadow = false;
108 	/** @private */
109 	this._revertX = 0;
110 	/** @private */
111 	this._revertY = 0;
112 	/** @private */
113 	this._revertAlpha = 1;
114 	
115 // constructor:
116 	// separated so it can be easily addressed in subclasses:
117 	/** @private */
118 	this.initialize = function() {
119 		this.id = UID.get();
120 		this.children = [];
121 	}
122 	
123 // public methods:
124 	/**
125 	 * NOTE: This method is mainly for internal use, though it may be useful for advanced developers.
126 	 * Returns true or falsee indicating whether the display object would be visible if drawn to a canvas.
127 	 * This does not account for whether it would be visible within the boundaries of the stage.
128 	 */
129 	this.isVisible = function() {
130 		return this.visible && this.alpha > 0 && this.scaleX != 0 && this.scaleY != 0;
131 	}
132 	
133 	/**
134 	 * NOTE: This method is mainly for internal use, though it may be useful for advanced developers.
135 	 * Draws the display object into the specified context ignoring it's visible, alpha, shadow, and transform.
136 	 * Returns true if the draw was handled (useful for overriding functionality).
137 	 * @param ctx The canvas 2D context object to draw into.
138 	 * @param ignoreCache Indicates whether the draw operation should ignore any current cache. For example,
139 	 * used for drawing the cache (to prevent it from simply drawing an existing cache back into itself).
140 	 */
141 	this.draw = function(ctx,ignoreCache) {
142 		if (ignoreCache || !this.cacheCanvas) { return false; }
143 		ctx.translate(this._cacheOffsetX,this._cacheOffsetY);
144 		ctx.drawImage(this.cacheCanvas,0,0);
145 		ctx.translate(-this._cacheOffsetX,-this._cacheOffsetY);
146 		return true;
147 	}
148 	
149 	/**
150 	* Draws the display object into a new canvas, which is then used for subsequent draws. For complex content that does not change frequently (ex. a Sprite with many children that do not move, or a complex vector Shape), this can provide for much faster rendering because the content does not need to be re-rendered each tick. The cached display object can be moved, rotated, faded, etc freely, however if it's content changes, you must manually update the cache by calling updateCache() or cache() again. You must specify the cache area via the x, y, w, and h parameters. This defines the rectangle that will be rendered and cached using this display object's coordinates. For example if you defined a Shape that drew a circle at 0,0 with a radius of 25, you could call myShape.cache(-25,-25,50,50) to cache the full shape.
151 	* @param x
152 	* @param y
153 	* @param width
154 	* @param height
155 	*/
156 	this.cache = function(x, y, width, height) {
157 		// draw to canvas.
158 		var ctx;
159 		if (this.cacheCanvas == null) { this.cacheCanvas = document.createElement("canvas"); }
160 		ctx = this.cacheCanvas.getContext("2d");
161 		this.cacheCanvas.width = width;
162 		this.cacheCanvas.height = height;
163 		ctx.setTransform(1,0,0,1,-x,-y);
164 		ctx.clearRect(0,0,width+1,height+1); // because some browsers don't correctly clear if the width/height remain the same.
165 		this.draw(ctx,true);
166 		this._cacheOffsetX = x;
167 		this._cacheOffsetY = y;
168 	}
169 
170 	/**
171 	 * Redraws the display object to its cache. Calling updateCache without an active cache will throw an error.
172 	 * If compositeOperation is null the current cache will be cleared prior to drawing. Otherwise the display object
173 	 * will be drawn over the existing cache using the specified compositeOperation.
174 	 * @param compositeOperation The compositeOperation to use, or null to clear the cache and redraw it. <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#compositing">whatwg spec on compositing</a>.
175 	 */
176 	this.updateCache = function(compositeOperation) {
177 		if (this.cacheCanvas == null) { throw "cache() must be called before updateCache()"; }
178 		ctx = this.cacheCanvas.getContext("2d");
179 		ctx.setTransform(1,0,0,1,-this._cacheOffsetX,-this._cacheOffsetY);
180 		if (!compositeOperation) { ctx.clearRect(0,0,this.cacheCanvas.width+1,this.cacheCanvas.height+1); }
181 		else { ctx.globalCompositeOperation = compositeOperation; }
182 		this.draw(ctx,true);
183 		if (compositeOperation) { ctx.globalCompositeOperation = "source-over"; }
184 	}
185 	
186 	/**
187 	* Clears the current cache. See cache() for more information.
188 	*/
189 	this.uncache = function() {
190 		this.cacheCanvas = null;
191 		this.cacheOffsetX = this.cacheOffsetY = 0;
192 	}
193 	
194 	/**
195 	* Returns the stage that this display object will be rendered on, or null if it has not been added to one.
196 	*/
197 	this.getStage = function() {
198 		var o = this;
199 		while (o.parent) {
200 			o = o.parent;
201 		}
202 		if (o instanceof Stage) { return o; }
203 		return null;
204 	}
205 
206 	/**
207 	* Transforms the specified x and y position from the coordinate space of the display object
208 	* to the global (stage) coordinate space. For example, this could be used to position an HTML label
209 	* over a specific point on a nested display object. Returns a Point instance with x and y properties
210 	* correlating to the transformed coordinates on the stage.
211 	* @param x The x position in the source display object to transform.
212 	* @param y The y position in the source display object to transform.
213 	*/
214 	this.localToGlobal = function(x, y) {
215 		var mtx = this.getConcatenatedMatrix();
216 		if (mtx == null) { return null; }
217 		mtx.append(1,0,0,1,x,y);
218 		return new Point(mtx.tx, mtx.ty);
219 	}
220 
221 	/**
222 	* Transforms the specified x and y position from the global (stage) coordinate space to the
223 	* coordinate space of the display object. For example, this could be used to determine
224 	* the current mouse position within the display object. Returns a Point instance with x and y properties
225 	* correlating to the transformed position in the display object's coordinate space.
226 	* @param x The x position on the stage to transform.
227 	* @param y The y position on the stage to transform.
228 	*/
229 	this.globalToLocal = function(x, y) {
230 		var mtx = this.getConcatenatedMatrix();
231 		if (mtx == null) { return null; }
232 		mtx.invert();
233 		mtx.append(1,0,0,1,x,y);
234 		return new Point(mtx.tx, mtx.ty);
235 	}
236 
237 	/**
238 	* Transforms the specified x and y position from the coordinate space of this display object to the
239 	* coordinate space of the target display object. Returns a Point instance with x and y properties
240 	* correlating to the transformed position in the target's coordinate space. Effectively the same as calling
241 	* var pt = this.localToGlobal(x, y); pt = target.globalToLocal(pt.x, pt.y);
242 	* @param x The x position in the source display object to transform.
243 	* @param y The y position on the stage to transform.
244 	* @param target The target display object to which the coordinates will be transformed.
245 	*/
246 	this.localToLocal = function(x, y, target) {
247 		var pt = this.localToGlobal(x, y);
248 		return target.globalToLocal(pt.x, pt.y);
249 	}
250 
251 	/**
252 	 * Generates a concatenated Matrix2D object representing the combined transform of
253 	 * the display object and all of its parent Containers up to the highest level ancestor
254 	 * (usually the stage). This can be used to transform positions between coordinate spaces,
255 	 * such as with localToGlobal and globalToLocal.
256 	 * @param mtx Optional. A Matrix2D object to populate with the calculated values. If null, a new Matrix object is returned.
257 	 */
258 	this.getConcatenatedMatrix = function(mtx) {
259 		if (mtx) { mtx.identity(); }
260 		else { mtx = new Matrix2D(); }
261 		var target = this;
262 		while (true) {
263 			mtx.prependTransform(target.x, target.y, target.scaleX, target.scaleY, target.rotation, target.skewX, target.skewY, target.regX, target.regY);
264 			mtx.prependProperties(target.alpha, target.shadow, target.compositeOperation);
265 			if ((p = target.parent) == null) { break; }
266 			target = p;
267 		}
268 		return mtx;
269 	}
270 
271 	/**
272 	 * Tests whether the display object intersects the specified local point (ie. draws a pixel with alpha > 0 at the specified position).
273 	 * This ignores the alpha, shadow and compositeOperation of the display object, and all transform properties including regX/Y.
274 	 * @param x The x position to check in the display object's local coordinates.
275 	 * @param y The y position to check in the display object's local coordinates.
276 	 */
277 	this.hitTest = function(x, y) {
278 		var ctx = DisplayObject._hitTestContext;
279 		var canvas = DisplayObject._hitTestCanvas;
280 
281 		ctx.setTransform(1,  0, 0, 1, -x, -y);
282 		this.draw(ctx);
283 		
284 		var hit = this._testHit(ctx);
285 		
286 		canvas.width = 0;
287 		canvas.width = 1;
288 		return hit;
289 	}
290 	
291 	/**
292 	* Returns a clone of this DisplayObject. Some properties that are specific to this instance's current context are reverted to their defaults (for example .parent).
293 	*/
294 	this.clone = function() {
295 		var o = new DisplayObject();
296 		this.cloneProps(o);
297 		return o;
298 	}
299 	
300 	/**
301 	* Returns a string representation of this object.
302 	*/
303 	this.toString = function() {
304 		return "[DisplayObject (name="+  this.name +")]";
305 	}
306 	
307 // private methods:
308 
309 	// separated so it can be used more easily in subclasses:
310 	/** @private */
311 	this.cloneProps = function(o) {
312 		o.alpha = this.alpha;
313 		o.name = this.name;
314 		o.regX = this.regX;
315 		o.regY = this.regY;
316 		o.rotation = this.rotation;
317 		o.scaleX = this.scaleX;
318 		o.scaleY = this.scaleY;
319 		o.shadow = this.shadow;
320 		o.skewX = this.skewX;
321 		o.skewY = this.skewY;
322 		o.visible = this.visible;
323 		o.x  = this.x;
324 		o.y = this.y;
325 		o.mouseEnabled = this.mouseEnabled;
326 		o.compositeOperation = this.compositeOperation;
327 	}
328 	
329 	/** @private */
330 	this.applyShadow = function(ctx, shadow) {
331 		ctx.shadowColor = shadow.color;
332 		ctx.shadowOffsetX = shadow.offsetX;
333 		ctx.shadowOffsetY = shadow.offsetY;
334 		ctx.shadowBlur = shadow.blur;
335 	}
336 
337 	/** @private */
338 	this._testHit = function(ctx) {
339 		try {
340 			var hit = ctx.getImageData(0,0,1,1).data[3] > 1;
341 		} catch (e) {
342 			if (!DisplayObject.suppressCrossDomainErrors) {
343 				throw "An error has occured. This is most likely due to security restrictions on reading canvas pixel data with local or cross-domain images.";
344 			}
345 		}
346 		return hit;
347 	}
348 }