1 /**
  2 * DisplayObject 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 * 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.init();
 38 this.prototype;
 39 
 40 // public properties:
 41 	/** The alpha (transparency) for this display object. 0 is fully transparent, 1 is fully opaque. **/
 42 	this.alpha = 1;
 43 	/** 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. **/
 44 	this.cacheCanvas = null;
 45 	/** Unique ID for this display object. Makes display objects easier for some uses. **/
 46 	this.id = -1;
 47 	/** 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. **/
 48 	this.mouseEnabled = false;
 49 	/** An optional name for this display object. Included in toString(). Useful for debugging. **/
 50 	this.name = null;
 51 	/** 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. **/
 52 	this.parent = null;
 53 	/** 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. **/
 54 	this.regX = 0;
 55 	/** 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. **/
 56 	this.regY = 0;
 57 	/** The rotation in degrees for this display object. **/
 58 	this.rotation = 0;
 59 	/** 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. **/
 60 	this.scaleX = 1;
 61 	/** 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. **/
 62 	this.scaleY = 1;
 63 	/** A shadow object that defines the shadow to render on this display object. Set to null to remove a shadow. Note that nested shadows can result in unexpected behaviour (ex. if both a child and a parent have a shadow set). **/
 64 	this.shadow = null;
 65 	/** Indicates whether this display object should be rendered to the canvas and included when running Stage.getObjectsUnderPoint(). **/
 66 	this.visible = true;
 67 	/** The x (horizontal) position of the display object, relative to its parent. **/
 68 	this.x = 0;
 69 	/** The y (vertical) position of the display object, relative to its parent. **/
 70 	this.y = 0;
 71 	
 72 // private properties:
 73 	/** @private **/
 74 	this._cacheOffsetX = 0;
 75 	/** @private **/
 76 	this._cacheOffsetY = 0;
 77 	/** @private **/
 78 	this._cacheDraw = false;
 79 	/** @private **/
 80 	this._activeContext = null;
 81 	/** @private **/
 82 	this._restoreContext = false;
 83 	/** @private **/
 84 	this._revertShadow = false;
 85 	/** @private **/
 86 	this._revertX = 0;
 87 	/** @private **/
 88 	this._revertY = 0;
 89 	
 90 // constructor:
 91 	// separated so it can be easily addressed in subclasses:
 92 	/** @private **/
 93 	this.init = function() {
 94 		this.id = UID.get();
 95 		this.children = [];
 96 	}
 97 	
 98 // public methods:
 99 	/**
100 	* NOTE: This method is mainly for internal use, though it may be useful for advanced developers extending the capabilities of the CanvasDisplay library.
101 	* Updates the specified context based on this display object's properties.
102 	* @param ctx The canvas 2D context object to update.
103 	* @param ignoreShadows Indicates whether the shadow property should be applied. Passing false will ignore the shadow, resulting in faster rendering for uses like hit testing.
104 	**/
105 	this.updateContext = function(ctx,ignoreShadows) {
106 		if (this._activeContext != null) { throw "ERROR: updateContext called twice without calling revertContext. ["+this.name+"]"; } // GDS: temp for catching errors.
107 		if (this.visible != true || ctx == null || this.alpha <= 0) { return false; }
108 		// apply context changes:
109 		this._activeContext = ctx;
110 		
111 		if (this._restoreContext = (this.rotation%360 || this.scaleX != 1 || this.scaleY != 1)) {
112 			ctx.save();
113 			// GDS: might be worth benchmarking implicit vs explicit boolean tests here:
114 			if (this.x || this.y) { ctx.translate(this.x, this.y); }
115 			if (this.rotation%360) { ctx.rotate(this.rotation%360/180*Math.PI); }
116 			if (this.scaleX != 1 || this.scaleY != 1) { ctx.scale(this.scaleX, this.scaleY); }
117 			if (this.regX || this.regY) { ctx.translate(-this.regX, -this.regY); }
118 		} else {
119 			ctx.translate(-(this._restoreX = -this.x+this.regX), -(this._restoreY = -this.y+this.regY));
120 		}
121 			
122 		ctx.globalAlpha *= this.alpha;
123 		if (this._revertShadow = (this.shadow && !ignoreShadows)) {
124 			this.applyShadow(ctx, this.shadow);
125 		}
126 	}
127 	
128 	/**
129 	* NOTE: This method is mainly for internal use, though it may be useful for advanced developers extending the capabilities of the CanvasDisplay library.
130 	* Draws the display object into the specified context if it is visible.
131 	* @param ctx The canvas 2D context object to draw into.
132 	* @param ignoreCache Indicates whether the draw operation should ignore any current cache. For example, used for drawing the cache (to prevent it from simply drawing an existing cache back into itself).
133 	**/
134 	this.draw = function(ctx,ignoreCache) {
135 		// can't use _activeContext because sometimes we need to draw without an updateContext being called (ex. caching)
136 		if (this.visible != true || ctx == null || this.alpha <= 0) { return false; }
137 		if (this.cacheCanvas && !ignoreCache) {
138 			ctx.translate(this._cacheOffsetX,this._cacheOffsetY);
139 			ctx.drawImage(this.cacheCanvas,0,0);
140 			ctx.translate(-this._cacheOffsetX,-this._cacheOffsetY);
141 			return false;
142 		}
143 		return true;
144 	}
145 	
146 	/**
147 	* NOTE: This method is mainly for internal use, though it may be useful for advanced developers extending the capabilities of the CanvasDisplay library.
148 	* Reverts the last context that was updated with updateContext(), restoring it to the state it was in prior to the update.
149 	**/
150 	this.revertContext = function() {
151 		if (this._activeContext == null) { return; }
152 		this._activeContext.globalAlpha /= this.alpha;
153 		if (this._revertShadow) {
154 			// GDS: instead, we could save out the shadow properties in update, and restore them here.
155 			this.applyShadow(this._activeContext, Shadow.identity);
156 		}
157 		if (this._restoreContext) { this._activeContext.restore(); }
158 		else { this._activeContext.translate(this._restoreX, this._restoreY); }
159 		this._activeContext = null
160 	}
161 	
162 	/**
163 	* 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 rerendered 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 cache() again. Do not call uncache before the subsequent cache call. 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.
164 	* @param x
165 	* @param y
166 	* @param width
167 	* @param height
168 	**/
169 	this.cache = function(x, y, w, h) {
170 		// draw to canvas.
171 		var ctx;
172 		if (this.cacheCanvas == null) {
173 			this.cacheCanvas = document.createElement("canvas");
174 			ctx = this.cacheCanvas.getContext("2d");
175 		} else {
176 			ctx = this.cacheCanvas.getContext("2d");
177 			ctx.clearRect(0,0,this.cacheCanvas.width,this.cacheCanvas.height);
178 		}
179 		this.cacheCanvas.width = w;
180 		this.cacheCanvas.height = h;
181 		ctx.translate(-x,-y);
182 		this.draw(ctx,true);
183 		this._cacheOffsetX = x;
184 		this._cacheOffsetY = y;
185 	}
186 	
187 	/**
188 	* Clears the current cache. See cache() for more information.
189 	**/
190 	this.uncache = function() {
191 		this.cacheCanvas = null;
192 		this.cacheOffsetX = this.cacheOffsetY = 0;
193 	}
194 	
195 	/**
196 	* Returns the stage that this display object will be rendered on, or null if it has not been added to one.
197 	**/
198 	this.getStage = function() {
199 		var o = this;
200 		while (o.parent) {
201 			o = o.parent;
202 		}
203 		if (o instanceof Stage) { return o; }
204 		return null;
205 	}
206 	
207 	/**
208 	* 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).
209 	**/
210 	this.clone = function() {
211 		var o = new DisplayObject();
212 		this.cloneProps(o);
213 		return o;
214 	}
215 	
216 	/**
217 	* Returns a string representation of this object.
218 	**/
219 	this.toString = function() {
220 		return "[DisplayObject (name="+  this.name +")]";
221 	}
222 	
223 // private methods:
224 	// separated so it can be used easily in subclasses:
225 	/** @private **/
226 	this.cloneProps = function(o) {
227 		o.alpha = this.alpha;
228 		o.name = this.name;
229 		o.regX = this.regX;
230 		o.regY = this.regY;
231 		o.rotation = this.rotation;
232 		o.scaleX = this.scaleX;
233 		o.scaleY = this.scaleY;
234 		o.shadow = this.shadow;
235 		o.visible = this.visible;
236 		o.x  = this.x;
237 		o.y = this.y;
238 		o.mouseEnabled = this.mouseEnabled;
239 	}
240 	
241 	/** @private **/
242 	this.applyShadow = function(ctx, shadow) {
243 		ctx.shadowColor = shadow.color;
244 		ctx.shadowOffsetX = shadow.offsetX;
245 		ctx.shadowOffsetY = shadow.offsetY;
246 		ctx.shadowBlur = shadow.blur;
247 	}
248 }