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 }