1 /** 2 * The Render Engine 3 * Engine Support Class 4 * 5 * @fileoverview A support class for the engine with useful methods 6 * to manipulate arrays, parse JSON, and handle query parameters. 7 * 8 * @author: Brett Fattori (brettf@renderengine.com) 9 * @author: $Author: bfattori@gmail.com $ 10 * @version: $Revision: 1569 $ 11 * 12 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 13 * 14 * Permission is hereby granted, free of charge, to any person obtaining a copy 15 * of this software and associated documentation files (the "Software"), to deal 16 * in the Software without restriction, including without limitation the rights 17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 * copies of the Software, and to permit persons to whom the Software is 19 * furnished to do so, subject to the following conditions: 20 * 21 * The above copyright notice and this permission notice shall be included in 22 * all copies or substantial portions of the Software. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 * THE SOFTWARE. 31 * 32 */ 33 34 /** 35 * @class A static class with support methods the engine or games can use. 36 * Many of the methods can be used to manipulate arrays. Additional 37 * methods are provided to access query parameters, and generate or 38 * read JSON. A system capabilities method, {@link #sysInfo}, can be 39 * used to query the environment for support of features. 40 * @static 41 */ 42 R.engine.Support = Base.extend(/** @scope R.engine.Support.prototype */{ 43 constructor:null, 44 45 /** 46 * Get the index of an element in the specified array. 47 * 48 * @param array {Array} The array to scan 49 * @param obj {Object} The object to find 50 * @param [from=0] {Number} The index to start at, defaults to zero. 51 * @memberOf R.engine.Support 52 */ 53 indexOf:function (array, obj, from) { 54 return array && R.isArray(array) ? array.indexOf(obj, from) : -1; 55 }, 56 57 /** 58 * Remove an element from an array. This method modifies the array 59 * directly. 60 * 61 * @param array {Array} The array to modify 62 * @param obj {Object} The object to remove 63 * @memberOf R.engine.Support 64 */ 65 arrayRemove:function (array, obj) { 66 if (!array || !R.isArray(array)) { 67 return; 68 } 69 70 var idx = R.engine.Support.indexOf(array, obj); 71 if (idx != -1) { 72 array.splice(idx, 1); 73 } 74 }, 75 76 /** 77 * Returns <tt>true</tt> if the string, after trimming, is either 78 * empty or is null. 79 * 80 * @param str {String} The string to test 81 * @return {Boolean} <tt>true</tt> if the string is empty or <tt>null</tt> 82 * @memberOf R.engine.Support 83 */ 84 isEmpty:function (str) { 85 return R.isEmpty(str); 86 }, 87 88 /** 89 * Calls a provided callback function once for each element in 90 * an array, and constructs a new array of all the values for which 91 * callback returns a <tt>true</tt> value. callback is invoked only 92 * for indexes of the array which have assigned values; it is not invoked 93 * for indexes which have been deleted or which have never been assigned 94 * values. Array elements which do not pass the callback test are simply 95 * skipped, and are not included in the new array. 96 * 97 * @param array {Array} The array to filter. 98 * @param fn {Function} The callback to invoke. It will be passed three 99 * arguments: The element value, the index of the element, 100 * and the array being traversed. 101 * @param [thisp=null] {Object} Used as <tt>this</tt> for each invocation of the 102 * callback. 103 * @memberOf R.engine.Support 104 */ 105 filter:function (array, fn, thisp) { 106 return array && R.isArray(array) ? array.filter(fn, thisp) : undefined; 107 }, 108 109 /** 110 * Executes a callback for each element within an array. 111 * 112 * @param array {Array} The array to operate on 113 * @param fn {Function} The function to apply to each element. It will be passed three 114 * arguments: The element value, the index of the element, 115 * and the array being traversed. 116 * @param [thisp=null] {Object} An optional "this" pointer to use in the callback 117 * @memberOf R.engine.Support 118 */ 119 forEach:function (array, fn, thisp) { 120 return array && R.isArray(array) ? array.forEach(fn, thisp) : undefined; 121 }, 122 123 /** 124 * Fill the specified array with <tt>size</tt> elements 125 * each with the value "<tt>value</tt>". Modifies the provided 126 * array directly. 127 * 128 * @param {Array} arr The array to fill 129 * @param {Number} size The size of the array to fill 130 * @param {Object} value The value to put at each index 131 * @memberOf R.engine.Support 132 */ 133 fillArray:function (arr, size, value) { 134 for (var i = 0; i < size; i++) { 135 arr[i] = value; 136 } 137 }, 138 139 /** 140 * Get the path from a fully qualified URL, not including the trailing 141 * slash character. 142 * 143 * @param url {String} The URL 144 * @return {String} The path 145 * @memberOf R.engine.Support 146 */ 147 getPath:function (url) { 148 return R.isString(url) ? url.substr(0, url.lastIndexOf("/")) : undefined; 149 }, 150 151 /** 152 * Get the query parameters from the window location object. The 153 * object returned will contain a key/value pair for each argument 154 * found. 155 * 156 * @return {Object} A generic <tt>Object</tt> with a key and value for each query argument. 157 * @memberOf R.engine.Support 158 */ 159 getQueryParams:function () { 160 if (!R.engine.Support.parms) { 161 R.engine.Support.parms = {}; 162 var p = window.location.toString().split("?")[1]; 163 if (p) { 164 p = p.split("&"); 165 for (var x = 0; x < p.length; x++) { 166 var v = p[x].split("="); 167 R.engine.Support.parms[v[0]] = (v.length > 1 ? v[1] : ""); 168 } 169 } 170 } 171 return R.engine.Support.parms; 172 }, 173 174 /** 175 * Check for a query parameter and to see if it evaluates to one of the following: 176 * <tt>true</tt>, <tt>1</tt>, <tt>yes</tt>, or <tt>y</tt>. If so, returns <tt>true</tt>. 177 * 178 * @param paramName {String} The query parameter name 179 * @return {Boolean} <tt>true</tt> if the query parameter exists and is one of the specified values. 180 * @memberOf R.engine.Support 181 */ 182 checkBooleanParam:function (paramName) { 183 return (R.engine.Support.getQueryParams()[paramName] && 184 (R.engine.Support.getQueryParams()[paramName].toLowerCase() != "0" || 185 R.engine.Support.getQueryParams()[paramName].toLowerCase() != "false")); 186 }, 187 188 /** 189 * Check for a query parameter and to see if it evaluates to the specified value. 190 * If so, returns <tt>true</tt>. 191 * 192 * @param paramName {String} The query parameter name 193 * @param val {String} The value to check for 194 * @return {Boolean} <tt>true</tt> if the query parameter exists and is the value specified 195 * @memberOf R.engine.Support 196 */ 197 checkStringParam:function (paramName, val) { 198 return (R.engine.Support.getStringParam(paramName, null) == val); 199 }, 200 201 /** 202 * Check for a query parameter and to see if it evaluates to the specified number. 203 * If so, returns <tt>true</tt>. 204 * 205 * @param paramName {String} The query parameter name 206 * @param val {Number} The number to check for 207 * @return {Boolean} <tt>true</tt> if the query parameter exists and is the value specified 208 * @memberOf R.engine.Support 209 */ 210 checkNumericParam:function (paramName, val) { 211 var num = R.engine.Support.getStringParam(paramName, null); 212 return (R.isNumber(num) && num == val); 213 }, 214 215 /** 216 * Get a numeric query parameter, or the default specified if the parameter 217 * doesn't exist. 218 * 219 * @param paramName {String} The name of the parameter 220 * @param defaultVal {Number} The number to return if the parameter doesn't exist 221 * @return {Number} The value 222 * @memberOf R.engine.Support 223 */ 224 getNumericParam:function (paramName, defaultVal) { 225 return Number(R.engine.Support.getStringParam(paramName, defaultVal)); 226 }, 227 228 /** 229 * Get a string query parameter, or the default specified if the parameter 230 * doesn't exist. 231 * 232 * @param paramName {String} The name of the parameter 233 * @param defaultVal {String} The string to return if the parameter doesn't exist 234 * @return {String} The value 235 * @memberOf R.engine.Support 236 */ 237 getStringParam:function (paramName, defaultVal) { 238 return (R.engine.Support.getQueryParams()[paramName] || defaultVal); 239 }, 240 241 /** 242 * Returns specified object as a JavaScript Object Notation (JSON) string. 243 * 244 * @param object {Object} Must not be undefined or contain undefined types and variables. 245 * @return String 246 * @memberOf R.engine.Support 247 * @deprecated Use <tt>JSON.stringify()</tt> 248 */ 249 toJSON:function (o) { 250 return window.JSON.stringify(o); 251 }, 252 253 /** 254 * Parses specified JavaScript Object Notation (JSON) string back into its corresponding object. 255 * 256 * @param jsonString 257 * @return Object 258 * @see http://www.json.org 259 * @memberOf R.engine.Support 260 * @deprecated Use <tt>JSON.parse()</tt> instead 261 */ 262 parseJSON:function (jsonString) { 263 return JSON.parse(jsonString); 264 }, 265 266 /** 267 * Determine the OS platform from the user agent string, if possible 268 * @private 269 * @memberOf R.engine.Support 270 */ 271 checkOS:function () { 272 // Scrape the userAgent to get the OS 273 var uA = navigator.userAgent.toLowerCase(); 274 return /windows nt 6\.0/.test(uA) ? "Windows Vista" : 275 /windows nt 6\.1/.test(uA) ? "Windows 7" : 276 /windows nt 5\.1/.test(uA) ? "Windows XP" : 277 /windows/.test(uA) ? "Windows" : 278 /android 1\./.test(uA) ? "Android 1.x" : 279 /android 2\./.test(uA) ? "Android 2.x" : 280 /android/.test(uA) ? "Android" : 281 /x11/.test(uA) ? "X11" : 282 /linux/.test(uA) ? "Linux" : 283 /Mac OS X/.test(uA) ? "Mac OS X" : 284 /macintosh/.test(uA) ? "Macintosh" : 285 /iphone|ipad|ipod/.test(uA) ? "iOS" : 286 "unknown"; 287 }, 288 289 /** 290 * Gets an object that is a collation of a number of browser and 291 * client settings. You can use this information to tailor a game 292 * to the environment it is running within. 293 * <ul> 294 * <li>browser - A string indicating the browser type (safari, mozilla, opera, msie)</li> 295 * <li>version - The browser version</li> 296 * <li>agent - The user agent</li> 297 * <li>platform - The platform the browser is running on</li> 298 * <li>cpu - The CPU on the machine the browser is running on</li> 299 * <li>OS - The operating system the browser is running on</li> 300 * <li>language - The browser's language</li> 301 * <li>online - If the browser is running in online mode</li> 302 * <li>cookies - If the browser supports cookies</li> 303 * <li>fullscreen - If the browser is running in fullscreen mode</li> 304 * <li>width - The browser's viewable width</li> 305 * <li>height - The browser's viewable height</li> 306 * <li>viewWidth - The innerWidth of the viewport</li> 307 * <li>viewHeight - The innerHeight of the viewport</li> 308 * <li>support: 309 * <ul><li>xhr - Browser supports XMLHttpRequest object</li> 310 * <li>geo - navigator.geolocation is supported</li> 311 * <li>threads - Browser supports Worker threads</li> 312 * <li>sockets - Browser supports WebSocket object</li> 313 * <li>storage: 314 * <ul><li>cookie - Cookie support. Reports an object with "maxLength", or <code>false</code></li> 315 * <li>local - localStorage support</li> 316 * <li>session - sessionStorage support</li> 317 * <li>indexeddb - indexedDB support</li> 318 * <li>sqllite - SQL lite support</li> 319 * <li>audio - HTML5 Audio support</li> 320 * <li>video - HTML5 Video support</li> 321 * </ul> 322 * </li> 323 * <li>canvas: 324 * <ul><li>defined - Canvas is either native or emulated</li> 325 * <li>text - Supports text</li> 326 * <li>textMetrics - Supports text measurement</li> 327 * <li>contexts: 328 * <ul><li>ctx2D - Supports 2D</li> 329 * <li>ctxGL - Supports webGL</li> 330 * </ul> 331 * </li> 332 * </ul> 333 * </li> 334 * </ul> 335 * </li> 336 * </ul> 337 * @return {Object} An object with system information 338 * @memberOf R.engine.Support 339 */ 340 sysInfo:function () { 341 if (!R.engine.Support._sysInfo) { 342 343 // Canvas and Storage support defaults 344 var canvasSupport = { 345 defined:false, 346 text:false, 347 textMetrics:false, 348 contexts:{ 349 "2D":false, 350 "GL":false 351 } 352 }, 353 storageSupport = { 354 cookie:false, 355 local:false, 356 session:false, 357 indexeddb:false, 358 sqllite:false 359 }; 360 361 // Check for canvas support 362 try { 363 var canvas = document.createElement("canvas"); 364 if (typeof canvas !== "undefined" && R.isFunction(canvas.getContext)) { 365 canvasSupport.defined = true; 366 var c2d = canvas.getContext("2d"); 367 if (typeof c2d !== "undefined") { 368 canvasSupport.contexts["2D"] = true; 369 370 // Does it support native text 371 canvasSupport.text = (R.isFunction(c2d.fillText)); 372 canvasSupport.textMetrics = (R.isFunction(c2d.measureText)); 373 } else { 374 canvasSupport.contexts["2D"] = false; 375 } 376 377 try { 378 var webGL = canvas.getContext("webgl"); 379 if (typeof webGL !== "undefined") { 380 canvasSupport.contexts["GL"] = true; 381 } 382 } catch (ex) { 383 canvasSupport.contexts["GL"] = false; 384 } 385 } 386 } catch (ex) { /* ignore */ 387 } 388 389 // Check storage support 390 try { 391 try { 392 // Drop a cookie, then look for it (3kb max) 393 for (var i = 0, j = []; i < 3072; i++) { 394 j.push("x"); 395 } 396 window.document.cookie = "tre.test=" + j.join("") + ";path=/"; 397 var va = window.document.cookie.match('(?:^|;)\\s*tre.test=([^;]*)'), 398 supported = !!va; 399 if (supported) { 400 // expire the cookie before returning 401 window.document.cookie = "tre.test=;path=/;expires=" + new Date(R.now() - 1).toGMTString(); 402 } 403 storageSupport.cookie = supported ? { "maxLength":va[1].length } : false; 404 } catch (ex) { /* ignored */ 405 } 406 407 try { 408 storageSupport.local = (typeof localStorage !== "undefined"); 409 storageSupport.session = (typeof sessionStorage !== "undefined"); 410 } catch (ex) { 411 // Firefox bug (https://bugzilla.mozilla.org/show_bug.cgi?id=389002) 412 } 413 414 try { 415 storageSupport.indexeddb = (typeof mozIndexedDB !== "undefined"); 416 } catch (ex) { /* ignored */ 417 } 418 419 storageSupport.sqllite = R.isFunction(window.openDatabase); 420 } catch (ex) { /* ignored */ 421 } 422 423 // Build support object 424 R.engine.Support._sysInfo = { 425 "browser":R.browser.chrome ? "chrome" : 426 (R.browser.android ? "android" : 427 (R.browser.Wii ? "wii" : 428 (R.browser.safariMobile ? "safarimobile" : 429 (R.browser.safari ? "safari" : 430 (R.browser.firefox ? "firefox" : 431 (R.browser.mozilla ? "mozilla" : 432 (R.browser.opera ? "opera" : 433 (R.browser.msie ? "msie" : "unknown")))))))), 434 "version":R.browser.version, 435 "agent":navigator.userAgent, 436 "platform":navigator.platform, 437 "cpu":navigator.cpuClass || navigator.oscpu, 438 "OS":R.engine.Support.checkOS(), 439 "language":navigator.language, 440 "online":navigator.onLine, 441 "fullscreen":window.fullScreen || false, 442 "support":{ 443 "audio":(typeof Audio !== "undefined"), 444 "video":(typeof Video !== "undefined"), 445 "xhr":(typeof XMLHttpRequest !== "undefined"), 446 "threads":(typeof Worker !== "undefined"), 447 "sockets":(typeof WebSocket !== "undefined"), 448 "storage":storageSupport, 449 "geo":(typeof navigator.geolocation !== "undefined"), 450 "canvas":canvasSupport 451 } 452 }; 453 454 $(document).ready(function () { 455 // When the document is ready, we'll go ahead and get the width and height added in 456 R.engine.Support._sysInfo = $.extend(R.engine.Support._sysInfo, { 457 "width":$(window).width(), 458 "height":$(window).height(), 459 "viewWidth":$(document).width(), 460 "viewHeight":$(document).height() 461 }); 462 }); 463 } 464 return R.engine.Support._sysInfo; 465 }, 466 467 /** 468 * When the object is no longer <tt>undefined</tt>, the function will 469 * be executed. 470 * @param obj {Object} The object to wait for 471 * @param fn {Function} The function to execute when the object is ready 472 * @memberOf R.engine.Support 473 */ 474 whenReady:function (obj, fn) { 475 var whenObject = { 476 callback:fn, 477 object:obj 478 }, sleeper; 479 480 sleeper.fn = fn; 481 sleeper.obj = obj; 482 483 sleeper = R.bind(whenObject, function () { 484 if (typeof this.object != "undefined") { 485 this.callback(); 486 } else { 487 setTimeout(sleeper, 50); 488 } 489 }); 490 sleeper(); 491 }, 492 493 /** 494 * Displays the virtual D-pad on the screen, if enabled via <tt>R.Engine.options.useVirtualControlPad</tt>, 495 * and wires up the appropriate events for the current browser. 496 */ 497 showDPad:function () { 498 if (!R.Engine.options.useVirtualControlPad) { 499 return; 500 } 501 502 R.debug.Console.debug("Virtual D-pad Enabled."); 503 504 // Events to track based on platform 505 var downEvent, upEvent; 506 switch (R.engine.Support.sysInfo().browser) { 507 case "safarimobile": 508 case "android": 509 downEvent = "touchstart"; 510 upEvent = "touchend"; 511 break; 512 default: 513 downEvent = "mousedown"; 514 upEvent = "mouseup"; 515 } 516 517 var dpad = $("<div class='virtual-d-pad'></div>"), 518 vpad = $("<div class='virtual-buttons'></div>"), dpButtons = [], vbButtons = [], i = 0; 519 520 521 // Decodes the key mapping 522 function getMappedKey(key) { 523 if (key.indexOf("R.engine.Events.") != -1) { 524 return R.engine.Events[key.split(".")[3]]; 525 } else { 526 return key.toUpperCase().charCodeAt(0); 527 } 528 } 529 530 // Don't allow touches in the virtual pads to propagate 531 dpad.bind(downEvent, function (evt) { 532 evt.preventDefault(); 533 evt.stopPropagation(); 534 }); 535 vpad.bind(downEvent, function (evt) { 536 evt.preventDefault(); 537 evt.stopPropagation(); 538 }); 539 540 // D-pad buttons 541 $.each(R.Engine.options.virtualPad, function (key, v) { 542 if (R.Engine.options.virtualPad[key] != false) { 543 dpButtons[i++] = [key, getMappedKey(v), $("<div class='button " + key + "'></div>")]; 544 } 545 }); 546 547 $.each(dpButtons, function () { 548 dpad.append(this[2]); 549 }); 550 $(document.body).append(dpad); 551 552 // Virtual Pad Buttons 553 i = 0; 554 $.each(R.Engine.options.virtualButtons, function (key, v) { 555 if (R.Engine.options.virtualButtons[key] != false) { 556 vbButtons[i++] = [key, getMappedKey(v), $("<div class='button " + key + "'>" + key + "</div>")]; 557 } 558 }); 559 560 $.each(vbButtons, function () { 561 vpad.append(this[2]); 562 }); 563 $(document.body).append(vpad); 564 565 // Wire up the buttons to fire keyboard events on the context 566 var allButtons = dpButtons.concat(vbButtons); 567 $.each(allButtons, function () { 568 var key = this; 569 key[2].bind(downEvent,function () { 570 R.debug.Console.debug("virtual keydown: " + key[1]); 571 var e = $.Event("keydown", { 572 which:key[1] 573 }); 574 R.Engine.getDefaultContext().jQ().trigger(e); 575 }).bind(upEvent, function () { 576 R.debug.Console.debug("virtual keyup: " + key[1]); 577 var e = $.Event("keyup", { 578 which:key[1] 579 }); 580 R.Engine.getDefaultContext().jQ().trigger(e); 581 }); 582 }); 583 } 584 }); 585 586