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 * @name jQuery 42 * @exports $ as jQuery 43 */ 44 45 (function ($) { 46 //helpers 47 /** 48 Checks if the argument 'arg' is set and if its type is 'type'.<br> 49 1. if arg is set: it returns 'toReturn' if specified, otherwise it returns 50 'true' 51 2. if arg is not set: it returns 'false' 52 @param arg The param to check 53 @param {Object} [type] The expeted type of 'arg' 54 @param [toReturn] object or value to return in case 1 55 @returns true | false | toReturn 56 @example 57 var msg = "hi"; 58 valid(msg); //returns true 59 valid(msg, "String" ); //returns true 60 valid(msg, "Number"); //returns false 61 valid(msg, "String",false ); //returns false 62 valid(msg, "String", "hello" ); //returns "hello" 63 valid(msg123); //returns false 64 **/ 65 var valid = function ( arg, type, toReturn ) { 66 if ( type ) { 67 if ( typeof arg === type ) { 68 if ( toReturn ) 69 return toReturn; 70 return true; 71 } 72 return false 73 } 74 if ( typeof arg !== "undefined" && arg!== "null" ) return true; 75 return false; 76 } 77 /** 78 Clones an object 79 @param {object} obj The source object 80 @returns {object} The cloned object 81 **/ 82 var clone = function( obj ){ 83 if(obj == null || typeof(obj) != 'object') 84 return obj; 85 var temp = new obj.constructor(); 86 for(var key in obj) 87 temp[ key ] = clone( obj[ key ] ); 88 return temp; 89 } 90 91 /** 92 Karma 93 @name Karma 94 @class Represents a Karma (master) object. 95 @namespace 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 //Bryan: we aren't using this right now 241 /* that.i18n.root[ that.i18n.shortcut ] = i18nWrapper( 242 { 243 domain : lang, 244 file : { 245 type: that.supportedLangFileTypes[i].type, 246 uri: this.url, data: data 247 } 248 } 249 );*/ 250 localiseContent( lang ); 251 tryNext = false; 252 }, 253 error: function ( XHR, textStatus, errorThrown ) { 254 //the file doesn't exist or it wasn't possible to load it 255 tryNext = true; 256 } 257 }); 258 return tryNext; 259 } 260 }); 261 return loaded; 262 }; 263 //PRIVATE STUFF end 264 // default options 265 var defaultOptions ={ 266 container: "#karma-main", 267 language: { 268 lang: undefined, 269 alternatives: ['en-US', 'en'], 270 countryCode: undefined, 271 langCode: undefined, 272 }, 273 i18n: { 274 root: self, // self is global 275 shortcut: "_" 276 } 277 }; 278 // 279 this.library = { "images": [], "sounds": [], "videos":[], "shapes":[] }; 280 281 //initializes the defaultOptions argument 282 //1 argument: string. assume it's the container 283 if ( typeof options === "string" ) { 284 options = { container: options }; 285 options.language = getLanguage() ; 286 } else if (typeof options === "object" ){ 287 if ( typeof options.lang === "string" ) { 288 //if language is string, assume it's the language.lang 289 options.language = { lang: options.lang }; 290 } 291 } 292 $.extend( true, defaultOptions, options ); 293 // 294 //copy defaultOptions to this, we use this.xyz instead this.defaultOptions.xyz 295 for (var i in defaultOptions ) { 296 this[ i ] = defaultOptions[i]; 297 } 298 299 //initializes i18n 300 //add the localized language to the language.alternatives 301 if ( typeof this.language.countryCode !== "undefined" ) { 302 this.language.alternatives.unshift( 303 this.language.langCode, 304 this.language.countryCode 305 ); 306 } 307 if ( typeof this.language.lang !== "undefined" ) { 308 this.language.alternatives.unshift( this.language.lang ); 309 } 310 //try to load the localized lang file (po or json or ...) 311 this.language.fileLoaded = loadAlternatives( ); 312 //initializes the container 313 if ( typeof this.container === "string" ) { 314 this.container = $( this.container )[ 0 ]; 315 if ( !valid(this.container) ) delete this.container; 316 } 317 318 gk = { 319 "paths": this.paths 320 } 321 this.surfaces = {}; 322 }; 323 324 /** 325 @memberOf Karma 326 @namespace Geometry functions. 327 **/ 328 Karma.prototype.geometry = { 329 /** 330 Converts a value from degrees to radians. 331 @param {Number} angle The angle in degrees 332 @returns {Number} The The angle in radians 333 **/ 334 radians : function( angle ){ 335 return ( angle / 180 ) * Math.PI; 336 }, 337 /** 338 Gets the square of the Euclidian (ordinary) distance between 2 points. 339 @param {Number} Point Point No. 0 340 @param {Number} Point Point No. 1 341 @returns {Number} The square of the Euclidian distance 342 **/ 343 distance2 : function ( p0, p1 ) { 344 return (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p1.y) * (p1.y - p1.y); 345 }, 346 /** 347 Gets the Euclidian (ordinary) distance between 2 points.<br> 348 <b>Warning:</b> It's slower than distance2 function 349 @param {Number} Point Point No. 0 350 @param {Number} Point Point No. 1 351 @returns {Number} The Euclidian distance 352 **/ 353 distance : function ( p0, p1 ) { 354 return Math.sqrt( Karma.prototype.geometry.distance2( p0, p1 ) ); 355 } 356 357 }; 358 /** 359 @memberOf Karma 360 @namespace Graphics functions. 361 **/ 362 Karma.prototype.graphics = { 363 /** 364 Creates a new rectangle. It's a shortcut for calling 'new KRectangle(..)'. 365 **/ 366 rectangle: function ( args ) { return new KRectangle( args ); }, 367 /** 368 Creates a new circle. It's a shortcut for calling 'new KCircle(..)'. 369 **/ 370 circle: function ( args ) { return new KCircle( args ); } 371 } 372 /** 373 @memberOf Karma 374 @namespace Math functions. 375 **/ 376 Karma.prototype.math = { 377 /** 378 Generates a random bumber between lower bound and upper bound inclusive. 379 @param {Number} lower The lower bound 380 @param {Number} upper The upper bound 381 @returns {Number} The generated number 382 **/ 383 rand : function ( lower, upper ){ 384 return Math.round ( Math.random() * (upper - lower) + lower ); 385 } 386 } 387 //FIXME 388 //everything inside karma.graphics is exported to karma.prototype 389 $.extend( Karma.prototype, Karma.prototype.graphics); 390 // 391 /** 392 @param {Object} [toLoad] The Object that has the arrays for preloading. 393 @param {Array} [toLoad.images] The images 394 @param {Array} [toLoad.sounds] The sounds 395 @param {Array} [toLoad.videos] The videos 396 @memberOf Karma 397 @returns {Object} this 398 **/ 399 Karma.prototype.init = function( toLoad ) { 400 this.pendingToLoad = toLoad; 401 return this; //chaining :) 402 }; 403 404 /** 405 Main function. Any Karma function call should be inside the callback function. 406 The callback function will be executed when the preloading finishes. 407 @param {Function} cb The callback funtion 408 @memberOf Karma 409 @see Karma#init 410 **/ 411 Karma.prototype.main = function ( cb ) { 412 if ( valid( this.pendingToLoad ) ) { 413 //loader 414 var loaderDiv = $("body").append('<div id=\"karma-loader\">Karma is \ 415 loading ...<div id=\"karma-loader\" class=\"status\"></div></div>'); 416 var statusDiv = $("#karma-loader .status"); 417 418 var statusUpdate = function ( current, error, total) { 419 statusDiv.html(current + "/" + total + (error > 0 ? " [ "+error+" ]":'')); 420 }; 421 422 var that = this; 423 var categories = ["images", "sounds", "videos" ]; 424 var counters = { "loaded":0, "error": 0 }; 425 var totalItems = 0; 426 //creates the surfaces 427 if ( valid( this.pendingToLoad[ "surfaces" ] ) ) { 428 $.each (this.pendingToLoad[ "surfaces" ], function( key, config ){ 429 Karma.prototype.surface.call( that, config ); 430 }); 431 } 432 statusUpdate( 0, 0, totalItems); 433 //get the total items 434 for ( var i=0; i < categories.length; i++ ) { 435 if ( valid ( this.pendingToLoad[ categories[ i ] ] ) ) { 436 totalItems += this.pendingToLoad[ categories[ i ] ].length; 437 } 438 } 439 440 /** 441 callback to check if all the items were loaded or got an error when 442 loading 443 **/ 444 var errors=[]; 445 var checkAllLoaded = function ( ev ) { 446 if ( ev.type === "load") counters.loaded += 1; 447 else { 448 errors.push( ev.target.src ); 449 counters.error += 1; 450 } 451 statusUpdate( counters.loaded, counters.error, totalItems); 452 if ( counters.loaded + counters.error === totalItems ) { 453 if ( counters.error > 0 ){ 454 throw ( "Media files not found: " + errors ); 455 } 456 $("#karma-loader:hiden:first").fadeOut("slow",function(){ 457 $(this).remove();}); 458 if ( cb ) cb(); 459 } 460 }; 461 462 for ( var i=0; i < categories.length; i++ ) { 463 var category = categories[ i ]; 464 if ( valid ( this.pendingToLoad[ category ] ) ) { 465 //load all the category elements 466 var type = category.substr( 0, category.length-1 ) 467 $.each (this.pendingToLoad[ category ], function( key, config ){ 468 var name = config.name; 469 delete config.name; 470 //register the elements into the library 471 that.library[ category ][ name ] = Karma.prototype[ type ]( 472 config 473 ); 474 that.library[ category ][ name ].media.addEventListener( 475 "load",checkAllLoaded,false 476 ); 477 that.library[ category ][ name ].media.addEventListener( 478 "error",checkAllLoaded,false 479 ); 480 }); 481 } 482 } 483 }else { 484 if ( cb ) cb(); 485 } 486 } 487 /** 488 A shortcut for calling 'KImage( )' 489 @see KImage 490 @memberOf Karma 491 @returns {Object} new instance of KImage object 492 **/ 493 Karma.prototype.image = function ( args ) { return new KImage( args ) }; 494 /** 495 A shortcut for calling 'KSound( )' 496 @see KSound 497 @memberOf Karma 498 @returns {Object} new instance of KSound object 499 **/ 500 Karma.prototype.sound = function ( args ) { return new KSound( args ) }; 501 /** 502 A shortcut for calling 'KVideo( )' 503 @see KVideo 504 @memberOf Karma 505 @returns {Object} new instance of KVideo object 506 **/ 507 Karma.prototype.video = function ( args ) { alert("Not implemented yet"); }; 508 /** 509 A shortcut for calling 'KGroup( )' 510 @see KGroup 511 @memberOf Karma 512 @returns {Object} new instance of KGroup object 513 **/ 514 Karma.prototype.group = function ( args ) { return new KGroup( args ) }; 515 /** 516 A shortcut for calling 'KButton( )' 517 @see KButton 518 @memberOf Karma 519 @returns {Object} new instance of KButton object 520 **/ 521 Karma.prototype.button = function ( args ) { return new KButton( args ) }; 522 /** 523 A shortcut for calling 'KSurface(.. )'. 524 @see KSurface 525 @memberOf Karma 526 @returns {Object} new instance of KSurface object 527 **/ 528 Karma.prototype.surface = function ( options ) { 529 if ( !valid(options, "object") ){ 530 var options = { name: "ksurface-"+ ( this.surfaces.length + 1 ) }; 531 } 532 options.mainContainer = this.container; 533 options.paths = this.paths; 534 this.surfaces[ options.name ] = new KSurface( options ); 535 return this.surface[ options.name ]; 536 }; 537 538 /** 539 Mouse 540 **/ 541 var mouse = {}; 542 /** 543 Gets the 'x' and 'y' mouse coordinates relatives to the canvas 544 @returns {Object} An Object with 'x' and 'y' attributes 545 **/ 546 mouse.getRelativeCanvasPosition = function ( ev ) { 547 if ( !ev ) return; 548 var xy ={x:0, y:0}; 549 xy.x = ev.layerX; 550 xy.y = ev.layerY; 551 return xy; 552 }; 553 554 //Events stuff 555 var master ={}; 556 master.buttons =[]; 557 var handleEvents = function( ev ) { 558 var xy = mouse.getRelativeCanvasPosition( ev ); 559 for (var i in master.buttons) { 560 if (master.buttons[i].isPointInPath( xy.x, xy.y) ){ 561 master.buttons[i].onClick( ev ); 562 } 563 } 564 /*switch(ev.type){ 565 case "click": break; 566 }*/ 567 /*var s=""; 568 for (var i in ev) { 569 s+=i+"="+ev[i]+"\n"; 570 } 571 alert(s);*/ 572 }; 573 574 /** 575 Master class creator. It will merge all the properties and methods of the 576 recived arguments (objects) into one new class that wil be returned. 577 @returns {Object} The new class 578 **/ 579 var Class = function ( ) { 580 var log=""; 581 var parents = []; 582 for ( var i = 0; i < arguments.length; i++ ) { 583 if ( arguments[i].prototype && arguments[i].init ) { 584 parents.push( arguments[i].init ); 585 } 586 } 587 var o = function ( ) { 588 //we inject all the init functions 589 /*for ( var i = 0; i < this.__parents.length; i++ ) { 590 this.__parents[ i ].apply ( this, arguments ); 591 }*/ 592 //call the real class init 593 if ( this.init ) 594 this.init.apply( this, arguments ); 595 }; 596 597 o.prototype ={}; 598 var a; 599 for ( var i =0; i < arguments.length; i++) { 600 a = arguments[i]; 601 log += "**" + typeof a+"\n"; 602 //if ( a === "function") { 603 if (a.prototype) { 604 for ( var j in a.prototype ) { 605 //log += j+" = "+a.prototype[j]+"\n"; 606 o[ j ] = o.prototype[ j ] = a.prototype [ j ]; 607 } 608 } 609 else { 610 //if ( typeof a === "object") { 611 for (var j in a) { 612 //log += j+" = "+a[j]+"\n"; 613 o[ j ] = o.prototype[ j ] = a [ j ]; 614 } 615 } 616 617 } 618 o.prototype.__parents = parents; 619 //alert( log ); 620 return o; //(function ( ) { return new o( arguments );}); 621 }; 622 623 /** 624 Creates a new surface. A surface is a 'canvas' element with additional methods 625 that makes easier its manipulation. <br> 626 There are 2 ways to create a new KSurface: 627 <ol> 628 <li><b>Using an existing canvas element:</b>You must provide at least 629 the 'canvas' parameter. The 'name' is optional (if it's not provided the 630 'canvas' parameter will be used). 631 </li> 632 <li><b>Creating a new canvas element:</b> A new 'canvas' element will be 633 created and it will be appended to the specific 'container'. 634 You must provide at least the 'name' and 'container' parameters. 635 </li> 636 </ol> 637 @name KSurface 638 @class KSurface class 639 @param {object} options Constructor options. 640 @param {string} [options.name] The desired name for the surface. The value must 641 be unique among others KSurfaces-name objects. 642 @param {string} [options.canvas] The name of the element. Commonly the 643 canvas-id value. 644 @param {string | object} [options.container] The the name of the container 645 element. Commonly a div-id value. 646 @param {number} [width=100] The width of the canvas. 647 @param {number} [height=100] The height of the canvas. 648 @param {number} [fps=24] The frames per second for any refresh operation. 649 @param {boolean} [visible=true] 'true' if the content is visible (will be drawn). 650 @memberOf Karma 651 **/ 652 var KSurface = Class( 653 { 654 init: function( options ){ 655 //fix the container 656 if ( valid( options.container, "string" ) && !valid( options.canvas) 657 ) { 658 var name=options.container; 659 options.container = $( options.container )[ 0 ]; 660 if ( !valid (options.container) ){ 661 // the container must be created inside the mainContainer 662 if ( !valid( options.mainContainer ) ){ 663 throw ("You need to create the Karma master container"); 664 } 665 var div = document.createElement("div"); 666 div.id = name; 667 options.container=options.mainContainer.appendChild( div ); 668 } 669 }else { 670 if ( !valid( options.mainContainer ) ){ 671 throw ("You need to create the Karma master container"); 672 } 673 options.container = options.mainContainer; 674 } 675 676 var defaultOptions = { 677 //mainContainer: '',//must be overwritten by Karma.container 678 name: '',//must be overwritten by the Karma.surface OR user 679 container: '', //must be overwritten by Karma.container OR user 680 681 width: 100, 682 height: 100, 683 fps: 24, 684 visible: true 685 }; 686 $.extend( this, defaultOptions, options); 687 688 if ( !this.canvas ) { 689 this.canvas = document.createElement("canvas"); 690 this.canvas.width = this.width; 691 this.canvas.height = this.height; 692 this.canvas.id = this.name; 693 this.container.appendChild( this.canvas ); 694 }else { 695 this.canvas = document.getElementById( options.canvas ); 696 if ( !this.canvas ){ 697 throw new Error ("The canvas id doesn't exist"); 698 } 699 this.width = this.canvas.width; 700 this.height = this.canvas.height; 701 if (!this.name){ 702 this.name = this.canvas.id; 703 } 704 } 705 if ( this.canvas.getContext ) { 706 this.ctx = this.canvas.getContext("2d"); 707 }else { 708 throw new Error ("Your browser doesn't support canvas, \ 709 try the newest Firefox, Safari or Google Chrome"); 710 } 711 //ctx methods chaining stuff 712 var toChain = [ 713 "globalAlpha", "globalCompositeOperation", "lineWidth", "lineCap", 714 "lineJoin", "miterLimit", "font", "textAlign", "textBaseline", "save", 715 "restore", "scale", "rotate", "translate", "transform", "setTransform", 716 "clearRect", "fillRect", "strokeRect", "beginPath", "closePath", 717 "moveTo", "lineTo", "quadraticCurveTo", "bezierCurveTo", "arcTo", 718 "arc", "rect", "fill", "stroke", "clip", "fillText", "strokeText", 719 "measureText", "isPointInPath", "strokeStyle", "fillStyle", 720 "createLinearGradient", "createRadialGradient", "createPattern", 721 "shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", 722 //"mozTextStyle", "mozDrawText", "mozMeasureText", "mozPathText", 723 "mozTextAlongPath", "drawImage", "getImageData", "putImageData", 724 "createImageData", "drawWindow" 725 ]; 726 var that=this; 727 var chainMaker = function ( name ){ 728 that[ name ] = function ( ){ 729 var type = typeof that.ctx[name]; 730 if ( type === "function") { 731 that.ctx[ name ].apply( that.ctx, arguments ); 732 }else if ( type === "string" ){ 733 that.ctx[ name ] = arguments[0]; 734 }else { 735 throw ("wtf?!: impossible to chain " + name + "!"); 736 } 737 return that; 738 }; 739 }; 740 for (var i=0; i<toChain.length; i++){ 741 chainMaker( toChain[ i ] ); 742 } 743 744 745 //events 746 this.canvas.addEventListener("contextmenu", function(ev){ 747 // 748 },false 749 ); 750 this.canvas.addEventListener("click", 751 handleEvents, 752 false 753 ); 754 755 }, 756 /** 757 Adds an event listener to the surface 758 @param {string} type Event type 759 @param {function} cb Function call back 760 @param {boolean} [bubble=false] If the event must be captured on 761 bubbling phase 762 **/ 763 addEventListener : function ( type, cb, bubble ) { 764 this.canvas.addEventListener( type, cb, bubble || false ); 765 }, 766 /** 767 Removes an event listener attached to the surface 768 @param {string} type Event type 769 @param {function} cb Function call back 770 @param {boolean} [bubble=false] If the event must be captured on 771 bubbling phase 772 **/ 773 removeEventListener : function ( type, cb, bubble ) { 774 this.canvas.removeEventListener( type, cb, bubble || false ); 775 }, 776 /** 777 Clears a rectangular area within the canvas 778 @param {Number} [x=0] Start position of x 779 @param {Number} [y=0] Start position of y 780 @param {Number} [width=canvas width] Square width 781 @param {Number} [height=canvas height] Square height 782 **/ 783 clear : function ( x, y, width, height ) { 784 this.ctx.clearRect( 785 x || 0, 786 y || 0, 787 width || this.width, 788 height || this.height 789 ); 790 return this; 791 }, 792 draw: function ( ) { 793 794 } 795 } 796 ); 797 798 /** 799 Karma basic Object 800 @name KObject 801 @class The basic Karma object 802 @param {Object} [options] Options 803 @param {String} [options.localized = true] The object will be localized 804 @memberOf Karma 805 **/ 806 var KObject = Class( 807 { 808 init: function ( options ) { 809 if ( valid(options.localized, "boolean" ) ) { 810 this.localized = options.localized; 811 }else { 812 this.localized = true; 813 } 814 } 815 } 816 ); 817 /** 818 Graphics basic Object 819 @name KGraphic 820 @class General methods for any Graphic object 821 @param {object} [options] Options 822 @param {number} [options.x = 0] The 'x' position of the object 823 @param {number} [options.y = 0] The 'y' position of the object 824 @param {number} [options.z = 0] The 'z' index of the object 825 @param {number} [options.width = 0] The 'width' of the object 826 @param {number} [options.height = 0] The 'height' of the object 827 @param {boolean} [options.visible = true] Defines if the object will be visible 828 when drawing 829 @memberOf Karma 830 @augments KObject 831 **/ 832 var KGraphic = Class( 833 KObject, 834 { 835 init: function ( options ) { 836 if ( valid( options.localized ) ) 837 KObject.init.call(this, options.localized ); 838 var defaultOptions = { 839 x : 0, 840 y : 0, 841 z : 0, 842 width: 0, 843 height: 0, 844 visible : true 845 } 846 $.extend( this, defaultOptions, options); 847 }, 848 /** 849 @memberOf KGraphic 850 Determines if the 'x' and 'y' coodinates are inside the object. 851 @returns {boolean} 'true' if the coordinates are inside or on the border 852 of the object, otherwise 'false' 853 **/ 854 isPointInPath : function( x, y ) { 855 return (this.x <= x && (this.x + this.width) >= x && 856 this.y <= y && (this.y+this.width)>=y); 857 }, 858 addEventListener : function (type, cb, bubble) { 859 //FIXME 860 } 861 } 862 ); 863 /** 864 An object that collects multiple KGraphic objects. Supports multiple objects. 865 @name KGroup 866 @class An object that collects multiple KGraphic objects 867 @augments KGraphic 868 @memberOf Karma 869 **/ 870 var KGroup = Class( 871 KGraphic, 872 { 873 init: function ( options ) { 874 this.childNodes = []; 875 this.sorted = true; 876 }, 877 /** 878 @memberOf KGroup 879 Adds each argument passed to the funtion to chilNodes. 880 @param {Array:KGraphic} arguments The elements to add to childNodes 881 @see KGroup#draw 882 **/ 883 appendChild : function ( ) { 884 if ( arguments.length > 0 ) { 885 for ( var i = 0; i< arguments.length; i++) { 886 this.childNodes.push ( arguments[ i ] ); 887 } 888 this.sorted = false; 889 890 } 891 }, 892 removeChild: function () { 893 //FIXME 894 }, 895 /** 896 @memberOf KGroup 897 Draws all the elements in childNodes. The elements are drawn according 898 to its 'z' (z-index) value. 899 @see KGroup#appendChild 900 **/ 901 draw : function() { 902 if ( this.visible && this.childNodes.length > 0 ) { 903 if ( !this.sorted ) { 904 this.childNodes.sort ( function ( g1, g2 ) { 905 return g1.z - g2.z; 906 }); 907 this.sorted = true; 908 } 909 for (var i in this.childNodes) { 910 this.childNodes[ i ].draw(); 911 } 912 } 913 }, 914 isPointInPath : function() { 915 //TODO 916 } 917 918 } 919 ); 920 921 /** 922 Graphics basic Media object. 923 @name KMedia 924 @class General methods for any Graphic object 925 @param {String} file The name of the file that must be loaded 926 @param {String} type 'image', 'sound' or 'video' 927 @param {Object} [options] Options that will be passed to the media element 928 constructor 929 @augments KObject 930 @memberOf Karma 931 **/ 932 var KMedia = Class( 933 KObject, 934 { 935 init: function (file, type, options ) { 936 if ( !file || !type ) { 937 throw new Error ("file and type needed"); 938 } 939 if ( valid ( options ) ) 940 KObject.init.call (this, options); 941 942 this.file = file; 943 this.type = type; 944 945 this.status = undefined; 946 this.path = undefined; 947 this.media = undefined; 948 switch ( this.type ) { 949 case "image": this.media = new Image(); break; 950 case "sound": this.media = new Audio(); break; 951 default: throw new Error ("Media type not supported"); 952 } 953 this.path = gk.paths[ this.type + "s" ][ 954 this.localized ? "localized": "generic" 955 ]; 956 this.media.src = this.src = this.path + this.file; 957 958 var that = this; 959 this.media.addEventListener("load", 960 function (e) { that.status = "loaded";}, false); 961 this.media.addEventListener("error", 962 function (e) { that.status = "error";}, false); 963 this.media.addEventListener("abort", 964 function (e) { that.status = "aborted";}, false); 965 } 966 } 967 ); 968 969 /** 970 Image object 971 @name KImage 972 @class General methods for any Image object 973 @param {Object} options Constructor arguments. 974 @param {Object} options.file The image file that will be loaded. 975 @augments KGraphic 976 @augments KMedia 977 @memberOf Karma 978 **/ 979 var KImage = Class( 980 KGraphic, 981 KMedia, 982 { 983 init: function ( options ) { 984 if ( valid ( options, "string" ) ) { 985 options = { file:options }; 986 } 987 if ( valid( options ) ) { 988 KGraphic.init.call(this, options); 989 KMedia.init.call(this, options.file, "image", options ); 990 } 991 var defaultOptions = { 992 //w : undefined, 993 //h : undefined, 994 }; 995 $.extend( this, defaultOptions, options); 996 }, 997 draw : function( ctx, x, y ) { 998 if ( this.visible && this.isReady() ) { 999 this.x = x || this.x; 1000 this.y = y || this.y; 1001 ctx.drawImage( this.media, this.x , this.y ); 1002 } 1003 }, 1004 /** 1005 Checks if the image has been loaded and fully decoded. 1006 @returns {boolean} 'true' or 'false' 1007 **/ 1008 isReady : function () { 1009 if ( !this.media.complete ) return false; 1010 if ( !this.media.naturalWidth || this.media.naturalWidth === 0) 1011 return false; 1012 return true; 1013 } 1014 } 1015 ); 1016 1017 /** 1018 Sound object 1019 @name KSound 1020 @class General methods for any Sound object 1021 @param {Object} options Constructor arguments. 1022 @param {Object} options.file The image file that will be loaded. 1023 @augments KMedia 1024 @memberOf Karma 1025 **/ 1026 var KSound = Class( 1027 /**@lends_ KMedia*/ 1028 KMedia, 1029 { 1030 init: function( options ) { 1031 if ( valid ( options, "string" ) ) { 1032 options = { file: options }; 1033 } 1034 if ( valid( options ) ) { 1035 KMedia.init.call(this, options.file, "sound", options ); 1036 //next line is important! 1037 this.media.load(); 1038 } 1039 }, 1040 /** 1041 Checks if the image has been loaded and fully decoded. 1042 @returns {boolean} 'true' or 'false' 1043 **/ 1044 isReady: function () { 1045 return this.readyState === 4; 1046 }, 1047 play: function (){ 1048 //hack to fix the audio "stuttering" problem 1049 //more info: https://bugs.launchpad.net/karma/+bug/426108 1050 this.media.currentTime = 0.1; 1051 this.media.play(); 1052 } 1053 } 1054 ); 1055 1056 /** 1057 Shape object 1058 @name KShape 1059 @class General methods for any Shape object 1060 @param {object} options Constructor arguments. 1061 @param {boolean} [options.fill=true] 'true' if the Shape will be filled when 1062 drawing. 1063 @param {boolean} [options.stroke=true] 'true' if the stroke will be drawn. 1064 @param {color|string} [options.fillStyle="#000"] The fill style of the shape. 1065 @param {color|string} [options.strokeStyle="#000"] The stroke style of the shape. 1066 @augments KMedia 1067 @memberOf Karma 1068 **/ 1069 var KShape = Class( 1070 /**@lends_ KGraphic*/ 1071 KGraphic, 1072 { 1073 init : function ( options ) { 1074 if ( valid( options ) ) { 1075 KGraphic.init.call(this, options ); 1076 } 1077 var defaultOptions = { 1078 fill: true, 1079 stroke: true, 1080 fillStyle: '#000', 1081 strokeStyle: '#000', 1082 openPath : false 1083 }; 1084 $.extend( this, defaultOptions, options); 1085 }, 1086 draw : function ( ctx ) { 1087 if ( this.visible ) { 1088 ctx.fillStyle = this.fillStyle 1089 ctx.strokeStyle= this.strokeStyle 1090 if ( this.fill ) 1091 ctx.fill(); 1092 if ( this.stroke ) 1093 ctx.stroke(); 1094 if ( !this.openPath ) 1095 ctx.closePath(); 1096 ctx.restore(); 1097 } 1098 } 1099 } 1100 ); 1101 /** 1102 Rectangle object 1103 @name KRectangle 1104 @class General methods for a rectangle object 1105 @param {object} options Constructor arguments. 1106 @param {number} options.x The 'x' position. 1107 @param {number} options.y The 'y' position. 1108 @param {number} options.w The width of the rectangle. 1109 @param {number} options.h The height of the rectangle. 1110 @augments KShape 1111 @memberOf Karma 1112 **/ 1113 var KRectangle = Class( 1114 KShape, 1115 { 1116 init : function ( options ) { 1117 //ADD multiple constructors support 1118 //x,y,w,h 1119 //w,y,w,h,options 1120 if ( valid( options ) ) { 1121 KShape.init.call(this, options ); 1122 } 1123 }, 1124 draw : function ( ctx ) { 1125 if ( this.visible ) { 1126 ctx.save(); 1127 ctx.beginPath(); 1128 ctx.rect( this.x, this.y, this.width, this.height); 1129 KShape.draw.call( this, ctx ); 1130 } 1131 }, 1132 clear : function ( ) { 1133 if ( this.visible ) { 1134 1135 } 1136 } 1137 } 1138 1139 ); 1140 1141 /**@class_ */ 1142 var KButton = Class( 1143 /**@lends_ KGraphic*/ 1144 KGraphic, 1145 { 1146 1147 init : function ( options ) { 1148 //ADD multiple constructors support 1149 //x,y,w,h 1150 //w,y,w,h,options 1151 if ( valid( options ) ) { 1152 KGraphic.init.call(this, options ); 1153 } 1154 this.name = options.name; 1155 master.buttons.push(this); 1156 }, 1157 draw : function ( ) {}, 1158 onClick : function() { } //callback 1159 } 1160 ); 1161 // 1162 /** 1163 Karma function. It's a shotcut for calling 'new Karma(..)' 1164 @param [options] Options passed to the Karma constructor 1165 @returns {Object} a new Karma object 1166 @see Karma 1167 **/ 1168 $.karma = function (options) { 1169 var k =new Karma( options ); 1170 return k; 1171 } 1172 })(jQuery);