1 /* 2 * Karma Framework 3 * http://wiki.sugarlabs.org/go/Karma 4 * 5 * Copyright (c) 2009 6 * Felipe López Toledo zer.subzero@gmail.com 7 * Bryan W Berry bryan@olenepal.org 8 * 9 * Under MIT License: 10 * Permission is hereby granted, free of charge, to any person 11 * obtaining a copy of this software and associated documentation 12 * files (the "Software"), to deal in the Software without 13 * restriction, including without limitation the rights to use, 14 * copy, modify, merge, publish, distribute, sublicense, and/or sell 15 * copies of the Software, and to permit persons to whom the 16 * Software is furnished to do so, subject to the following 17 * conditions: 18 * 19 * The above copyright notice and this permission notice shall be 20 * included in all copies or substantial portions of the Software. 21 * 22 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 24 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 26 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 27 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 29 * OTHER DEALINGS IN THE SOFTWARE. 30 */ 31 32 /** 33 * @fileOverview Contains karma library 34 * @version 0.1 35 * @author Felipe Lopez Toledo <zer.subzero@gmail.com> 36 */ 37 38 39 /** 40 * See <a href="http://jquery.com">jQuery</a>. 41 * @class 42 * @name jQuery 43 * @exports $ as jQuery 44 */ 45 46 (function ($) { 47 //helpers 48 /** 49 Checks if the argument 'arg' is set and if its type is 'type'.<br> 50 1. if arg is set: it returns 'toReturn' if specified, otherwise it returns 51 'true' 52 2. if arg is not set: it returns 'false' 53 @param arg The param to check 54 @param {Object} [type] The expeted type of 'arg' 55 @param [toReturn] object or value to return in case 1 56 @returns true | false | toReturn 57 @example 58 var msg = "hi"; 59 valid(msg); //returns true 60 valid(msg, "String" ); //returns true 61 valid(msg, "Number"); //returns false 62 valid(msg, "String",false ); //returns false 63 valid(msg, "String", "hello" ); //returns "hello" 64 valid(msg123); //returns false 65 **/ 66 var valid = function ( arg, type, toReturn ) { 67 if ( type ) { 68 if ( typeof arg === type ) { 69 if ( toReturn ) 70 return toReturn; 71 return true; 72 } 73 return false 74 } 75 if ( typeof arg !== "undefined" && arg!== "null" ) return true; 76 return false; 77 } 78 /** 79 Clones an object 80 @param {object} obj The source object 81 @returns {object} The cloned object 82 **/ 83 var clone = function( obj ){ 84 if(obj == null || typeof(obj) != 'object') 85 return obj; 86 var temp = new obj.constructor(); 87 for(var key in obj) 88 temp[ key ] = clone( obj[ key ] ); 89 return temp; 90 } 91 92 /** 93 Karma 94 @name Karma 95 @class Represents a Karma (master) object. 96 @param {String | Object } options Constructor arguments 97 @param {String | Object } [options.container] Target DIV-class that will contain 98 any canvas element created using Karma functions 99 @param {String} [options.language] 100 */ 101 var Karma = function( options ) { 102 var that = this; 103 this.version = "0.01"; 104 // 105 //relative path to the po, images, sounds, etc. from the html 106 //defined here: http://wiki.sugarlabs.org/go/Karma/Bundle_layout 107 //localized is recalculated inside localizeContent ( $ = language.lang ) 108 this.paths = { 109 po: "po/", 110 images: { 111 localized: "assets/$/images/", 112 generic: "assets/generic/images/" 113 }, 114 sounds: { 115 localized: "assets/$/sounds/", 116 generic: "assets/generic/sounds/" 117 }, 118 videos: { 119 localized: "assets/$/videos/", 120 generic: "assets/generic/videos/" 121 } 122 }; 123 this.supportedLangFileTypes = [ 124 { ext: "po", type: 'application/x-po' }, 125 { ext: "json", type: 'application/json'} 126 ]; 127 // 128 //PRIVATE STUFF start 129 /** 130 Gets the language acording to the browser language 131 @returns {Object} <br> 132 lang: countryCode and langCode (if specified) 133 langCode*: language code represented as xx, example: en.<br> 134 countryCode*: country code represented as YY, example: US.<br> 135 *optional 136 **/ 137 var getLanguage = function () { 138 //console.log += navigator.language +"\n"; 139 var lang = navigator.language || navigator.browserLanguage; //mozilla/ie 140 lang = lang.replace(/_/, '-').toLowerCase(); 141 if (lang.length > 3 ) { 142 var country = lang.substring(3, 5); 143 lang = lang.substring(0, 2); 144 if ( country.match(/[^a-zA-Z]/) === null ) { 145 country = country.toUpperCase(); 146 return { 147 "lang": lang + "-" + country, 148 "langCode": lang, 149 "countryCode": country 150 }; 151 } 152 } 153 return { "lang": lang }; 154 } 155 /** 156 Creates a new Gettext object and returns a shortcut function to localise 157 defined strings.<br>We use karma.Gettext.js it's a modification of 158 <a href=http://jsgettext.berlios.de/doc/html/Gettext.html> 159 Gettext.js</a> from beril OS. 160 @requires karma.Gettext.js 161 @param {Object} options The arguments of the Gettext constructor 162 @returns {Function} A generic function to call Gettext functions 163 **/ 164 var i18nWrapper = function ( options ) { 165 var gt = new Gettext( options ); 166 if ( typeof ( gt ) === 'undefined' ) 167 throw new Error("Unable to initialize Gettext object"); 168 return (function (str1, str2, str3, str4 ) { 169 var n, context, singular, plural; 170 if (typeof(str4) != 'undefined') { 171 // number, context, singular, plural 172 return gt.npgettext(str2, str3, str4, str1); 173 } else if (typeof(str3) != 'undefined') { 174 // number, singular, plural 175 return gt.ngettext(str2, str3, str1); 176 } else if (typeof(str2) != 'undefined') { 177 // context, msgid 178 return gt.pgettext(str1, str2); 179 } else if (typeof(str1) != 'undefined') { 180 // msgid 181 return gt.gettext(str1); 182 } else { 183 // nothing passed in; return blank string. 184 // XXX: we could error here, but that may cause more harm than good. 185 return ''; 186 } 187 }); 188 }; 189 /** 190 Localises the inline html content and it creates the localised paths for 191 "images", "sounds" and "videos". 192 <b>Note:</b>Inline html localisation under development<br> 193 @param {String} lang The language that will be used to localise the content 194 @see <a href="http://wiki.sugarlabs.org/go/Karma/Bundle_layout"> 195 Karma Bundle_layout</a> 196 **/ 197 var localiseContent = function ( lang ) { 198 199 var toFix = ["images", "sounds", "videos"]; 200 for (var i = 0; i < toFix.length; i++) { 201 that.paths[ toFix[ i ] ].localized = that.paths[ 202 toFix[ i ] ].localized.replace('\$', lang ); 203 } 204 //dirty hack to support {lang}_AudioFile 205 var prefix = lang+"_"; 206 that.paths[ "sounds" ].localized+=prefix; 207 } 208 /** 209 It will attempt to load a language file, the posible languages are defined 210 on language.alternatives. 211 <p>The language file type could be: .po (Pootle) or .json (JSON). The 212 precedence between file types is defined according to which is defined first 213 in supportedLangFileTypes. By default Pootle files has precedence over JSON, 214 files.</p> 215 @see Karma 216 @returns {String} The name of the language file loaded. Example: en-US.po 217 **/ 218 var loadAlternatives = function ( ) { 219 var loaded = undefined; 220 var tryNext = true; 221 //try to load the po or json language file if it exists. 222 //the lang order is acording to options.language.alternatives 223 //the type (po or json or ...) is defined in supportedLangFileTypes 224 $.each( that.language.alternatives, function ( c, lang ) { 225 for (var i=0; i < that.supportedLangFileTypes.length 226 && tryNext === true; i++) { 227 $.ajax({ 228 url: that.paths.po + lang + "." + 229 that.supportedLangFileTypes[i].ext, 230 cache: true, 231 dataType: "text", 232 async: false, //important: touch it at your own risk 233 success: function( data, textStatus ){ 234 235 loaded = lang + "." + 236 that.supportedLangFileTypes[i].ext; 237 //i18n 238 //we pass the data so we avoid re-loading the file 239 //creates the shorcout 240 that.i18n.root[ that.i18n.shortcut ] = i18nWrapper( 241 { 242 domain : lang, 243 file : { 244 type: that.supportedLangFileTypes[i].type, 245 uri: this.url, data: data 246 } 247 } 248 ); 249 localiseContent( lang ); 250 tryNext = false; 251 }, 252 error: function ( XHR, textStatus, errorThrown ) { 253 //the file doesn't exist or it wasn't possible to load it 254 tryNext = true; 255 } 256 }); 257 return tryNext; 258 } 259 }); 260 return loaded; 261 } 262 //PRIVATE STUFF end 263 // default options 264 var defaultOptions ={ 265 container: "#karma-main", 266 language: { 267 lang: undefined, 268 alternatives: ['en-US', 'en'], 269 countryCode: undefined, 270 langCode: undefined, 271 }, 272 i18n: { 273 root: self, // self is global 274 shortcut: "_" 275 } 276 }; 277 // 278 this.library = { "images": [], "sounds": [], "videos":[], "shapes":[] }; 279 280 //initializes the defaultOptions argument 281 //1 argument: string. assume it's the container 282 if ( typeof options === "string" ) { 283 options = { container: options }; 284 options.language = getLanguage() ; 285 } else if (typeof options === "object" ){ 286 if ( typeof options.lang === "string" ) { 287 //if language is string, assume it's the language.lang 288 options.language = { lang: options.lang }; 289 } 290 } 291 $.extend( true, defaultOptions, options ); 292 // 293 //copy defaultOptions to this, we use this.xyz instead this.defaultOptions.xyz 294 for (var i in defaultOptions ) { 295 this[ i ] = defaultOptions[i]; 296 } 297 298 //initializes i18n 299 //add the localized language to the language.alternatives 300 if ( typeof this.language.countryCode !== "undefined" ) { 301 this.language.alternatives.unshift( 302 this.language.langCode, 303 this.language.countryCode 304 ); 305 } 306 if ( typeof this.language.lang !== "undefined" ) { 307 this.language.alternatives.unshift( this.language.lang ); 308 } 309 //try to load the localized lang file (po or json or ...) 310 this.language.fileLoaded = loadAlternatives( ); 311 //initializes the container 312 if ( typeof this.container === "string" ) { 313 this.container = $( this.container )[ 0 ]; 314 if ( !valid(this.container) ) delete this.container; 315 } 316 317 gk = { 318 "paths": this.paths 319 } 320 this.surfaces = {}; 321 } 322 323 /** 324 @memberOf Karma 325 @namespace Geometry functions. 326 **/ 327 Karma.prototype.geometry = { 328 /** 329 Converts a value from degrees to radians. 330 @param {Number} angle The angle in degrees 331 @returns {Number} The The angle in radians 332 **/ 333 radians : function( angle ){ 334 return ( angle / 180 ) * Math.PI; 335 }, 336 /** 337 Gets the square of the Euclidian (ordinary) distance between 2 points. 338 @param {Number} Point Point No. 0 339 @param {Number} Point Point No. 1 340 @returns {Number} The square of the Euclidian distance 341 **/ 342 distance2 : function ( p0, p1 ) { 343 return (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p1.y) * (p1.y - p1.y); 344 }, 345 /** 346 Gets the Euclidian (ordinary) distance between 2 points.<br> 347 <b>Warning:</b> It's slower than distance2 function 348 @param {Number} Point Point No. 0 349 @param {Number} Point Point No. 1 350 @returns {Number} The Euclidian distance 351 **/ 352 distance : function ( p0, p1 ) { 353 return Math.sqrt( Karma.prototype.geometry.distance2( p0, p1 ) ); 354 } 355 } 356 /** 357 @memberOf Karma 358 @namespace Graphics functions. 359 **/ 360 Karma.prototype.graphics = { 361 /** 362 Creates a new rectangle. It's a shortcut for calling 'new KRectangle(..)'. 363 **/ 364 rectangle: function ( args ) { return new KRectangle( args ); }, 365 /** 366 Creates a new circle. It's a shortcut for calling 'new KCircle(..)'. 367 **/ 368 circle: function ( args ) { return new KCircle( args ); } 369 } 370 /** 371 @memberOf Karma 372 @namespace Math functions. 373 **/ 374 Karma.prototype.math = { 375 /** 376 Generates a random bumber between lower bound and upper bound inclusive. 377 @param {Number} lower The lower bound 378 @param {Number} upper The upper bound 379 @returns {Number} The generated number 380 **/ 381 rand : function ( lower, upper ){ 382 return Math.round ( Math.random() * (upper - lower) + lower ); 383 } 384 } 385 //FIXME 386 //everything inside karma.graphics is exported to karma.prototype 387 $.extend( Karma.prototype, Karma.prototype.graphics); 388 // 389 /** 390 @param {Object} [toLoad] The Object that has the arrays for preloading. 391 @param {Array} [toLoad.images] The images 392 @param {Array} [toLoad.sounds] The sounds 393 @param {Array} [toLoad.videos] The videos 394 @memberOf Karma 395 @returns {Object} this 396 **/ 397 Karma.prototype.init = function( toLoad ) { 398 this.pendingToLoad = toLoad; 399 return this; //chaining :) 400 } 401 402 /** 403 Main function. Any Karma function call should be inside the callback function. 404 The callback function will be executed when the preloading finishes. 405 @param {Function} cb The callback funtion 406 @memberOf Karma 407 @see Karma#init 408 **/ 409 Karma.prototype.main = function ( cb ) { 410 if ( valid( this.pendingToLoad ) ) { 411 //loader 412 var loaderDiv = $("body").append('<div id=\"karma-loader\">Karma is \ 413 loading ...<div id=\"karma-loader\" class=\"status\"></div></div>'); 414 var statusDiv = $("#karma-loader .status"); 415 416 var statusUpdate = function ( current, error, total) { 417 statusDiv.html(current + "/" + total + (error > 0 ? " [ "+error+" ]":'')); 418 } 419 420 var that = this; 421 var categories = ["images", "sounds", "videos" ]; 422 var counters = { "loaded":0, "error": 0 }; 423 var totalItems = 0; 424 //creates the surfaces 425 if ( valid( this.pendingToLoad[ "surfaces" ] ) ) { 426 $.each (this.pendingToLoad[ "surfaces" ], function( key, config ){ 427 Karma.prototype.surface.call( that, config ); 428 }); 429 } 430 statusUpdate( 0, 0, totalItems); 431 //get the total items 432 for ( var i=0; i < categories.length; i++ ) { 433 if ( valid ( this.pendingToLoad[ categories[ i ] ] ) ) { 434 totalItems += this.pendingToLoad[ categories[ i ] ].length; 435 } 436 } 437 438 /** 439 callback to check if all the items were loaded or got an error when 440 loading 441 **/ 442 var errors=[]; 443 var checkAllLoaded = function ( ev ) { 444 if ( ev.type === "load") counters.loaded += 1; 445 else { 446 errors.push( ev.target.src ); 447 counters.error += 1; 448 } 449 statusUpdate( counters.loaded, counters.error, totalItems); 450 if ( counters.loaded + counters.error === totalItems ) { 451 if ( counters.error > 0 ){ 452 throw ( "Media files not found: " + errors ); 453 } 454 $("#karma-loader:hiden:first").fadeOut("slow",function(){ 455 $(this).remove();}); 456 if ( cb ) cb(); 457 } 458 } 459 460 for ( var i=0; i < categories.length; i++ ) { 461 var category = categories[ i ]; 462 if ( valid ( this.pendingToLoad[ category ] ) ) { 463 //load all the category elements 464 var type = category.substr( 0, category.length-1 ) 465 $.each (this.pendingToLoad[ category ], function( key, config ){ 466 var name = config.name; 467 delete config.name; 468 //register the elements into the library 469 that.library[ category ][ name ] = Karma.prototype[ type ]( 470 config 471 ); 472 that.library[ category ][ name ].media.addEventListener( 473 "load",checkAllLoaded,false 474 ); 475 that.library[ category ][ name ].media.addEventListener( 476 "error",checkAllLoaded,false 477 ); 478 }); 479 } 480 } 481 }else { 482 if ( cb ) cb(); 483 } 484 } 485 /** 486 A shortcut for calling 'KImage( )' 487 @see KImage 488 @memberOf Karma 489 @returns {Object} new instance of KImage object 490 **/ 491 Karma.prototype.image = function ( args ) { return new KImage( args ) }; 492 /** 493 A shortcut for calling 'KSound( )' 494 @see KSound 495 @memberOf Karma 496 @returns {Object} new instance of KSound object 497 **/ 498 Karma.prototype.sound = function ( args ) { return new KSound( args ) }; 499 /** 500 A shortcut for calling 'KVideo( )' 501 @see KVideo 502 @memberOf Karma 503 @returns {Object} new instance of KVideo object 504 **/ 505 Karma.prototype.video = function ( args ) { alert("Not implemented yet"); }; 506 /** 507 A shortcut for calling 'KGroup( )' 508 @see KGroup 509 @memberOf Karma 510 @returns {Object} new instance of KGroup object 511 **/ 512 Karma.prototype.group = function ( args ) { return new KGroup( args ) }; 513 /** 514 A shortcut for calling 'KButton( )' 515 @see KButton 516 @memberOf Karma 517 @returns {Object} new instance of KButton object 518 **/ 519 Karma.prototype.button = function ( args ) { return new KButton( args ) }; 520 /** 521 A shortcut for calling 'KSurface(.. )'. 522 @see KSurface 523 @memberOf Karma 524 @returns {Object} new instance of KSurface object 525 **/ 526 Karma.prototype.surface = function ( options ) { 527 if ( !valid(options, "object") ){ 528 var options = { name: "ksurface-"+ ( this.surfaces.length + 1 ) }; 529 } 530 options.mainContainer = this.container; 531 options.paths = this.paths; 532 this.surfaces[ options.name ] = new KSurface( options ); 533 return this.surface[ options.name ]; 534 } 535 536 /** 537 Mouse 538 **/ 539 var mouse = {}; 540 /** 541 Gets the 'x' and 'y' mouse coordinates relatives to the canvas 542 @returns {Object} An Object with 'x' and 'y' attributes 543 **/ 544 mouse.getRelativeCanvasPosition = function ( ev ) { 545 if ( !ev ) return; 546 var xy ={x:0, y:0}; 547 xy.x = ev.layerX; 548 xy.y = ev.layerY; 549 return xy; 550 } 551 552 //Events stuff 553 var master ={} 554 master.buttons =[]; 555 var handleEvents = function( ev ) { 556 var xy = mouse.getRelativeCanvasPosition( ev ); 557 for (var i in master.buttons) { 558 if (master.buttons[i].isPointInPath( xy.x, xy.y) ){ 559 master.buttons[i].onClick( ev ); 560 } 561 } 562 /*switch(ev.type){ 563 case "click": break; 564 }*/ 565 /*var s=""; 566 for (var i in ev) { 567 s+=i+"="+ev[i]+"\n"; 568 } 569 alert(s);*/ 570 }; 571 572 /** 573 Master class creator. It will merge all the properties and methods of the 574 recived arguments (objects) into one new class that wil be returned. 575 @returns {Object} The new class 576 **/ 577 var Class = function ( ) { 578 var log=""; 579 var parents = []; 580 for ( var i = 0; i < arguments.length; i++ ) { 581 if ( arguments[i].prototype && arguments[i].init ) { 582 parents.push( arguments[i].init ); 583 } 584 } 585 var o = function ( ) { 586 //we inject all the init functions 587 /*for ( var i = 0; i < this.__parents.length; i++ ) { 588 this.__parents[ i ].apply ( this, arguments ); 589 }*/ 590 //call the real class init 591 if ( this.init ) 592 this.init.apply( this, arguments ); 593 }; 594 595 o.prototype ={}; 596 var a; 597 for ( var i =0; i < arguments.length; i++) { 598 a = arguments[i]; 599 log += "**" + typeof a+"\n"; 600 //if ( a === "function") { 601 if (a.prototype) { 602 for ( var j in a.prototype ) { 603 //log += j+" = "+a.prototype[j]+"\n"; 604 o[ j ] = o.prototype[ j ] = a.prototype [ j ]; 605 } 606 } 607 else { 608 //if ( typeof a === "object") { 609 for (var j in a) { 610 //log += j+" = "+a[j]+"\n"; 611 o[ j ] = o.prototype[ j ] = a [ j ]; 612 } 613 } 614 615 } 616 o.prototype.__parents = parents; 617 //alert( log ); 618 return o; //(function ( ) { return new o( arguments );}); 619 }; 620 621 /** 622 Creates a new surface. A surface is a 'canvas' element with additional methods 623 that makes easier its manipulation. <br> 624 There are 2 ways to create a new KSurface: 625 <ol> 626 <li><b>Using an existing canvas element:</b>You must provide at least 627 the 'canvas' parameter. The 'name' is optional (if it's not provided the 628 'canvas' parameter will be used). 629 </li> 630 <li><b>Creating a new canvas element:</b> A new 'canvas' element will be 631 created and it will be appended to the specific 'container'. 632 You must provide at least the 'name' and 'container' parameters. 633 </li> 634 </ol> 635 @class KSurface class 636 @param {object} options Constructor options. 637 @param {string} [options.name] The desired name for the surface. The value must 638 be unique among others KSurfaces-name objects. 639 @param {string} [options.canvas] The name of the element. Commonly the 640 canvas-id value. 641 @param {string | object} [options.container] The the name of the container 642 element. Commonly a div-id value. 643 @param {number} [width=100] The width of the canvas. 644 @param {number} [height=100] The height of the canvas. 645 @param {number} [fps=24] The frames per second for any refresh operation. 646 @param {boolean} [visible=true] 'true' if the content is visible (will be drawn). 647 @memberOf_ Karma 648 **/ 649 var KSurface = Class( 650 { 651 init: function( options ){ 652 //fix the container 653 if ( valid( options.container, "string" ) && !valid( options.canvas) 654 ) { 655 var name=options.container; 656 options.container = $( options.container )[ 0 ]; 657 if ( !valid (options.container) ){ 658 // the container must be created inside the mainContainer 659 if ( !valid( options.mainContainer ) ){ 660 throw ("You need to create the Karma master container"); 661 } 662 var div = document.createElement("div"); 663 div.id = name; 664 options.container=options.mainContainer.appendChild( div ); 665 } 666 }else { 667 if ( !valid( options.mainContainer ) ){ 668 throw ("You need to create the Karma master container"); 669 } 670 options.container = options.mainContainer; 671 } 672 673 var defaultOptions = { 674 //mainContainer: '',//must be overwritten by Karma.container 675 name: '',//must be overwritten by the Karma.surface OR user 676 container: '', //must be overwritten by Karma.container OR user 677 678 width: 100, 679 height: 100, 680 fps: 24, 681 visible: true 682 } 683 $.extend( this, defaultOptions, options); 684 685 if ( !this.canvas ) { 686 this.canvas = document.createElement("canvas"); 687 this.canvas.width = this.width; 688 this.canvas.height = this.height; 689 this.canvas.id = this.name; 690 this.container.appendChild( this.canvas ); 691 }else { 692 this.canvas = document.getElementById( options.canvas ); 693 if ( !this.canvas ){ 694 throw new Error ("The canvas id doesn't exist"); 695 } 696 this.width = this.canvas.width; 697 this.height = this.canvas.height; 698 if (!this.name){ 699 this.name = this.canvas.id; 700 } 701 } 702 if ( this.canvas.getContext ) { 703 this.ctx = this.canvas.getContext("2d"); 704 }else { 705 throw new Error ("Your browser doesn't support canvas, \ 706 try the newest Firefox, Safari or Google Chrome"); 707 } 708 //ctx methods chaining stuff 709 var toChain = [ 710 "globalAlpha", "globalCompositeOperation", "lineWidth", "lineCap", 711 "lineJoin", "miterLimit", "font", "textAlign", "textBaseline", "save", 712 "restore", "scale", "rotate", "translate", "transform", "setTransform", 713 "clearRect", "fillRect", "strokeRect", "beginPath", "closePath", 714 "moveTo", "lineTo", "quadraticCurveTo", "bezierCurveTo", "arcTo", 715 "arc", "rect", "fill", "stroke", "clip", "fillText", "strokeText", 716 "measureText", "isPointInPath", "strokeStyle", "fillStyle", 717 "createLinearGradient", "createRadialGradient", "createPattern", 718 "shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", 719 //"mozTextStyle", "mozDrawText", "mozMeasureText", "mozPathText", 720 "mozTextAlongPath", "drawImage", "getImageData", "putImageData", 721 "createImageData", "drawWindow" 722 ]; 723 var that=this; 724 var chainMaker = function ( name ){ 725 that[ name ] = function ( ){ 726 var type = typeof that.ctx[name]; 727 if ( type === "function") { 728 that.ctx[ name ].apply( that.ctx, arguments ); 729 }else if ( type === "string" ){ 730 that.ctx[ name ] = arguments[0]; 731 }else { 732 throw ("wtf?!: impossible to chain " + name + "!"); 733 } 734 return that; 735 } 736 } 737 for (var i=0; i<toChain.length; i++){ 738 chainMaker( toChain[ i ] ); 739 } 740 741 742 //events 743 this.canvas.addEventListener("contextmenu", function(ev){ 744 // 745 },false 746 ); 747 this.canvas.addEventListener("click", 748 handleEvents, 749 false 750 ); 751 752 }, 753 /** 754 Adds an event listener to the surface 755 @param {string} type Event type 756 @param {function} cb Function call back 757 @param {boolean} [bubble=false] If the event must be captured on 758 bubbling phase 759 **/ 760 addEventListener : function ( type, cb, bubble ) { 761 this.canvas.addEventListener( type, cb, bubble || false ); 762 }, 763 /** 764 Removes an event listener attached to the surface 765 @param {string} type Event type 766 @param {function} cb Function call back 767 @param {boolean} [bubble=false] If the event must be captured on 768 bubbling phase 769 **/ 770 removeEventListener : function ( type, cb, bubble ) { 771 this.canvas.removeEventListener( type, cb, bubble || false ); 772 }, 773 /** 774 Clears a rectangular area within the canvas 775 @param {Number} [x=0] Start position of x 776 @param {Number} [y=0] Start position of y 777 @param {Number} [width=canvas width] Square width 778 @param {Number} [height=canvas height] Square height 779 **/ 780 clear : function ( x, y, width, height ) { 781 this.ctx.clearRect( 782 x || 0, 783 y || 0, 784 width || this.width, 785 height || this.height 786 ); 787 return this; 788 }, 789 draw: function ( ) { 790 791 } 792 } 793 ); 794 795 /** 796 Karma basic Object 797 @class The basic Karma object 798 @param {Object} [options] Options 799 @param {String} [options.localized = true] The object will be localized 800 @memberOf_ Karma 801 **/ 802 var KObject = Class( 803 { 804 init: function ( options ) { 805 if ( valid(options.localized, "boolean" ) ) { 806 this.localized = options.localized; 807 }else { 808 this.localized = true; 809 } 810 } 811 } 812 ); 813 /** 814 Graphics basic Object 815 @class General methods for any Graphic object 816 @param {object} [options] Options 817 @param {number} [options.x = 0] The 'x' position of the object 818 @param {number} [options.y = 0] The 'y' position of the object 819 @param {number} [options.z = 0] The 'z' index of the object 820 @param {number} [options.width = 0] The 'width' of the object 821 @param {number} [options.height = 0] The 'height' of the object 822 @param {boolean} [options.visible = true] Defines if the object will be visible 823 when drawing 824 @augments KObject 825 @memberOf_ Karma 826 **/ 827 var KGraphic = Class( 828 KObject, 829 { 830 init: function ( options ) { 831 if ( valid( options.localized ) ) 832 KObject.init.call(this, options.localized ); 833 var defaultOptions = { 834 x : 0, 835 y : 0, 836 z : 0, 837 width: 0, 838 height: 0, 839 visible : true 840 } 841 $.extend( this, defaultOptions, options); 842 }, 843 /** 844 @memberOf KGraphic 845 Determines if the 'x' and 'y' coodinates are inside the object. 846 @returns {boolean} 'true' if the coordinates are inside or on the border 847 of the object, otherwise 'false' 848 **/ 849 isPointInPath : function( x, y ) { 850 return (this.x <= x && (this.x + this.width) >= x && 851 this.y <= y && (this.y+this.width)>=y); 852 }, 853 addEventListener : function (type, cb, bubble) { 854 //FIXME 855 } 856 } 857 ); 858 /** 859 An object that collects multiple KGraphic objects. Supports multiple objects. 860 @class An object that collects multiple KGraphic objects 861 @augments KGraphic 862 @memberOf_ Karma 863 **/ 864 var KGroup = Class( 865 KGraphic, 866 { 867 init: function ( options ) { 868 this.childNodes = []; 869 this.sorted = true; 870 }, 871 /** 872 @memberOf KGroup 873 Adds each argument passed to the funtion to chilNodes. 874 @param {Array:KGraphic} arguments The elements to add to childNodes 875 @see KGroup#draw 876 **/ 877 appendChild : function ( ) { 878 if ( arguments.length > 0 ) { 879 for ( var i = 0; i< arguments.length; i++) { 880 this.childNodes.push ( arguments[ i ] ); 881 } 882 this.sorted = false; 883 884 } 885 }, 886 removeChild: function () { 887 //FIXME 888 }, 889 /** 890 @memberOf_ KGroup 891 Draws all the elements in childNodes. The elements are drawn according 892 to its 'z' (z-index) value. 893 @see KGroup#appendChild 894 **/ 895 draw : function() { 896 if ( this.visible && this.childNodes.length > 0 ) { 897 if ( !this.sorted ) { 898 this.childNodes.sort ( function ( g1, g2 ) { 899 return g1.z - g2.z; 900 }); 901 this.sorted = true; 902 } 903 for (var i in this.childNodes) { 904 this.childNodes[ i ].draw(); 905 } 906 } 907 }, 908 isPointInPath : function() { 909 //TODO 910 } 911 912 } 913 ); 914 915 /** 916 Graphics basic Media object. 917 @class General methods for any Graphic object 918 @param {String} file The name of the file that must be loaded 919 @param {String} type 'image', 'sound' or 'video' 920 @param {Object} [options] Options that will be passed to the media element 921 constructor 922 @augments KObject 923 @memberOf_ Karma 924 **/ 925 var KMedia = Class( 926 KObject, 927 { 928 init: function (file, type, options ) { 929 if ( !file || !type ) { 930 throw new Error ("file and type needed"); 931 } 932 if ( valid ( options ) ) 933 KObject.init.call (this, options); 934 935 this.file = file; 936 this.type = type; 937 938 this.status = undefined; 939 this.path = undefined; 940 this.media = undefined; 941 switch ( this.type ) { 942 case "image": this.media = new Image(); break; 943 case "sound": this.media = new Audio(); break; 944 default: throw new Error ("Media type not supported"); 945 } 946 this.path = gk.paths[ this.type + "s" ][ 947 this.localized ? "localized": "generic" 948 ]; 949 this.media.src = this.src = this.path + this.file; 950 951 var that = this; 952 this.media.addEventListener("load", 953 function (e) { that.status = "loaded";}, false); 954 this.media.addEventListener("error", 955 function (e) { that.status = "error";}, false); 956 this.media.addEventListener("abort", 957 function (e) { that.status = "aborted";}, false); 958 } 959 } 960 ); 961 962 /** 963 Image object 964 @class General methods for any Image object 965 @param {Object} options Constructor arguments. 966 @param {Object} options.file The image file that will be loaded. 967 @augments KGraphic 968 @augments KMedia 969 @memberOf_ Karma 970 **/ 971 var KImage = Class( 972 KGraphic, 973 KMedia, 974 { 975 init: function ( options ) { 976 if ( valid ( options, "string" ) ) { 977 options = { file:options }; 978 } 979 if ( valid( options ) ) { 980 KGraphic.init.call(this, options); 981 KMedia.init.call(this, options.file, "image", options ); 982 } 983 var defaultOptions = { 984 //w : undefined, 985 //h : undefined, 986 }; 987 $.extend( this, defaultOptions, options); 988 }, 989 draw : function( ctx, x, y ) { 990 if ( this.visible && this.isReady() ) { 991 this.x = x || this.x; 992 this.y = y || this.y; 993 ctx.drawImage( this.media, this.x , this.y ); 994 } 995 }, 996 /** 997 Checks if the image has been loaded and fully decoded. 998 @returns {boolean} 'true' or 'false' 999 **/ 1000 isReady : function () { 1001 if ( !this.media.complete ) return false; 1002 if ( !this.media.naturalWidth || this.media.naturalWidth === 0) 1003 return false; 1004 return true; 1005 } 1006 } 1007 ); 1008 1009 /** 1010 Sound object 1011 @class General methods for any Sound object 1012 @param {Object} options Constructor arguments. 1013 @param {Object} options.file The image file that will be loaded. 1014 @augments KMedia 1015 @memberOf_ Karma 1016 **/ 1017 var KSound = Class( 1018 /**@lends_ KMedia*/ 1019 KMedia, 1020 { 1021 init: function( options ) { 1022 if ( valid ( options, "string" ) ) { 1023 options = { file: options }; 1024 } 1025 if ( valid( options ) ) { 1026 KMedia.init.call(this, options.file, "sound", options ); 1027 //next line is important! 1028 this.media.load(); 1029 } 1030 }, 1031 /** 1032 Checks if the image has been loaded and fully decoded. 1033 @returns {boolean} 'true' or 'false' 1034 **/ 1035 isReady: function () { 1036 return this.readyState === 4; 1037 }, 1038 play: function (){ 1039 //hack to fix the audio "stuttering" problem 1040 //more info: https://bugs.launchpad.net/karma/+bug/426108 1041 this.media.currentTime = 0.1; 1042 this.media.play(); 1043 } 1044 } 1045 ); 1046 1047 /** 1048 Shape object 1049 @class General methods for any Shape object 1050 @param {object} options Constructor arguments. 1051 @param {boolean} [options.fill=true] 'true' if the Shape will be filled when 1052 drawing. 1053 @param {boolean} [options.stroke=true] 'true' if the stroke will be drawn. 1054 @param {color|string} [options.fillStyle="#000"] The fill style of the shape. 1055 @param {color|string} [options.strokeStyle="#000"] The stroke style of the shape. 1056 @augments KMedia 1057 @memberOf_ Karma 1058 **/ 1059 var KShape = Class( 1060 /**@lends_ KGraphic*/ 1061 KGraphic, 1062 { 1063 init : function ( options ) { 1064 if ( valid( options ) ) { 1065 KGraphic.init.call(this, options ); 1066 } 1067 var defaultOptions = { 1068 fill: true, 1069 stroke: true, 1070 fillStyle: '#000', 1071 strokeStyle: '#000', 1072 openPath : false 1073 } 1074 $.extend( this, defaultOptions, options); 1075 }, 1076 draw : function ( ctx ) { 1077 if ( this.visible ) { 1078 ctx.fillStyle = this.fillStyle 1079 ctx.strokeStyle= this.strokeStyle 1080 if ( this.fill ) 1081 ctx.fill(); 1082 if ( this.stroke ) 1083 ctx.stroke(); 1084 if ( !this.openPath ) 1085 ctx.closePath(); 1086 ctx.restore(); 1087 } 1088 } 1089 } 1090 ); 1091 /** 1092 Rectangle object 1093 @class General methods for a rectangle object 1094 @param {object} options Constructor arguments. 1095 @param {number} options.x The 'x' position. 1096 @param {number} options.y The 'y' position. 1097 @param {number} options.w The width of the rectangle. 1098 @param {number} options.h The height of the rectangle. 1099 @augments KShape 1100 @memberOf_ Karma 1101 **/ 1102 var KRectangle = Class( 1103 KShape, 1104 { 1105 init : function ( options ) { 1106 //ADD multiple constructors support 1107 //x,y,w,h 1108 //w,y,w,h,options 1109 if ( valid( options ) ) { 1110 KShape.init.call(this, options ); 1111 } 1112 }, 1113 draw : function ( ctx ) { 1114 if ( this.visible ) { 1115 ctx.save(); 1116 ctx.beginPath(); 1117 ctx.rect( this.x, this.y, this.width, this.height); 1118 KShape.draw.call( this, ctx ); 1119 } 1120 }, 1121 clear : function ( ) { 1122 if ( this.visible ) { 1123 1124 } 1125 } 1126 } 1127 1128 ); 1129 1130 /**@class_ */ 1131 var KButton = Class( 1132 /**@lends_ KGraphic*/ 1133 KGraphic, 1134 { 1135 1136 init : function ( options ) { 1137 //ADD multiple constructors support 1138 //x,y,w,h 1139 //w,y,w,h,options 1140 if ( valid( options ) ) { 1141 KGraphic.init.call(this, options ); 1142 } 1143 this.name = options.name; 1144 master.buttons.push(this); 1145 }, 1146 draw : function ( ) {}, 1147 onClick : function() { } //callback 1148 } 1149 ); 1150 // 1151 /** 1152 Karma function. It's a shotcut for calling 'new Karma(..)' 1153 @param [options] Options passed to the Karma constructor 1154 @returns {Object} a new Karma object 1155 @see Karma 1156 **/ 1157 $.karma = function (options) { 1158 var k =new Karma( options ); 1159 return k; 1160 } 1161 })(jQuery);