1 /** 2 * The Render Engine 3 * HTMLElementContext 4 * 5 * @fileoverview A render context which wraps a specified HTML node. 6 * 7 * @author: Brett Fattori (brettf@renderengine.com) 8 * @author: $Author: bfattori $ 9 * @version: $Revision: 1555 $ 10 * 11 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 12 * 13 * Permission is hereby granted, free of charge, to any person obtaining a copy 14 * of this software and associated documentation files (the "Software"), to deal 15 * in the Software without restriction, including without limitation the rights 16 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 * copies of the Software, and to permit persons to whom the Software is 18 * furnished to do so, subject to the following conditions: 19 * 20 * The above copyright notice and this permission notice shall be included in 21 * all copies or substantial portions of the Software. 22 * 23 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 29 * THE SOFTWARE. 30 * 31 */ 32 33 // The class this file defines and its required classes 34 R.Engine.define({ 35 "class":"R.rendercontexts.HTMLElementContext", 36 "requires":[ 37 "R.rendercontexts.RenderContext2D", 38 "R.math.Math2D" 39 ] 40 }); 41 42 /** 43 * @class A wrapper for any HTML element to convert it into a targetable render context. 44 * The {@link R.rendercontexts.DocumentContext} and {@link R.rendercontexts.HTMLDivContext} use this as their base 45 * class. 46 * 47 * @extends R.rendercontexts.RenderContext2D 48 * @constructor 49 * @description Create an instance of an HTML element rendering context. This context 50 * represents any HTML element. 51 * @param name {String} The name of the context 52 * @param element {Number} The element which is the surface of the context. 53 */ 54 R.rendercontexts.HTMLElementContext = function () { 55 return R.rendercontexts.RenderContext2D.extend(/** @scope R.rendercontexts.HTMLElementContext.prototype */{ 56 57 transformStack:null, 58 cursorPos:null, 59 jQObj:null, 60 hasTxfm:false, 61 has3dTxfm:false, 62 txfmBrowser:null, 63 txfmOrigin:null, 64 txfm:null, 65 66 tmpP1:null, 67 tmpP2:null, 68 tmpP3:null, 69 70 /** @private */ 71 constructor:function (name, element) { 72 this.base(name || "HTMLElementContext", element); 73 element.id = this.getId(); 74 this.cursorPos = R.math.Point2D.create(0, 0); 75 this.txfm = []; 76 this.transformStack = []; 77 this.pushTransform(); 78 this.jQObj = null; 79 this.setViewport(R.math.Rectangle2D.create(0, 0, this.jQ().width(), this.jQ().height())); 80 this.checkTransformSupport(); 81 82 // Temporary points to use in calculations 83 this.tmpP1 = R.math.Point2D.create(0, 0); 84 this.tmpP2 = R.math.Point2D.create(0, 0); 85 this.tmpP3 = R.math.Point2D.create(0, 0); 86 }, 87 88 /** 89 * Destroy the context and any objects within the context. 90 */ 91 destroy:function () { 92 93 // If the objects in the context are elements in 94 // the DOM, remove them from the DOM 95 var objs = this.getObjects(); 96 for (var o in objs) { 97 var e = objs[o].getElement(); 98 if (e && e.nodeName && e != document.body) { 99 100 this.getSurface().removeChild(e); 101 } 102 } 103 this.cursorPos.destroy(); 104 this.getViewport().destroy(); 105 this.txfm = null; 106 107 this.tmpP1.destroy(); 108 this.tmpP2.destroy(); 109 this.tmpP3.destroy(); 110 111 this.base(); 112 }, 113 114 /** 115 * Check the browser and version to see if it supports transformations. 116 * @private 117 */ 118 checkTransformSupport:function () { 119 var version = parseFloat(R.engine.Support.sysInfo().version); 120 switch (R.engine.Support.sysInfo().browser) { 121 case "safari": 122 case "safarimobile": 123 if (version >= 3) { 124 // Support for webkit transforms 125 this.hasTxfm = true; 126 this.has3dTxfm = true; 127 this.txfmBrowser = "webkitTransform"; 128 this.txfmOrigin = "webkitTransformOrigin"; 129 } 130 break; 131 case "chrome": 132 // Support for webkit transforms 133 this.hasTxfm = true; 134 this.has3dTxfm = true; 135 this.txfmBrowser = "webkitTransform"; 136 this.txfmOrigin = "webkitTransformOrigin"; 137 break; 138 case "firefox": 139 if (version >= 3.5) { 140 // Support for gecko transforms 141 this.hasTxfm = true; 142 this.has3dTxfm = false; 143 this.txfmBrowser = "MozTransform"; 144 this.txfmOrigin = "MozTransformOrigin"; 145 } 146 break; 147 case "opera": 148 if (version >= 10.5) { 149 // Support for opera transforms 150 this.hasTxfm = true; 151 this.has3dTxfm = false; 152 this.txfmBrowser = "OTransform"; 153 this.txfmOrigin = "OTransformOrigin"; 154 } 155 break; 156 case "msie": 157 if (version >= 9.0) { 158 // Support for Internet Explorer transforms 159 this.hasTxfm = true; 160 this.has3dTxfm = false; 161 this.txfmBrowser = "msTransform"; 162 this.txfmOrigin = "msTransformOrigin"; 163 } 164 break; 165 default: 166 this.hasTxfm = false; 167 this.has3dTxfm = false; 168 break; 169 } 170 }, 171 172 /** 173 * Add an object to the context, or creates an element to represent the object. Objects 174 * added to the <tt>HTMLElementContext</tt> need a DOM representation, otherwise one 175 * will be created for the object being added. 176 * 177 * @param obj {HTMLElement} The element, or <tt>null</tt> 178 */ 179 add:function (obj) { 180 if (!obj.getElement()) { 181 // Create an element for the object 182 obj.setElement($("<div>").css("position", "absolute")); 183 } 184 185 // Look to see if the element is already a child of the element 186 // we're appending to. This will occur when someone adds a HTMLElementContext 187 // to the default context, when the element which represents the HTMLElementContext 188 // already exists in the DOM. 189 if (this.jQ().find(obj.getElement()).length == 0) { 190 this.jQ().append(obj.getElement()); 191 } 192 193 var pos = $(obj.getElement()).position(); 194 obj.setObjectDataModel("DOMPosition", R.math.Point2D.create(pos.left, pos.top)); 195 196 this.base(obj); 197 }, 198 199 /** 200 * Remove an object from the context. 201 * @param obj {HTMLElement} The object to remove 202 */ 203 remove:function (obj) { 204 if (obj.jQ().length) { 205 obj.jQ().remove(); 206 } 207 this.base(obj); 208 }, 209 210 /** 211 * Serializes the current transformation state to an object. 212 * @return {Object} 213 * @private 214 */ 215 serializeTransform:function () { 216 return { 217 pos:R.clone(this.cursorPos), 218 txfm:this.txfm, 219 stroke:this.getLineStyle(), 220 sWidth:this.getLineWidth(), 221 fill:this.getFillStyle() 222 }; 223 }, 224 225 /** 226 * Deserializes a transformation state from an object. 227 * @param transform {Object} The object which contains the current transformation 228 * @private 229 */ 230 deserializeTransform:function (transform) { 231 this.txfm = transform.txfm; 232 this.cursorPos.set(transform.pos); 233 transform.pos.destroy(); 234 this.setLineStyle(transform.stroke); 235 this.setLineWidth(transform.sWidth); 236 this.setFillStyle(transform.fill); 237 }, 238 239 /** 240 * Push a transform state onto the stack. 241 */ 242 pushTransform:function () { 243 this.base(); 244 this.transformStack.push(this.serializeTransform()); 245 }, 246 247 /** 248 * Pop a transform state off the stack. 249 */ 250 popTransform:function () { 251 this.base(); 252 this.deserializeTransform(this.transformStack.pop()); 253 }, 254 255 //================================================================ 256 // Drawing functions 257 258 /** 259 * Set the background color of the context. 260 * 261 * @param color {String} An HTML color 262 */ 263 setBackgroundColor:function (color) { 264 this.base(color); 265 this.jQ().css("background-color", color); 266 }, 267 268 /** 269 * Set the current transform position (translation). 270 * 271 * @param point {R.math.Point2D} The translation 272 */ 273 setPosition:function (point) { 274 this.cursorPos.add(point); 275 if (this.hasTxfm) { 276 this.txfm[0] = "translate" + (this.has3dTxfm ? "3d" : "") + "(" + this.cursorPos.x + "px," + this.cursorPos.y + "px" + (this.has3dTxfm ? ",0" : "") + ")"; 277 } 278 this.base(this.cursorPos); 279 }, 280 281 /** 282 * Set the rotation angle of the current transform 283 * 284 * @param angle {Number} An angle in degrees 285 */ 286 setRotation:function (angle) { 287 if (this.hasTxfm) { 288 angle = Math.floor(angle % 360); 289 this.txfm[1] = "rotate" + (this.has3dTxfm ? "3d(0,0,1," : "(") + angle + "deg)"; 290 } 291 this.base(angle); 292 }, 293 294 /** 295 * Set the scale of the current transform. Specifying 296 * only the first parameter implies a uniform scale. 297 * 298 * @param scaleX {Number} The X scaling factor, with 1 being 100% 299 * @param scaleY {Number} The Y scaling factor 300 */ 301 setScale:function (scaleX, scaleY) { 302 scaleX = scaleX || 1; 303 scaleY = scaleY || scaleX; 304 if (this.hasTxfm) { 305 this.txfm[2] = "scale" + (this.has3dTxfm ? "3d" : "") + "(" + scaleX + "," + scaleY + (this.has3dTxfm ? ",1" : "") + ")"; 306 } 307 this.base(scaleX, scaleY); 308 }, 309 310 /** 311 * Set the width of the context drawing area. 312 * 313 * @param width {Number} The width in pixels 314 */ 315 setWidth:function (width) { 316 this.base(width); 317 this.jQ().width(width); 318 }, 319 320 /** 321 * Set the height of the context drawing area 322 * 323 * @param height {Number} The height in pixels 324 */ 325 setHeight:function (height) { 326 this.base(height); 327 this.jQ().height(height); 328 }, 329 330 _onlyChanged:function (ref, css) { 331 if (!ref) 332 return css; 333 334 var modifiedCSS = {}, jq = ref.jQ(); 335 if (!jq) 336 return css; 337 338 for (var attr in css) { 339 var oldValue = jq.css(attr); 340 if (oldValue !== css[attr]) { 341 modifiedCSS[attr] = css[attr]; 342 } 343 } 344 return modifiedCSS; 345 }, 346 347 /** 348 * Merge in the CSS transformations object, if the browser supports it. 349 * @param css {Object} CSS properties to merge with 350 * @return {Object} 351 * @private 352 */ 353 _mergeTransform:function (ref, css) { 354 if (this.hasTxfm && this.txfm[0]) { 355 css[this.txfmBrowser] = this.txfm[0] + " " + 356 (ref && ref.getRotation() != 0 ? this.txfm[1] + " " : "") + 357 (ref && ref.getScale().len() != 1 ? this.txfm[2] : ""); 358 } 359 else { 360 css.top = css.top || this.cursorPos.y; 361 css.left = css.left || this.cursorPos.x; 362 } 363 364 return css; 365 }, 366 367 /** 368 * Create an element and append it to the render context 369 * @param element {String} Element type 370 * @return {jQuery} 371 * @private 372 */ 373 _createElement:function (element) { 374 var e = $(element).css({ 375 position:"absolute", 376 display:"block", 377 left:0, 378 top:0 379 }); 380 this.jQ().append(e); 381 return e; 382 }, 383 384 /** 385 * Draw an un-filled rectangle on the context. Unless <tt>ref</tt> is provided, a div element 386 * will be added to the render context. 387 * 388 * @param rect {R.math.Rectangle2D} The rectangle to draw 389 * @param ref {R.engine.GameObject} A reference game object 390 * @return {HTMLElement} The element added to the DOM 391 */ 392 drawRectangle:function (rect, ref) { 393 var rD = rect.getDims(), 394 obj = ref && ref.jQ() ? ref.jQ() : this._createElement("<div>"); 395 396 obj.css(this._mergeTransform(ref, { 397 borderWidth:this.getLineWidth(), 398 borderColor:this.getLineStyle(), 399 left:rD.l, 400 top:rD.t, 401 width:rD.w, 402 height:rD.h, 403 position:"absolute" 404 })); 405 406 return obj; 407 }, 408 409 /** 410 * Draw a filled rectangle on the context. Unless <tt>ref</tt> is provided, a div element 411 * will be added to the render context. 412 * 413 * @param rect {R.math.Rectangle2D} The rectangle to draw 414 * @param ref {R.engine.GameObject} A reference game object 415 * @return {HTMLElement} The element added to the DOM 416 */ 417 drawFilledRectangle:function (rect, ref) { 418 var rD = rect.getDims(), 419 obj = ref && ref.jQ() ? ref.jQ() : this._createElement("<div>"); 420 421 obj.css(this._mergeTransform(ref, { 422 borderWidth:this.getLineWidth(), 423 borderColor:this.getLineStyle(), 424 backgroundColor:this.getFillStyle(), 425 left:rD.l, 426 top:rD.t, 427 width:rD.w, 428 height:rD.h, 429 position:"absolute" 430 })); 431 432 return obj; 433 }, 434 435 /** 436 * Draw a point on the context. Unless <tt>ref</tt> is provided, a new image 437 * will be added to the render context. 438 * 439 * @param point {R.math.Point2D} The position to draw the point 440 * @param ref {R.engine.GameObject} A reference game object 441 * @return {HTMLElement} The element added to the DOM 442 */ 443 drawPoint:function (point, ref) { 444 return this.drawFilledRectangle(R.math.Rectangle2D.create(point.x, point.y, 1, 1), ref); 445 }, 446 447 /** 448 * Draw a sprite on the context. Unless <tt>ref</tt> is provided, a new image 449 * will be added to the render context. 450 * 451 * @param sprite {R.resources.types.Sprite} The sprite to draw 452 * @param time {Number} The current world time 453 * @param dt {Number} The delta between the world time and the last time the world was updated 454 * in milliseconds. 455 * @param ref {R.math.HostObject} A reference game object 456 * @return {HTMLElement} The element added to the DOM 457 */ 458 drawSprite:function (sprite, time, dt, ref) { 459 var f = sprite.getFrame(time, dt); 460 461 // The reference object is a host object it 462 // will give us a reference to the HTML element which we can then 463 // just modify the displayed image for. If no ref was provided, 464 // create a new image. 465 var obj = ref && ref.jQ() ? ref.jQ() : this._createElement("<div>"); 466 467 var css = this._mergeTransform(ref, { 468 width:f.w, 469 height:f.h, 470 backgroundPosition:-f.x + "px " + -f.y + "px", 471 backgroundImage:'url:(' + sprite.getSourceImage().src + ')' 472 }); 473 obj.css(css); 474 this.base(sprite, time, dt); 475 f.destroy(); 476 477 return obj; 478 }, 479 480 /** 481 * Draw an image on the context. Unless <tt>ref</tt> is provided, a new image 482 * will be added to the render context. 483 * 484 * @param rect {R.math.Rectangle2D} The rectangle that specifies the position and 485 * dimensions of the image rectangle. 486 * @param image {HTMLImage} The image to draw onto the context 487 * @param [srcRect] {R.math.Rectangle2D} <i>[optional]</i> The source rectangle within the image, if 488 * <tt>null</tt> the entire image is used 489 * @param [ref] {R.engine.GameObject} A reference game object 490 * @return {HTMLElement} The element added to the DOM 491 */ 492 drawImage:function (rect, image, srcRect, ref) { 493 srcRect = (srcRect.__RECTANGLE2D ? srcRect : null); 494 var sD = srcRect ? srcRect : rect; 495 ref = (!srcRect.__RECTANGLE2D ? srcRect : ref); 496 497 // The reference object is an object that should 498 // have a reference to an HTML element which we can 499 // just modify the displayed image for. 500 // If no ref is provided, create a new element. 501 var obj = ref && ref.jQ() ? ref.jQ() : this._createElement("<div>"); 502 503 var css = this._mergeTransform(ref, { 504 backgroundImage:"url(" + image.src + ")", 505 backgroundPosition:-sD.x + "px " + -sD.y + "px", 506 left:rect.x, 507 top:rect.y, 508 width:sD.w, 509 height:sD.h 510 }); 511 obj.css(this._onlyChanged(ref, css)); 512 this.base(rect, image, srcRect, ref); 513 514 return obj; 515 }, 516 517 /** 518 * Draw text on the context. Unless <tt>ref</tt> is provided, a span element 519 * will be added to the render context. 520 * 521 * @param point {R.math.Point2D} The top-left position to draw the image. 522 * @param text {String} The text to draw 523 * @param ref {R.engine.GameObject} A reference game object 524 * @return {HTMLElement} The element added to the DOM 525 */ 526 drawText:function (point, text, ref) { 527 this.base(point, text); 528 529 // The reference object is a host object it 530 // will give us a reference to the HTML element which we can then 531 // just modify the displayed text for. If no ref was provided, 532 // create a new image. 533 var obj = ref && ref.jQ() ? ref.jQ() : this._createElement("<span>"); 534 535 var css = this._mergeTransform(ref, { 536 font:this.getNormalizedFont(), 537 color:this.getFillStyle(), 538 left:point.x, 539 top:point.y, 540 position:"absolute" 541 }); 542 obj.css(css).text(text); 543 544 return obj; 545 }, 546 547 /** 548 * Draw an element on the context. 549 * @param ref {R.engine.GameObject} A reference game object 550 * @param [el] {HTMLElement} A DOM element to draw 551 */ 552 drawElement:function (ref, el, pos) { 553 if (ref && ref.getElement()) { 554 // TODO: Can probably save cycles by checking for changes in the 555 // transformations before blindly applying them 556 el = el || ref.getElement(); 557 var css = {}, i; 558 if (this.hasTxfm && ref.getOrigin) { 559 if (ref.getOrigin().isZero()) { 560 el.style[this.txfmOrigin] = "top left"; 561 } 562 else { 563 var o = ref.getOrigin(); 564 el.style[this.txfmOrigin] = o.x + "px " + o.y + "px"; 565 } 566 } 567 css = this._mergeTransform(ref, css); 568 for (i in css) { 569 el.style[i] = css[i]; 570 } 571 } else { 572 el.css({ 573 top:pos.y, 574 left:pos.x 575 }) 576 } 577 } 578 579 }, /** @scope R.rendercontexts.HTMLElementContext.prototype */ { 580 581 /** 582 * Get the class name of this object 583 * @return {String} The string "R.rendercontexts.HTMLElementContext" 584 */ 585 getClassName:function () { 586 return "R.rendercontexts.HTMLElementContext"; 587 } 588 }); 589 590 }; 591