فهرست منبع

Merge branch 'master' into feature/binary_extension

Gary Hsu 9 سال پیش
والد
کامیت
cc31e97ea8
100فایلهای تغییر یافته به همراه32622 افزوده شده و 19821 حذف شده
  1. BIN
      Exporters/Blender/Blender2Babylon-5.0.zip
  2. 1 1
      Exporters/Blender/src/__init__.py
  3. 1 1
      Exporters/Blender/src/mesh.py
  4. 0 16
      Tools/Gulp/config.json
  5. 1 1
      Tools/Gulp/gulpfile.js
  6. 1 1
      bower.json
  7. 47 0
      canvas2D/config.json
  8. 48 0
      canvas2D/gulp-addModuleExports.js
  9. 97 0
      canvas2D/gulp-removeShaderComments.js
  10. 68 0
      canvas2D/gulp-srcToVariable.js
  11. 124 0
      canvas2D/gulpfile.js
  12. 28 0
      canvas2D/package.json
  13. 65 0
      canvas2D/readme.md
  14. 0 0
      canvas2D/src/Engine/babylon.bounding2d.js
  15. 0 0
      canvas2D/src/Engine/babylon.bounding2d.ts
  16. 2 2
      src/Canvas2d/babylon.brushes2d.js
  17. 2 2
      src/Canvas2d/babylon.brushes2d.ts
  18. 81 7
      src/Canvas2d/babylon.canvas2d.js
  19. 81 8
      src/Canvas2d/babylon.canvas2d.ts
  20. 3 3
      src/Canvas2d/babylon.canvas2dLayoutEngine.js
  21. 3 3
      src/Canvas2d/babylon.canvas2dLayoutEngine.ts
  22. 1 1
      src/Canvas2d/babylon.ellipse2d.js
  23. 1 1
      src/Canvas2d/babylon.ellipse2d.ts
  24. 1 1
      src/Canvas2d/babylon.group2d.js
  25. 1 1
      src/Canvas2d/babylon.group2d.ts
  26. 1 1
      src/Canvas2d/babylon.lines2d.js
  27. 2 1
      src/Canvas2d/babylon.lines2d.ts
  28. 0 0
      canvas2D/src/Engine/babylon.modelRenderCache.js
  29. 0 0
      canvas2D/src/Engine/babylon.modelRenderCache.ts
  30. 203 67
      src/Canvas2d/babylon.prim2dBase.js
  31. 224 70
      src/Canvas2d/babylon.prim2dBase.ts
  32. 1 1
      src/Canvas2d/babylon.rectangle2d.js
  33. 1 1
      src/Canvas2d/babylon.rectangle2d.ts
  34. 1 1
      src/Canvas2d/babylon.renderablePrim2d.js
  35. 1 1
      src/Canvas2d/babylon.renderablePrim2d.ts
  36. 1 1
      src/Canvas2d/babylon.shape2d.js
  37. 1 1
      src/Canvas2d/babylon.shape2d.ts
  38. 1146 0
      canvas2D/src/Engine/babylon.smartPropertyPrim.js
  39. 1292 0
      canvas2D/src/Engine/babylon.smartPropertyPrim.ts
  40. 1 1
      src/Canvas2d/babylon.sprite2d.js
  41. 1 1
      src/Canvas2d/babylon.sprite2d.ts
  42. 12 2
      src/Canvas2d/babylon.text2d.js
  43. 9 2
      src/Canvas2d/babylon.text2d.ts
  44. 0 0
      canvas2D/src/Engine/babylon.worldSpaceCanvas2dNode.js
  45. 0 0
      canvas2D/src/Engine/babylon.worldSpaceCanvas2dNode.ts
  46. 779 0
      canvas2D/src/GUI/babylon.gui.UIElement.js
  47. 806 0
      canvas2D/src/GUI/babylon.gui.UIElement.ts
  48. 191 0
      canvas2D/src/GUI/babylon.gui.button.js
  49. 209 0
      canvas2D/src/GUI/babylon.gui.button.ts
  50. 212 0
      canvas2D/src/GUI/babylon.gui.control.js
  51. 212 0
      canvas2D/src/GUI/babylon.gui.control.ts
  52. 76 0
      canvas2D/src/GUI/babylon.gui.label.js
  53. 78 0
      canvas2D/src/GUI/babylon.gui.label.ts
  54. 195 0
      canvas2D/src/GUI/babylon.gui.window.js
  55. 214 0
      canvas2D/src/GUI/babylon.gui.window.ts
  56. 617 0
      canvas2D/src/Tools/babylon.observable.js
  57. 671 0
      canvas2D/src/Tools/babylon.observable.ts
  58. 296 0
      canvas2D/src/Tools/babylon.stringDictionary.js
  59. 320 0
      canvas2D/src/Tools/babylon.stringDictionary.ts
  60. 0 0
      canvas2D/src/shaders/ellipse2d.fragment.fx
  61. 0 0
      canvas2D/src/shaders/ellipse2d.vertex.fx
  62. 0 0
      canvas2D/src/shaders/lines2d.fragment.fx
  63. 0 0
      canvas2D/src/shaders/lines2d.vertex.fx
  64. 0 0
      canvas2D/src/shaders/rect2d.fragment.fx
  65. 0 0
      canvas2D/src/shaders/rect2d.vertex.fx
  66. 0 0
      canvas2D/src/shaders/sprite2d.fragment.fx
  67. 0 0
      canvas2D/src/shaders/sprite2d.vertex.fx
  68. 0 0
      canvas2D/src/shaders/text2d.fragment.fx
  69. 0 0
      canvas2D/src/shaders/text2d.vertex.fx
  70. 4051 0
      dist/preview release/babylon.canvas2d.d.ts
  71. 9 0
      dist/preview release/babylon.canvas2d.js
  72. 14291 0
      dist/preview release/babylon.canvas2d.max.js
  73. 27 27
      dist/preview release/babylon.core.js
  74. 4978 7974
      dist/preview release/babylon.d.ts
  75. 37 43
      dist/preview release/babylon.js
  76. 505 11497
      dist/preview release/babylon.max.js
  77. 37 43
      dist/preview release/babylon.noworker.js
  78. 6 0
      dist/preview release/what's new.md
  79. 21 0
      postProcessLibrary/config.json
  80. 0 0
      postProcessLibrary/dist/babylon.asciiArtPostProcess.js
  81. 0 0
      postProcessLibrary/dist/babylon.asciiArtPostProcess.min.js
  82. 0 0
      postProcessLibrary/dist/babylon.digitalRainPostProcess.js
  83. 0 0
      postProcessLibrary/dist/babylon.digitalRainPostProcess.min.js
  84. 68 0
      postProcessLibrary/gulp-srcToVariable.js
  85. 53 0
      postProcessLibrary/gulpfile.js
  86. 23 0
      postProcessLibrary/package.json
  87. 0 0
      postProcessLibrary/postProcesses/asciiArt/asciiart.fragment.fx
  88. 0 0
      postProcessLibrary/postProcesses/asciiArt/babylon.asciiArtPostProcess.ts
  89. 0 0
      postProcessLibrary/postProcesses/digitalRain/babylon.digitalRainPostProcess.ts
  90. 0 0
      postProcessLibrary/postProcesses/digitalRain/digitalrain.fragment.fx
  91. 41 0
      postProcessLibrary/readme.md
  92. 7 0
      postProcessLibrary/tsconfig.json
  93. 0 14
      proceduralTexturesLibrary/config.json
  94. 2 2
      proceduralTexturesLibrary/package.json
  95. 1 1
      proceduralTexturesLibrary/readme.md
  96. 1 1
      readme.md
  97. 13 5
      src/Animations/babylon.animatable.js
  98. 14 6
      src/Animations/babylon.animatable.ts
  99. 7 8
      src/Cameras/Inputs/babylon.freecamera.input.mouse.js
  100. 0 0
      src/Cameras/Inputs/babylon.freecamera.input.mouse.ts

BIN
Exporters/Blender/Blender2Babylon-5.0.zip


+ 1 - 1
Exporters/Blender/src/__init__.py

@@ -1,7 +1,7 @@
 bl_info = {
     'name': 'Babylon.js',
     'author': 'David Catuhe, Jeff Palmer',
-    'version': (5, 0, 5),
+    'version': (5, 0, 6),
     'blender': (2, 76, 0),
     'location': 'File > Export > Babylon.js (.babylon)',
     'description': 'Export Babylon.js scenes (.babylon)',

+ 1 - 1
Exporters/Blender/src/mesh.py

@@ -88,7 +88,7 @@ class Mesh(FCurveAnimatable):
             self.scaling  = Vector((1, 1, 1))
             
         # ensure no unapplied rotation or scale, when there is an armature
-        self.hasUnappliedTransforms = (scale.x != 1 or scale.y != 1 or scale.z != 1 or
+        self.hasUnappliedTransforms = (self.scaling.x != 1 or self.scaling.y != 1 or self.scaling.z != 1 or
                 (hasattr(self, 'rotation'          ) and (self.rotation          .x != 0 or self.rotation          .y != 0 or self.rotation          .z != 0)) or 
                 (hasattr(self, 'rotationQuaternion') and (self.rotationQuaternion.x != 0 or self.rotationQuaternion.y != 0 or self.rotationQuaternion.z != 0 or self.rotationQuaternion.w != 1))
                 )

+ 0 - 16
Tools/Gulp/config.json

@@ -159,22 +159,6 @@
       "../../src/Tools/babylon.dynamicFloatArray.js",
       "../../src/Materials/Textures/babylon.fontTexture.js",
       "../../src/Materials/Textures/babylon.mapTexture.js",
-      "../../src/Canvas2d/babylon.bounding2d.js",
-      "../../src/Canvas2d/babylon.canvas2dLayoutEngine.js",
-      "../../src/Canvas2d/babylon.brushes2d.js",
-      "../../src/Canvas2d/babylon.smartPropertyPrim.js",
-      "../../src/Canvas2d/babylon.prim2dBase.js",
-      "../../src/Canvas2d/babylon.modelRenderCache.js",
-      "../../src/Canvas2d/babylon.renderablePrim2d.js",
-      "../../src/Canvas2d/babylon.shape2d.js",
-      "../../src/Canvas2d/babylon.group2d.js",
-      "../../src/Canvas2d/babylon.rectangle2d.js",
-      "../../src/Canvas2d/babylon.ellipse2d.js",
-      "../../src/Canvas2d/babylon.sprite2d.js",
-      "../../src/Canvas2d/babylon.text2d.js",
-      "../../src/Canvas2d/babylon.lines2d.js",
-      "../../src/Canvas2d/babylon.canvas2d.js",
-      "../../src/Canvas2d/babylon.worldSpaceCanvas2dNode.js",
       "../../src/Materials/babylon.shaderMaterial.js",
       "../../src/Tools/babylon.tools.dds.js",
       "../../src/Physics/Plugins/babylon.cannonJSPlugin.js",

+ 1 - 1
Tools/Gulp/gulpfile.js

@@ -99,7 +99,7 @@ gulp.task('typescript-compile', function () {
     return merge2([
         tsResult.dts
             .pipe(concat(config.build.declarationFilename))
-            .pipe(addDtsExport("BABYLON"))
+            //.pipe(addDtsExport("BABYLON"))
             .pipe(gulp.dest(config.build.outputDirectory)),
         tsResult.js
             .pipe(gulp.dest(config.build.srcOutputDirectory))

+ 1 - 1
bower.json

@@ -26,7 +26,7 @@
     "Exporters",
     "Loaders",
     "Previous releases",
-    "Tools",
+    "/Tools",
     "gulpfile.js",
     "package.json",
     "babylon.2.1*.*",

+ 47 - 0
canvas2D/config.json

@@ -0,0 +1,47 @@
+{
+  "build": {
+    "filename": "babylon.canvas2d.max.js",
+    "minFilename": "babylon.canvas2d.js",
+    "declarationFilename": "babylon.canvas2d.d.ts",
+    "outputDirectory": "../dist/preview release",
+    "srcOutputDirectory": "src/"
+  },
+  "core": {
+    "typescript": [
+      "src/**/*.ts",
+      "../dist/preview release/babylon.d.ts", 
+      "!src/**/*.d.ts"
+    ],
+    "files": [   
+      "src/Tools/babylon.stringDictionary.js",
+      "src/Tools/babylon.observable.js",
+      "src/Engine/babylon.bounding2d.js",
+      "src/Engine/babylon.canvas2dLayoutEngine.js",
+      "src/Engine/babylon.brushes2d.js",
+      "src/Engine/babylon.smartPropertyPrim.js",
+      "src/Engine/babylon.prim2dBase.js",
+      "src/Engine/babylon.modelRenderCache.js",
+      "src/Engine/babylon.renderablePrim2d.js",
+      "src/Engine/babylon.shape2d.js",
+      "src/Engine/babylon.group2d.js",
+      "src/Engine/babylon.rectangle2d.js",
+      "src/Engine/babylon.ellipse2d.js",
+      "src/Engine/babylon.sprite2d.js",
+      "src/Engine/babylon.text2d.js",
+      "src/Engine/babylon.lines2d.js",
+      "src/Engine/babylon.canvas2d.js",
+      "src/Engine/babylon.worldSpaceCanvas2dNode.js",
+      "src/GUI/babylon.gui.UIElement.js",
+      "src/GUI/babylon.gui.control.js",
+      "src/GUI/babylon.gui.window.js",
+      "src/GUI/babylon.gui.label.js",
+      "src/GUI/babylon.gui.button.js"
+    ]
+  },
+  "shadersDirectories": [
+    {
+      "variable": "BABYLON.Effect.ShadersStore",
+      "files": "src/Shaders/*.fx"
+    }
+  ]
+}

+ 48 - 0
canvas2D/gulp-addModuleExports.js

@@ -0,0 +1,48 @@
+var gutil = require('gulp-util');
+var through = require('through2');
+
+module.exports = function (varName) {
+    return through.obj(function (file, enc, cb) {
+
+        var moduleExportsAddition =
+          '\nif (((typeof window != "undefined" && window.module) || (typeof module != "undefined")) && typeof module.exports != "undefined") {\n' +
+          '    module.exports = ' + varName + ';\n' +
+          '};\n';
+
+        var extendsAddition =
+        'var __extends = (this && this.__extends) || function (d, b) {\n' +
+          'for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];\n' +
+          'function __() { this.constructor = d; }\n' +
+          '__.prototype = b.prototype;\n' +
+          'd.prototype = new __();\n' +
+        '};\n';
+
+        var decorateAddition =
+        'var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {\n' +
+            'var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;\n' +
+            'if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);\n' +
+            'else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;\n' +
+            'return c > 3 && r && Object.defineProperty(target, key, r), r;\n' +
+        '};\n';
+
+        if (file.isNull()) {
+            cb(null, file);
+            return;
+        }
+
+        if (file.isStream()) {
+            //streams not supported, no need for now.
+            return;
+        }
+
+        try {
+            file.contents = new Buffer(decorateAddition.concat(new Buffer(extendsAddition.concat(String(file.contents)).concat(moduleExportsAddition))));
+            this.push(file);
+
+        } catch (err) {
+            this.emit('error', new gutil.PluginError('gulp-add-module-exports', err, { fileName: file.path }));
+        }
+        cb();
+    });
+};
+

+ 97 - 0
canvas2D/gulp-removeShaderComments.js

@@ -0,0 +1,97 @@
+'use strict';
+
+var through = require('through2');
+var PluginError = require('gulp-util').PluginError;
+var singleComment = 1;
+var multiComment = 2;
+
+function uncomment(str, opts) {
+    opts = opts || {};
+
+	var currentChar;
+	var nextChar;
+	var insideString = false;
+	var insideComment = 0;
+	var offset = 0;
+	var ret = '';
+
+    str = str.replace(/\r\n/g, '\n');
+    str = str.replace(/[ \f\t\v]+/g, ' ');
+    str = str.replace(/^\s*\n/gm, '');
+    str = str.replace(/ \+ /g, '+');
+    str = str.replace(/ \- /g, '-');
+    str = str.replace(/ \/ /g, '/');
+    str = str.replace(/ \* /g, '*');
+    str = str.replace(/ > /g, '>');
+    str = str.replace(/ < /g, '<');
+    str = str.replace(/ >= /g, '>=');
+    str = str.replace(/ <= /g, '<=');
+    str = str.replace(/ \+= /g, '+=');
+    str = str.replace(/ \-= /g, '-=');
+    str = str.replace(/ \/= /g, '/=');
+    str = str.replace(/ \*= /g, '*=');
+    str = str.replace(/ = /g, '=');
+    str = str.replace(/, /g, ',');
+    str = str.replace(/\n\n/g, '\n');
+    str = str.replace(/\n /g, '\n');
+    
+	for (var i = 0; i < str.length; i++) {
+		currentChar = str[i];
+		nextChar = str[i + 1];
+
+		if (!insideComment && currentChar === '"') {
+			var escaped = str[i - 1] === '\\' && str[i - 2] !== '\\';
+			if (!escaped) {
+				insideString = !insideString;
+			}
+		}
+
+		if (insideString) {
+			continue;
+		}
+
+		if (!insideComment && currentChar + nextChar === '//') {
+			ret += str.slice(offset, i);
+			offset = i;
+			insideComment = singleComment;
+			i++;
+		} else if (insideComment === singleComment && currentChar === '\n') {
+			insideComment = 0;
+			offset = i;
+		} else if (!insideComment && currentChar + nextChar === '/*') {
+			ret += str.slice(offset, i);
+			offset = i;
+			insideComment = multiComment;
+			i++;
+			continue;
+		} else if (insideComment === multiComment && currentChar + nextChar === '*/') {
+			i++;
+			insideComment = 0;
+			offset = i + 1;
+			continue;
+		}
+	}
+
+	return ret + (insideComment ? '' : str.substr(offset));
+}
+
+function gulpUncomment(options) {
+    return main(options, uncomment);
+}
+
+function main(options, func) {
+    return through.obj(function (file, enc, cb) {
+        if (file.isNull()) {
+            cb(null, file);
+            return;
+        }
+        if (file.isStream()) {
+            cb(new PluginError("Remove Shader Comments", "Streaming not supported."));
+        }
+        file.contents = new Buffer(func(file.contents.toString(), options));
+        this.push(file);
+        return cb();
+    });
+}
+
+module.exports = gulpUncomment;

+ 68 - 0
canvas2D/gulp-srcToVariable.js

@@ -0,0 +1,68 @@
+var through = require('through2');
+var gutil = require('gulp-util');
+var PluginError = gutil.PluginError;
+var path = require('path');
+var File = gutil.File;
+
+// Consts
+const PLUGIN_NAME = 'gulp-srcToVariable';
+
+var srcToVariable = function srcToVariable(varName, asMap, namingCallback) {
+
+    var content;
+    var firstFile;
+
+    namingCallback = namingCallback || function (filename) { return filename; };
+
+    function bufferContents(file, enc, cb) {
+        // ignore empty files
+        if (file.isNull()) {
+            cb();
+            return;
+        }
+
+        // no stream support, only files.
+        if (file.isStream()) {
+            this.emit('error', new PluginError('gulp-concat', 'Streaming not supported'));
+            cb();
+            return;
+        }
+
+        // set first file if not already set
+        if (!firstFile) {
+            firstFile = file;
+        }
+
+        // construct concat instance
+        if (!content) {
+            content = "";
+        }
+        var name = namingCallback(file.relative);
+        content += varName + "['" + name + "'] = " + JSON.stringify(file.contents.toString()) + ";\r\n";
+        cb();
+    }
+
+    function endStream(cb) {
+        if (!firstFile || !content) {
+            cb();
+            return;
+        }
+
+        var joinedPath = path.join(firstFile.base, varName);
+
+        var joinedFile = new File({
+            cwd: firstFile.cwd,
+            base: firstFile.base,
+            path: joinedPath,
+            contents: new Buffer(content)
+        });
+
+        this.push(joinedFile);
+
+        cb();
+    }
+
+    return through.obj(bufferContents, endStream);
+}
+
+module.exports = srcToVariable;

+ 124 - 0
canvas2D/gulpfile.js

@@ -0,0 +1,124 @@
+var gulp = require("gulp");
+var uglify = require("gulp-uglify");
+var typescript = require("gulp-typescript");
+var sourcemaps = require("gulp-sourcemaps");
+var srcToVariable = require("./gulp-srcToVariable");
+var addModuleExports = require("./gulp-addModuleExports");
+var merge2 = require("merge2");
+var concat = require("gulp-concat");
+var rename = require("gulp-rename");
+var cleants = require('gulp-clean-ts-extends');
+var changed = require('gulp-changed');
+var runSequence = require('run-sequence');
+var replace = require("gulp-replace");
+var uncommentShader = require("./gulp-removeShaderComments");
+var expect = require('gulp-expect-file');
+var optimisejs = require('gulp-optimize-js');
+
+var config = require("./config.json");
+
+var shadersStream;
+
+var extendsSearchRegex = /var\s__extends[\s\S]+?\};/g;
+var decorateSearchRegex = /var\s__decorate[\s\S]+?\};/g;
+
+//function to convert the shaders' filenames to variable names.
+function shadersName(filename) {
+    return filename.replace('.fragment', 'Pixel')
+        .replace('.vertex', 'Vertex')
+        .replace('.fx', 'Shader');
+}
+
+gulp.task("shaders", function (cb) {
+    shadersStream = config.shadersDirectories.map(function (shadersDef) {
+        return gulp.src(shadersDef.files).
+            pipe(expect.real({ errorOnFailure: true }, shadersDef.files)).
+            pipe(uncommentShader()).
+            pipe(srcToVariable("BABYLON.Effect.ShadersStore", true, shadersName)
+            );
+    });
+    cb();
+});
+
+/*
+Compiles all typescript files and creating a declaration file.
+*/
+gulp.task('typescript-compile', function () {
+    var tsResult = gulp.src(config.core.typescript).
+        pipe(typescript({
+            noExternalResolve: true,
+            target: 'ES5',
+            declarationFiles: true,
+            typescript: require('typescript'),
+            experimentalDecorators: true
+        }));
+    //If this gulp task is running on travis, file the build!
+    if (process.env.TRAVIS) {
+        var error = false;
+        tsResult.on('error', function () {
+            error = true;
+        }).on('end', function () {
+            if (error) {
+                console.log('Typescript compile failed');
+                process.exit(1);
+            }
+        });
+    }
+    return merge2([
+        tsResult.dts
+            .pipe(concat(config.build.declarationFilename))
+            .pipe(gulp.dest(config.build.outputDirectory)),
+        tsResult.js
+            .pipe(gulp.dest(config.build.srcOutputDirectory))
+    ])
+});
+
+gulp.task('typescript-sourcemaps', function () {
+    var tsResult = gulp.src(config.core.typescript)
+        .pipe(sourcemaps.init()) // sourcemaps init. currently redundant directory def, waiting for this - https://github.com/floridoo/gulp-sourcemaps/issues/111
+        .pipe(typescript({
+            noExternalResolve: true,
+            target: 'ES5',
+            declarationFiles: true,
+            typescript: require('typescript'),
+            experimentalDecorators: true
+        }));
+    return tsResult.js
+        .pipe(sourcemaps.write("./")) // sourcemaps are written.
+        .pipe(gulp.dest(config.build.srcOutputDirectory));
+});
+
+gulp.task("default", ["shaders"], function () {
+    return merge2(
+        gulp.src(config.core.files).        
+            pipe(expect.real({ errorOnFailure: true }, config.core.files)),
+        shadersStream)
+        .pipe(concat(config.build.filename))
+        .pipe(cleants())
+        .pipe(replace(extendsSearchRegex, ""))
+        .pipe(replace(decorateSearchRegex, ""))
+        .pipe(addModuleExports("BABYLON"))
+        .pipe(gulp.dest(config.build.outputDirectory))
+        .pipe(rename(config.build.minFilename))
+        .pipe(uglify())
+        .pipe(optimisejs())
+        .pipe(gulp.dest(config.build.outputDirectory));
+});
+
+gulp.task("typescript", function (cb) {
+    runSequence("typescript-compile", "default", cb);
+});
+
+/**
+ * Watch task, will call the default task if a js file is updated.
+ */
+gulp.task('watch', function () {
+    gulp.watch(config.core.typescript, ['default']);
+});
+
+/**
+ * Watch typescript task, will call the default typescript task if a typescript file is updated.
+ */
+gulp.task('watch-typescript', function () {
+    gulp.watch(config.core.typescript, ["typescript-compile", "build"]);
+});

+ 28 - 0
canvas2D/package.json

@@ -0,0 +1,28 @@
+{
+  "name": "BabylonJS",
+  "version": "2.4.0",
+  "description": "Babylon.js is a 3D engine based on webgl and javascript",
+  "main": "",
+  "repository": { "url": "https://github.com/BabylonJS/Babylon.js/" },
+  "readme": "https://github.com/BabylonJS/Babylon.js/edit/master/readme.md",
+  "license": "(Apache-2.0)",
+  "devDependencies": {
+    "gulp": "^3.8.11",
+    "gulp-uglify": "~1.5.3",
+    "gulp-sourcemaps": "~1.5.2",
+    "typescript": "^1.7.5",
+    "gulp-typescript": "~2.13.0",
+    "through2": "~0.6.5",
+    "gulp-util": "~3.0.4",
+    "gulp-concat": "~2.5.2",
+    "merge2": "~0.3.5",
+    "gulp-rename": "~1.2.2",
+    "gulp-clean-ts-extends": "~0.1.1",
+    "gulp-changed": "~1.2.1",
+    "run-sequence": "~1.1.0",
+    "gulp-replace": "~0.5.3",
+    "gulp-content-to-variable": "^0.1.0",
+    "gulp-expect-file": "^0.0.7",
+    "gulp-optimize-js": "^1.0.2"
+  }
+}

+ 65 - 0
canvas2D/readme.md

@@ -0,0 +1,65 @@
+Build Babylon.canvas2d.js with Gulp
+====================
+
+More info about [Canvas2D](http://doc.babylonjs.com/overviews/Canvas2D_Home)
+
+Build Babylon.canvas2d.js with [gulp](http://gulpjs.com/ "gulp") and npm ([nodejs](http://nodejs.org/ "nodejs")), easy and cross-platform
+
+(Paths in this file are relative to this file location.)
+
+# How to use it
+
+### First install gulp :
+```
+npm install -g gulp
+```
+
+### Install some dependencies :
+```
+npm install
+```
+
+### Update dependencies if necessary :
+```
+npm update
+```
+
+## From the javascript source
+### Build Babylon.canvas2d.js from the javascript files:
+
+```
+gulp
+```
+Will be generated :
+- babylon.canvas2d.js
+- babylon.canvas2d.max.js (unminified)
+
+### Build Babylon.canvas2d.js when you save a javascript file:
+```
+gulp watch
+```
+
+## From the typescript source
+### Build Babylon.canvas2d.js from the typescript files:
+
+```
+gulp typescript
+```
+Will be generated :
+- babylon.canvas2d.js
+- babylon.canvas2d.d.ts
+- babylon.canvas2d.max.js (unminified)
+
+Be aware that all js files content will be overwrite.
+
+### Build Babylon.canvas2d.js when you save a typescript file:
+```
+gulp watch-typescript
+```
+
+### Compile all the typscript files to their javascript respective files including declaration file
+```
+gulp typescript-compile
+```
+
+Be aware that all js files content will be overwritten.

src/Canvas2d/babylon.bounding2d.js → canvas2D/src/Engine/babylon.bounding2d.js


src/Canvas2d/babylon.bounding2d.ts → canvas2D/src/Engine/babylon.bounding2d.ts


+ 2 - 2
src/Canvas2d/babylon.brushes2d.js

@@ -79,7 +79,7 @@ var BABYLON;
             return this._color.toHexString();
         };
         SolidColorBrush2D = __decorate([
-            BABYLON.className("SolidColorBrush2D")
+            BABYLON.className("SolidColorBrush2D", "BABYLON")
         ], SolidColorBrush2D);
         return SolidColorBrush2D;
     }(LockableBase));
@@ -204,7 +204,7 @@ var BABYLON;
             return "C1:" + color1 + ";C2:" + color2 + ";T:" + translation.toString() + ";R:" + rotation + ";S:" + scale + ";";
         };
         GradientColorBrush2D = __decorate([
-            BABYLON.className("GradientColorBrush2D")
+            BABYLON.className("GradientColorBrush2D", "BABYLON")
         ], GradientColorBrush2D);
         return GradientColorBrush2D;
     }(LockableBase));

+ 2 - 2
src/Canvas2d/babylon.brushes2d.ts

@@ -66,7 +66,7 @@
         }
     }
 
-    @className("SolidColorBrush2D")
+    @className("SolidColorBrush2D", "BABYLON")
     /**
      * This class implements a Brush that will be drawn with a uniform solid color (i.e. the same color everywhere in the content where the brush is assigned to).
      */
@@ -113,7 +113,7 @@
         private _color: Color4;
     }
 
-    @className("GradientColorBrush2D")
+    @className("GradientColorBrush2D", "BABYLON")
     /**
      * This class implements a Gradient Color Brush, the brush color will blend from a first given color to a second one.
      */

+ 81 - 7
src/Canvas2d/babylon.canvas2d.js

@@ -86,6 +86,7 @@ var BABYLON;
             this._updateLocalTransformCounter = new BABYLON.PerfCounter();
             this._updateGlobalTransformCounter = new BABYLON.PerfCounter();
             this._boundingInfoRecomputeCounter = new BABYLON.PerfCounter();
+            this._uid = null;
             this._cachedCanvasGroup = null;
             this._profileInfoText = null;
             BABYLON.Prim2DBase._isCanvasInit = false;
@@ -169,6 +170,8 @@ var BABYLON;
             this._supprtInstancedArray = this._engine.getCaps().instancedArrays !== null;
             //this._supprtInstancedArray = false; // TODO REMOVE!!!
             this._setupInteraction(enableInteraction);
+            // Register this instance
+            Canvas2D._INSTANCES.push(this);
         }
         Object.defineProperty(Canvas2D.prototype, "drawCallsOpaqueCounter", {
             get: function () {
@@ -254,6 +257,13 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Canvas2D, "instances", {
+            get: function () {
+                return Canvas2D._INSTANCES;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Canvas2D.prototype._canvasPreInit = function (settings) {
             var cachingStrategy = (settings.cachingStrategy == null) ? Canvas2D.CACHESTRATEGY_DONTCACHE : settings.cachingStrategy;
             this._cachingStrategy = cachingStrategy;
@@ -375,7 +385,7 @@ var BABYLON;
             // Why before rendering the canvas? because some primitives may move and get away/under the mouse cursor (which is not moving). So we need to update at both location in order to always have an accurate list, which is needed for the hover state change.
             this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, capturedPrim !== null, true);
             // Update the over status, same as above, it's could be done here or during rendering, but will be performed only once per render frame
-            this._updateOverStatus();
+            this._updateOverStatus(true);
             // Check if we have nothing to raise
             if (!this._actualOverPrimitive && !capturedPrim) {
                 return;
@@ -478,11 +488,14 @@ var BABYLON;
             this._actualIntersectionList = ii.intersectedPrimitives;
             this._previousOverPrimitive = this._actualOverPrimitive;
             this._actualOverPrimitive = ii.topMostIntersectedPrimitive;
+            if ((!this._actualOverPrimitive && !this._previousOverPrimitive) || !(this._actualOverPrimitive && this._previousOverPrimitive && this._actualOverPrimitive.prim === this._previousOverPrimitive.prim)) {
+                this.onPropertyChanged("overPrim", this._previousOverPrimitive ? this._previousOverPrimitive.prim : null, this._actualOverPrimitive ? this._actualOverPrimitive.prim : null);
+            }
             this._intersectionRenderId = this.scene.getRenderId();
         };
         // Based on the previousIntersectionList and the actualInstersectionList we can determined which primitives are being hover state or loosing it
-        Canvas2D.prototype._updateOverStatus = function () {
-            if ((this.scene.getRenderId() === this._hoverStatusRenderId) || !this._previousIntersectionList || !this._actualIntersectionList) {
+        Canvas2D.prototype._updateOverStatus = function (force) {
+            if ((!force && (this.scene.getRenderId() === this._hoverStatusRenderId)) || !this._previousIntersectionList || !this._actualIntersectionList) {
                 return;
             }
             // Detect a change of over
@@ -612,6 +625,7 @@ var BABYLON;
                             var ppi = _this._primPointerInfo;
                             var capturedPrim = _this.getCapturedPrimitive(ppi.pointerId);
                             _this._updateIntersectionList(ppi.canvasPointerPos, capturedPrim !== null, true);
+                            _this._updateOverStatus(false);
                             var ii = new BABYLON.IntersectInfo2D();
                             ii.pickPosition = ppi.canvasPointerPos.clone();
                             ii.findFirstOnly = false;
@@ -701,6 +715,11 @@ var BABYLON;
                 this._groupCacheMaps.forEach(function (k, m) { return m.forEach(function (e) { return e.dispose(); }); });
                 this._groupCacheMaps = null;
             }
+            // Unregister this instance
+            var index = Canvas2D._INSTANCES.indexOf(this);
+            if (index > -1) {
+                Canvas2D._INSTANCES.splice(index, 1);
+            }
         };
         Object.defineProperty(Canvas2D.prototype, "scene", {
             /**
@@ -724,6 +743,35 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Canvas2D.prototype, "uid", {
+            /**
+             * return a unique identifier for the Canvas2D
+             */
+            get: function () {
+                if (!this._uid) {
+                    this._uid = BABYLON.Tools.RandomId();
+                }
+                return this._uid;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Canvas2D.prototype, "renderObservable", {
+            /**
+             * And observable called during the Canvas rendering process.
+             * This observable is called twice per render, each time with a different mask:
+             *  - 1: before render is executed
+             *  - 2: after render is executed
+             */
+            get: function () {
+                if (!this._renderObservable) {
+                    this._renderObservable = new BABYLON.Observable();
+                }
+                return this._renderObservable;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Canvas2D.prototype, "cachingStrategy", {
             /**
              * Accessor of the Caching Strategy used by this Canvas.
@@ -886,6 +934,16 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Canvas2D.prototype, "overPrim", {
+            /**
+             * Return
+             */
+            get: function () {
+                return this._actualOverPrimitive ? this._actualOverPrimitive.prim : null;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Canvas2D.prototype, "_engineData", {
             /**
              * Access the babylon.js' engine bound data, do not invoke this method, it's for internal purpose only
@@ -1103,6 +1161,7 @@ var BABYLON;
                     scale = this._renderingSize.height / this._designSize.height;
                 }
                 this.size = this._designSize.clone();
+                this.actualSize = this._designSize.clone();
                 this.scale = scale;
             }
             var context = new BABYLON.PrepareRender2DContext();
@@ -1116,6 +1175,9 @@ var BABYLON;
          */
         Canvas2D.prototype._render = function () {
             this._initPerfMetrics();
+            if (this._renderObservable && this._renderObservable.hasObservers()) {
+                this._renderObservable.notifyObservers(this, Canvas2D.RENDEROBSERVABLE_PRE);
+            }
             this._updateCanvasState(false);
             this._updateTrackedNodes();
             // Nothing to do is the Canvas is not visible
@@ -1128,7 +1190,7 @@ var BABYLON;
             this._updateCanvasState(false);
             if (this._primPointerInfo.canvasPointerPos) {
                 this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, false, false);
-                this._updateOverStatus(); // TODO this._primPointerInfo may not be up to date!
+                this._updateOverStatus(false);
             }
             this.engine.setState(false);
             this._groupRender();
@@ -1144,6 +1206,9 @@ var BABYLON;
             }
             this._fetchPerfMetrics();
             this._updateProfileCanvas();
+            if (this._renderObservable && this._renderObservable.hasObservers()) {
+                this._renderObservable.notifyObservers(this, Canvas2D.RENDEROBSERVABLE_POST);
+            }
         };
         /**
          * Internal method that allocate a cache for the given group.
@@ -1369,6 +1434,15 @@ var BABYLON;
          * Note that you can't use this strategy for WorldSpace Canvas, they need at least a top level group caching.
          */
         Canvas2D.CACHESTRATEGY_DONTCACHE = 4;
+        /**
+         * Observable Mask to be notified before rendering is made
+         */
+        Canvas2D.RENDEROBSERVABLE_PRE = 1;
+        /**
+         * Observable Mask to be notified after rendering is made
+         */
+        Canvas2D.RENDEROBSERVABLE_POST = 2;
+        Canvas2D._INSTANCES = [];
         Canvas2D._zMinDelta = 1 / (Math.pow(2, 24) - 1);
         Canvas2D._interInfo = new BABYLON.IntersectInfo2D();
         Canvas2D._v = BABYLON.Vector3.Zero(); // Must stay zero
@@ -1383,7 +1457,7 @@ var BABYLON;
         Canvas2D._solidColorBrushes = new BABYLON.StringDictionary();
         Canvas2D._gradientColorBrushes = new BABYLON.StringDictionary();
         Canvas2D = __decorate([
-            BABYLON.className("Canvas2D")
+            BABYLON.className("Canvas2D", "BABYLON")
         ], Canvas2D);
         return Canvas2D;
     }(BABYLON.Group2D));
@@ -1475,7 +1549,7 @@ var BABYLON;
             }, BABYLON.Prim2DBase.isVisibleProperty.flagId);
         }
         WorldSpaceCanvas2D = __decorate([
-            BABYLON.className("WorldSpaceCanvas2D")
+            BABYLON.className("WorldSpaceCanvas2D", "BABYLON")
         ], WorldSpaceCanvas2D);
         return WorldSpaceCanvas2D;
     }(Canvas2D));
@@ -1518,7 +1592,7 @@ var BABYLON;
             _super.call(this, scene, settings);
         }
         ScreenSpaceCanvas2D = __decorate([
-            BABYLON.className("ScreenSpaceCanvas2D")
+            BABYLON.className("ScreenSpaceCanvas2D", "BABYLON")
         ], ScreenSpaceCanvas2D);
         return ScreenSpaceCanvas2D;
     }(Canvas2D));

+ 81 - 8
src/Canvas2d/babylon.canvas2d.ts

@@ -19,7 +19,7 @@
         }
     }
 
-    @className("Canvas2D")
+    @className("Canvas2D", "BABYLON")
     /**
      * The Canvas2D main class.
      * This class is extended in both ScreenSpaceCanvas2D and WorldSpaceCanvas2D which are designed only for semantic use.
@@ -53,6 +53,19 @@
          */
         public static CACHESTRATEGY_DONTCACHE = 4;
 
+        /**
+         * Observable Mask to be notified before rendering is made
+         */
+        public static RENDEROBSERVABLE_PRE = 1;
+
+        /**
+         * Observable Mask to be notified after rendering is made
+         */
+        public static RENDEROBSERVABLE_POST = 2;
+
+
+        private static _INSTANCES : Array<Canvas2D> = [];
+
         constructor(scene: Scene, settings?: {
             id?: string,
             children?: Array<Prim2DBase>,
@@ -84,6 +97,7 @@
             this._updateGlobalTransformCounter = new PerfCounter();
             this._boundingInfoRecomputeCounter = new PerfCounter();
 
+            this._uid = null;
             this._cachedCanvasGroup = null;
 
             this._profileInfoText = null;
@@ -183,6 +197,9 @@
                         //this._supprtInstancedArray = false; // TODO REMOVE!!!
 
             this._setupInteraction(enableInteraction);
+
+            // Register this instance
+            Canvas2D._INSTANCES.push(this);
         }
 
         public get drawCallsOpaqueCounter(): PerfCounter {
@@ -233,6 +250,10 @@
             return this._boundingInfoRecomputeCounter;
         }
 
+        public static get instances() : Array<Canvas2D> {
+            return Canvas2D._INSTANCES;
+        }
+
         protected _canvasPreInit(settings: any) {
             let cachingStrategy = (settings.cachingStrategy == null) ? Canvas2D.CACHESTRATEGY_DONTCACHE : settings.cachingStrategy;
             this._cachingStrategy = cachingStrategy;
@@ -425,7 +446,7 @@
             this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, capturedPrim !== null, true);
 
             // Update the over status, same as above, it's could be done here or during rendering, but will be performed only once per render frame
-            this._updateOverStatus();
+            this._updateOverStatus(true);
 
             // Check if we have nothing to raise
             if (!this._actualOverPrimitive && !capturedPrim) {
@@ -543,12 +564,16 @@
             this._previousOverPrimitive = this._actualOverPrimitive;
             this._actualOverPrimitive = ii.topMostIntersectedPrimitive;
 
+            if ((!this._actualOverPrimitive && !this._previousOverPrimitive) || !(this._actualOverPrimitive && this._previousOverPrimitive && this._actualOverPrimitive.prim === this._previousOverPrimitive.prim)) {
+                this.onPropertyChanged("overPrim", this._previousOverPrimitive ? this._previousOverPrimitive.prim : null, this._actualOverPrimitive ? this._actualOverPrimitive.prim : null);
+            }
+
             this._intersectionRenderId = this.scene.getRenderId();
         }
 
         // Based on the previousIntersectionList and the actualInstersectionList we can determined which primitives are being hover state or loosing it
-        private _updateOverStatus() {
-            if ((this.scene.getRenderId() === this._hoverStatusRenderId) || !this._previousIntersectionList || !this._actualIntersectionList) {
+        private _updateOverStatus(force: boolean) {
+            if ((!force && (this.scene.getRenderId() === this._hoverStatusRenderId)) || !this._previousIntersectionList || !this._actualIntersectionList) {
                 return;
             }
 
@@ -703,6 +728,7 @@
                             let ppi = this._primPointerInfo;
                             let capturedPrim = this.getCapturedPrimitive(ppi.pointerId);
                             this._updateIntersectionList(ppi.canvasPointerPos, capturedPrim !== null, true);
+                            this._updateOverStatus(false);
 
                             let ii = new IntersectInfo2D();
                             ii.pickPosition = ppi.canvasPointerPos.clone();
@@ -812,7 +838,13 @@
             if (this._groupCacheMaps) {
                 this._groupCacheMaps.forEach((k, m) => m.forEach(e => e.dispose()));
                 this._groupCacheMaps = null;
-            }
+            }       
+
+            // Unregister this instance
+            let index = Canvas2D._INSTANCES.indexOf(this);
+            if (index > -1) {
+                Canvas2D._INSTANCES.splice(index, 1);
+            }  
         }
 
         /**
@@ -832,6 +864,29 @@
         }
 
         /**
+         * return a unique identifier for the Canvas2D
+         */
+        public get uid(): string {
+            if (!this._uid) {
+                this._uid = Tools.RandomId();
+            }
+            return this._uid;
+        }
+
+        /**
+         * And observable called during the Canvas rendering process.
+         * This observable is called twice per render, each time with a different mask:
+         *  - 1: before render is executed
+         *  - 2: after render is executed
+         */
+        public get renderObservable(): Observable<Canvas2D> {
+            if (!this._renderObservable) {
+                this._renderObservable = new Observable<Canvas2D>();
+            }
+            return this._renderObservable;
+        }
+
+        /**
          * Accessor of the Caching Strategy used by this Canvas.
          * See Canvas2D.CACHESTRATEGY_xxxx static members for more information
          * @returns the value corresponding to the used strategy.
@@ -975,6 +1030,13 @@
         }
 
         /**
+         * Return 
+         */
+        public get overPrim(): Prim2DBase {
+            return this._actualOverPrimitive ? this._actualOverPrimitive.prim : null;
+        }
+
+        /**
          * Access the babylon.js' engine bound data, do not invoke this method, it's for internal purpose only
          * @returns {} 
          */
@@ -1110,6 +1172,8 @@
             this._updateGlobalTransformCounter.addCount(count, false);
         }
 
+        private _uid: string;
+        private _renderObservable: Observable<Canvas2D>;
         private __engineData: Canvas2DEngineBoundData;
         private _interactionEnabled: boolean;
         private _primPointerInfo: PrimitivePointerInfo;
@@ -1285,6 +1349,7 @@
                     scale = this._renderingSize.height / this._designSize.height;
                 }
                 this.size = this._designSize.clone();
+                this.actualSize = this._designSize.clone();
                 this.scale = scale;
             }
 
@@ -1305,6 +1370,10 @@
 
             this._initPerfMetrics();
 
+            if (this._renderObservable && this._renderObservable.hasObservers()) {
+                this._renderObservable.notifyObservers(this, Canvas2D.RENDEROBSERVABLE_PRE);
+            }
+
             this._updateCanvasState(false);
 
             this._updateTrackedNodes();
@@ -1322,7 +1391,7 @@
 
             if (this._primPointerInfo.canvasPointerPos) {
                 this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, false, false);
-                this._updateOverStatus();   // TODO this._primPointerInfo may not be up to date!
+                this._updateOverStatus(false);
             }
 
             this.engine.setState(false);
@@ -1342,6 +1411,10 @@
 
             this._fetchPerfMetrics();
             this._updateProfileCanvas();
+
+            if (this._renderObservable && this._renderObservable.hasObservers()) {
+                this._renderObservable.notifyObservers(this, Canvas2D.RENDEROBSERVABLE_POST);
+            }
         }
 
         private static _unS = new Vector2(1, 1);
@@ -1581,7 +1654,7 @@
         private static _gradientColorBrushes: StringDictionary<IBrush2D> = new StringDictionary<IBrush2D>();
     }
 
-    @className("WorldSpaceCanvas2D")
+    @className("WorldSpaceCanvas2D", "BABYLON")
     /**
      * Class to create a WorldSpace Canvas2D.
      */
@@ -1701,7 +1774,7 @@
         }
     }
 
-    @className("ScreenSpaceCanvas2D")
+    @className("ScreenSpaceCanvas2D", "BABYLON")
     /**
      * Class to create a ScreenSpace Canvas2D
      */

+ 3 - 3
src/Canvas2d/babylon.canvas2dLayoutEngine.js

@@ -35,7 +35,7 @@ var BABYLON;
             return true;
         };
         LayoutEngineBase = __decorate([
-            BABYLON.className("LayoutEngineBase")
+            BABYLON.className("LayoutEngineBase", "BABYLON")
         ], LayoutEngineBase);
         return LayoutEngineBase;
     }());
@@ -79,7 +79,7 @@ var BABYLON;
         });
         CanvasLayoutEngine.Singleton = new CanvasLayoutEngine();
         CanvasLayoutEngine = __decorate([
-            BABYLON.className("CanvasLayoutEngine")
+            BABYLON.className("CanvasLayoutEngine", "BABYLON")
         ], CanvasLayoutEngine);
         return CanvasLayoutEngine;
     }(LayoutEngineBase));
@@ -176,7 +176,7 @@ var BABYLON;
         StackPanelLayoutEngine.dstOffset = BABYLON.Vector2.Zero();
         StackPanelLayoutEngine.dstArea = BABYLON.Size.Zero();
         StackPanelLayoutEngine = __decorate([
-            BABYLON.className("StackPanelLayoutEngine")
+            BABYLON.className("StackPanelLayoutEngine", "BABYLON")
         ], StackPanelLayoutEngine);
         return StackPanelLayoutEngine;
     }(LayoutEngineBase));

+ 3 - 3
src/Canvas2d/babylon.canvas2dLayoutEngine.ts

@@ -1,6 +1,6 @@
 module BABYLON {
 
-    @className("LayoutEngineBase")
+    @className("LayoutEngineBase", "BABYLON")
     /**
      * This is the base class you have to extend in order to implement your own Layout Engine.
      * Note that for performance reason, each different Layout Engine type can be exposed as one/many singleton or must be instanced each time.
@@ -35,7 +35,7 @@
         private _isLocked: boolean;
     }
 
-    @className("CanvasLayoutEngine")
+    @className("CanvasLayoutEngine", "BABYLON")
     /**
      * The default Layout Engine, primitive are positioning into a Canvas, using their x/y coordinates.
      * This layout must be used as a Singleton through the CanvasLayoutEngine.Singleton property.
@@ -82,7 +82,7 @@
     }
 
 
-    @className("StackPanelLayoutEngine")
+    @className("StackPanelLayoutEngine", "BABYLON")
     /**
      * A stack panel layout. Primitive will be stack either horizontally or vertically.
      * This Layout type must be used as a Singleton, use the StackPanelLayoutEngine.Horizontal for an horizontal stack panel or StackPanelLayoutEngine.Vertical for a vertical one.

+ 1 - 1
src/Canvas2d/babylon.ellipse2d.js

@@ -347,7 +347,7 @@ var BABYLON;
             BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 2, function (pi) { return Ellipse2D.subdivisionsProperty = pi; })
         ], Ellipse2D.prototype, "subdivisions", null);
         Ellipse2D = __decorate([
-            BABYLON.className("Ellipse2D")
+            BABYLON.className("Ellipse2D", "BABYLON")
         ], Ellipse2D);
         return Ellipse2D;
     }(BABYLON.Shape2D));

+ 1 - 1
src/Canvas2d/babylon.ellipse2d.ts

@@ -159,7 +159,7 @@
         }
     }
 
-    @className("Ellipse2D")
+    @className("Ellipse2D", "BABYLON")
     /**
      * Ellipse Primitive class
      */

+ 1 - 1
src/Canvas2d/babylon.group2d.js

@@ -852,7 +852,7 @@ var BABYLON;
             BABYLON.instanceLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 2, function (pi) { return Group2D.actualSizeProperty = pi; })
         ], Group2D.prototype, "actualSize", null);
         Group2D = __decorate([
-            BABYLON.className("Group2D")
+            BABYLON.className("Group2D", "BABYLON")
         ], Group2D);
         return Group2D;
     }(BABYLON.Prim2DBase));

+ 1 - 1
src/Canvas2d/babylon.group2d.ts

@@ -1,5 +1,5 @@
 module BABYLON {
-    @className("Group2D")
+    @className("Group2D", "BABYLON")
     /**
      * A non renderable primitive that defines a logical group.
      * Can also serve the purpose of caching its content into a bitmap to reduce rendering overhead

+ 1 - 1
src/Canvas2d/babylon.lines2d.js

@@ -1172,7 +1172,7 @@ var BABYLON;
             BABYLON.modelLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 5, function (pi) { return Lines2D.endCapProperty = pi; })
         ], Lines2D.prototype, "endCap", null);
         Lines2D = __decorate([
-            BABYLON.className("Lines2D")
+            BABYLON.className("Lines2D", "BABYLON")
         ], Lines2D);
         return Lines2D;
     }(BABYLON.Shape2D));

+ 2 - 1
src/Canvas2d/babylon.lines2d.ts

@@ -163,9 +163,10 @@
         }
         set boundingMax(value: Vector2) {
         }
+
     }
 
-    @className("Lines2D")
+    @className("Lines2D", "BABYLON")
     /**
      * Primitive drawing a series of line segments
      */

src/Canvas2d/babylon.modelRenderCache.js → canvas2D/src/Engine/babylon.modelRenderCache.js


src/Canvas2d/babylon.modelRenderCache.ts → canvas2D/src/Engine/babylon.modelRenderCache.ts


+ 203 - 67
src/Canvas2d/babylon.prim2dBase.js

@@ -304,7 +304,7 @@ var BABYLON;
                     return;
                 }
                 this._horizontal = value;
-                this._changedCallback();
+                this.onChangeCallback();
             },
             enumerable: true,
             configurable: true
@@ -321,11 +321,16 @@ var BABYLON;
                     return;
                 }
                 this._vertical = value;
-                this._changedCallback();
+                this.onChangeCallback();
             },
             enumerable: true,
             configurable: true
         });
+        PrimitiveAlignment.prototype.onChangeCallback = function () {
+            if (this._changedCallback) {
+                this._changedCallback();
+            }
+        };
         /**
          * Set the horizontal alignment from a string value.
          * @param text can be either: 'left','right','center','stretch'
@@ -374,37 +379,58 @@ var BABYLON;
          */
         PrimitiveAlignment.prototype.fromString = function (value) {
             var m = value.trim().split(",");
-            for (var _i = 0, m_1 = m; _i < m_1.length; _i++) {
-                var v = m_1[_i];
-                v = v.toLocaleLowerCase().trim();
-                // Horizontal
-                var i = v.indexOf("h:");
-                if (i === -1) {
-                    i = v.indexOf("horizontal:");
-                }
-                if (i !== -1) {
-                    v = v.substr(v.indexOf(":") + 1);
-                    this.setHorizontal(v);
-                    continue;
-                }
-                // Vertical
-                i = v.indexOf("v:");
-                if (i === -1) {
-                    i = v.indexOf("vertical:");
-                }
-                if (i !== -1) {
-                    v = v.substr(v.indexOf(":") + 1);
-                    this.setVertical(v);
-                    continue;
+            if (m.length === 1) {
+                this.setHorizontal(m[0]);
+                this.setVertical(m[0]);
+            }
+            else {
+                for (var _i = 0, m_1 = m; _i < m_1.length; _i++) {
+                    var v = m_1[_i];
+                    v = v.toLocaleLowerCase().trim();
+                    // Horizontal
+                    var i = v.indexOf("h:");
+                    if (i === -1) {
+                        i = v.indexOf("horizontal:");
+                    }
+                    if (i !== -1) {
+                        v = v.substr(v.indexOf(":") + 1);
+                        this.setHorizontal(v);
+                        continue;
+                    }
+                    // Vertical
+                    i = v.indexOf("v:");
+                    if (i === -1) {
+                        i = v.indexOf("vertical:");
+                    }
+                    if (i !== -1) {
+                        v = v.substr(v.indexOf(":") + 1);
+                        this.setVertical(v);
+                        continue;
+                    }
                 }
             }
         };
+        PrimitiveAlignment.prototype.copyFrom = function (pa) {
+            this._horizontal = pa._horizontal;
+            this._vertical = pa._vertical;
+            this.onChangeCallback();
+        };
+        Object.defineProperty(PrimitiveAlignment.prototype, "isDefault", {
+            get: function () {
+                return this.horizontal === PrimitiveAlignment.AlignLeft && this.vertical === PrimitiveAlignment.AlignBottom;
+            },
+            enumerable: true,
+            configurable: true
+        });
         PrimitiveAlignment._AlignLeft = 1;
         PrimitiveAlignment._AlignTop = 1; // Same as left
         PrimitiveAlignment._AlignRight = 2;
         PrimitiveAlignment._AlignBottom = 2; // Same as right
         PrimitiveAlignment._AlignCenter = 3;
         PrimitiveAlignment._AlignStretch = 4;
+        PrimitiveAlignment = __decorate([
+            BABYLON.className("PrimitiveAlignment", "BABYLON")
+        ], PrimitiveAlignment);
         return PrimitiveAlignment;
     }());
     BABYLON.PrimitiveAlignment = PrimitiveAlignment;
@@ -452,7 +478,7 @@ var BABYLON;
                 this._setStringValue(m[0], 1, false);
                 this._setStringValue(m[0], 2, false);
                 this._setStringValue(m[0], 3, false);
-                this._changedCallback();
+                this.onChangeCallback();
                 return;
             }
             var res = false;
@@ -472,7 +498,7 @@ var BABYLON;
                 this._flags |= PrimitiveThickness.Pixel << 8;
             if ((this._flags & 0xF000) === 0)
                 this._flags |= PrimitiveThickness.Pixel << 12;
-            this._changedCallback();
+            this.onChangeCallback();
         };
         /**
          * Set the thickness from multiple string
@@ -488,7 +514,7 @@ var BABYLON;
             this._setStringValue(left, 1, false);
             this._setStringValue(right, 2, false);
             this._setStringValue(bottom, 3, false);
-            this._changedCallback();
+            this.onChangeCallback();
             return this;
         };
         /**
@@ -504,7 +530,7 @@ var BABYLON;
             this._pixels[1] = left;
             this._pixels[2] = right;
             this._pixels[3] = bottom;
-            this._changedCallback();
+            this.onChangeCallback();
             return this;
         };
         /**
@@ -517,9 +543,18 @@ var BABYLON;
             this._pixels[1] = margin;
             this._pixels[2] = margin;
             this._pixels[3] = margin;
-            this._changedCallback();
+            this.onChangeCallback();
             return this;
         };
+        PrimitiveThickness.prototype.copyFrom = function (pt) {
+            this._clear();
+            for (var i = 0; i < 4; i++) {
+                this._pixels[i] = pt._pixels[i];
+                this._percentages[i] = pt._percentages[i];
+            }
+            this._flags = pt._flags;
+            this.onChangeCallback();
+        };
         /**
          * Set all edges in auto
          */
@@ -530,7 +565,7 @@ var BABYLON;
             this._pixels[1] = 0;
             this._pixels[2] = 0;
             this._pixels[3] = 0;
-            this._changedCallback();
+            this.onChangeCallback();
             return this;
         };
         PrimitiveThickness.prototype._clear = function () {
@@ -574,7 +609,7 @@ var BABYLON;
                 this._setType(index, PrimitiveThickness.Auto);
                 this._pixels[index] = 0;
                 if (emitChanged) {
-                    this._changedCallback();
+                    this.onChangeCallback();
                 }
             }
             else if (v === "inherit") {
@@ -584,7 +619,7 @@ var BABYLON;
                 this._setType(index, PrimitiveThickness.Inherit);
                 this._pixels[index] = null;
                 if (emitChanged) {
-                    this._changedCallback();
+                    this.onChangeCallback();
                 }
             }
             else {
@@ -602,7 +637,7 @@ var BABYLON;
                     }
                     this._percentages[index] = number_1;
                     if (emitChanged) {
-                        this._changedCallback();
+                        this.onChangeCallback();
                     }
                     return true;
                 }
@@ -625,7 +660,7 @@ var BABYLON;
                 this._pixels[index] = number;
                 this._setType(index, PrimitiveThickness.Pixel);
                 if (emitChanged) {
-                    this._changedCallback();
+                    this.onChangeCallback();
                 }
                 return true;
             }
@@ -639,7 +674,7 @@ var BABYLON;
             this._setType(index, PrimitiveThickness.Pixel);
             this._pixels[index] = value;
             if (emitChanged) {
-                this._changedCallback();
+                this.onChangeCallback();
             }
         };
         PrimitiveThickness.prototype._setPercentage = function (value, index, emitChanged) {
@@ -653,7 +688,7 @@ var BABYLON;
             this._setType(index, PrimitiveThickness.Percentage);
             this._percentages[index] = value;
             if (emitChanged) {
-                this._changedCallback();
+                this.onChangeCallback();
             }
         };
         PrimitiveThickness.prototype._getStringValue = function (index) {
@@ -937,6 +972,13 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(PrimitiveThickness.prototype, "isDefault", {
+            get: function () {
+                return this._flags === 0x1111;
+            },
+            enumerable: true,
+            configurable: true
+        });
         PrimitiveThickness.prototype._computePixels = function (index, sourceArea, emitChanged) {
             var type = this._getType(index, false);
             if (type === PrimitiveThickness.Inherit) {
@@ -949,6 +991,11 @@ var BABYLON;
             var pixels = ((index === 0 || index === 3) ? sourceArea.height : sourceArea.width) * this._percentages[index];
             this._pixels[index] = pixels;
             if (emitChanged) {
+                this.onChangeCallback();
+            }
+        };
+        PrimitiveThickness.prototype.onChangeCallback = function () {
+            if (this._changedCallback) {
                 this._changedCallback();
             }
         };
@@ -1143,6 +1190,9 @@ var BABYLON;
         PrimitiveThickness.Inherit = 0x2;
         PrimitiveThickness.Percentage = 0x4;
         PrimitiveThickness.Pixel = 0x8;
+        PrimitiveThickness = __decorate([
+            BABYLON.className("PrimitiveThickness", "BABYLON")
+        ], PrimitiveThickness);
         return PrimitiveThickness;
     }());
     BABYLON.PrimitiveThickness = PrimitiveThickness;
@@ -1235,7 +1285,6 @@ var BABYLON;
             this._padding = null;
             this._marginAlignment = null;
             this._id = settings.id;
-            this.propertyChanged = new BABYLON.Observable();
             this._children = new Array();
             this._localTransform = new BABYLON.Matrix();
             this._globalTransform = null;
@@ -1480,6 +1529,10 @@ var BABYLON;
             get: function () {
                 return this.actualPosition.x;
             },
+            set: function (val) {
+                this._actualPosition.x = val;
+                this._triggerPropertyChanged(Prim2DBase.actualPositionProperty, this._actualPosition);
+            },
             enumerable: true,
             configurable: true
         });
@@ -1490,6 +1543,10 @@ var BABYLON;
             get: function () {
                 return this.actualPosition.y;
             },
+            set: function (val) {
+                this._actualPosition.y = val;
+                this._triggerPropertyChanged(Prim2DBase.actualPositionProperty, this._actualPosition);
+            },
             enumerable: true,
             configurable: true
         });
@@ -1608,14 +1665,15 @@ var BABYLON;
                 return this.size.width;
             },
             set: function (value) {
+                if (this.size && this.size.width === value) {
+                    return;
+                }
                 if (!this.size) {
                     this.size = new BABYLON.Size(value, 0);
-                    return;
                 }
-                if (this.size.width === value) {
-                    return;
+                else {
+                    this.size.width = value;
                 }
-                this.size.width = value;
                 this._triggerPropertyChanged(Prim2DBase.sizeProperty, value);
                 this._positioningDirty();
             },
@@ -1634,14 +1692,15 @@ var BABYLON;
                 return this.size.height;
             },
             set: function (value) {
+                if (this.size && this.size.height === value) {
+                    return;
+                }
                 if (!this.size) {
                     this.size = new BABYLON.Size(0, value);
-                    return;
                 }
-                if (this.size.height === value) {
-                    return;
+                else {
+                    this.size.height = value;
                 }
-                this.size.height = value;
                 this._triggerPropertyChanged(Prim2DBase.sizeProperty, value);
                 this._positioningDirty();
             },
@@ -1692,6 +1751,34 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Prim2DBase.prototype, "actualWidth", {
+            /**
+             * Shortcut to actualSize.width
+             */
+            get: function () {
+                return this.actualSize.width;
+            },
+            set: function (val) {
+                this._actualSize.width = val;
+                this._triggerPropertyChanged(Prim2DBase.actualSizeProperty, this._actualSize);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "actualHeight", {
+            /**
+             * Shortcut to actualPosition.height
+             */
+            get: function () {
+                return this.actualSize.width;
+            },
+            set: function (val) {
+                this._actualSize.height = val;
+                this._triggerPropertyChanged(Prim2DBase.actualPositionProperty, this._actualSize);
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Prim2DBase.prototype, "actualZOffset", {
             get: function () {
                 if (this._manualZOrder != null) {
@@ -1819,12 +1906,18 @@ var BABYLON;
                 }
                 return this._margin;
             },
+            set: function (value) {
+                this.margin.copyFrom(value);
+            },
             enumerable: true,
             configurable: true
         });
         Object.defineProperty(Prim2DBase.prototype, "_hasMargin", {
+            /**
+             * Check for both margin and marginAlignment, return true if at least one of them is specified with a non default value
+             */
             get: function () {
-                return (this._margin !== null) || (this._marginAlignment !== null);
+                return (this._margin !== null && !this._margin.isDefault) || (this._marginAlignment !== null && !this._marginAlignment.isDefault);
             },
             enumerable: true,
             configurable: true
@@ -1842,12 +1935,15 @@ var BABYLON;
                 }
                 return this._padding;
             },
+            set: function (value) {
+                this.padding.copyFrom(value);
+            },
             enumerable: true,
             configurable: true
         });
         Object.defineProperty(Prim2DBase.prototype, "_hasPadding", {
             get: function () {
-                return this._padding !== null;
+                return this._padding !== null && !this._padding.isDefault;
             },
             enumerable: true,
             configurable: true
@@ -1860,6 +1956,19 @@ var BABYLON;
                 }
                 return this._marginAlignment;
             },
+            set: function (value) {
+                this.marginAlignment.copyFrom(value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Prim2DBase.prototype, "_hasMarginAlignment", {
+            /**
+             * Check if there a marginAlignment specified (non null and not default)
+             */
+            get: function () {
+                return (this._marginAlignment !== null && !this._marginAlignment.isDefault);
+            },
             enumerable: true,
             configurable: true
         });
@@ -2708,12 +2817,13 @@ var BABYLON;
             // We know have to :
             //  1. Determine the PaddingArea and the ActualPosition based on the margin/marginAlignment properties, which will also set the size property of the primitive
             //  2. Determine the contentArea based on the padding property.
+            var isSizeAuto = this.isSizeAuto;
             // Auto Create PaddingArea if there's no actualSize on width&|height to allocate the whole content available to the paddingArea where the actualSize is null
-            if (!this._hasMargin && (this.actualSize.width == null || this.actualSize.height == null)) {
-                if (this.actualSize.width == null) {
+            if (!this._hasMarginAlignment && (isSizeAuto || (this.actualSize.width == null || this.actualSize.height == null))) {
+                if (isSizeAuto || this.actualSize.width == null) {
                     this.marginAlignment.horizontal = PrimitiveAlignment.AlignStretch;
                 }
-                if (this.actualSize.height == null) {
+                if (isSizeAuto || this.actualSize.height == null) {
                     this.marginAlignment.vertical = PrimitiveAlignment.AlignStretch;
                 }
             }
@@ -2722,7 +2832,6 @@ var BABYLON;
                 this.margin.computeWithAlignment(this.layoutArea, this.size || this.actualSize, this.marginAlignment, this._marginOffset, Prim2DBase._size);
                 this.actualSize = Prim2DBase._size.clone();
             }
-            var isSizeAuto = this.isSizeAuto;
             if (this._hasPadding) {
                 // Two cases from here: the size of the Primitive is Auto, its content can't be shrink, so me resize the primitive itself
                 if (isSizeAuto) {
@@ -2966,7 +3075,7 @@ var BABYLON;
         Prim2DBase.prototype._getActualSizeFromContentToRef = function (primSize, newPrimSize) {
             newPrimSize.copyFrom(primSize);
         };
-        Prim2DBase.PRIM2DBASE_PROPCOUNT = 16;
+        Prim2DBase.PRIM2DBASE_PROPCOUNT = 24;
         Prim2DBase._bigInt = Math.pow(2, 30);
         Prim2DBase._nullPosition = BABYLON.Vector2.Zero();
         Prim2DBase.boundinbBoxReentrency = false;
@@ -2989,49 +3098,76 @@ var BABYLON;
             BABYLON.instanceLevelProperty(1, function (pi) { return Prim2DBase.actualPositionProperty = pi; }, false, false, true)
         ], Prim2DBase.prototype, "actualPosition", null);
         __decorate([
-            BABYLON.dynamicLevelProperty(2, function (pi) { return Prim2DBase.positionProperty = pi; }, false, false, true)
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 1, function (pi) { return Prim2DBase.actualXProperty = pi; }, false, false, true)
+        ], Prim2DBase.prototype, "actualX", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 2, function (pi) { return Prim2DBase.actualYProperty = pi; }, false, false, true)
+        ], Prim2DBase.prototype, "actualY", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 3, function (pi) { return Prim2DBase.positionProperty = pi; }, false, false, true)
         ], Prim2DBase.prototype, "position", null);
         __decorate([
-            BABYLON.dynamicLevelProperty(3, function (pi) { return Prim2DBase.sizeProperty = pi; }, false, true)
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 4, function (pi) { return Prim2DBase.xProperty = pi; }, false, false, true)
+        ], Prim2DBase.prototype, "x", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 5, function (pi) { return Prim2DBase.yProperty = pi; }, false, false, true)
+        ], Prim2DBase.prototype, "y", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 6, function (pi) { return Prim2DBase.sizeProperty = pi; }, false, true)
         ], Prim2DBase.prototype, "size", null);
         __decorate([
-            BABYLON.instanceLevelProperty(4, function (pi) { return Prim2DBase.rotationProperty = pi; }, false, true)
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 7, function (pi) { return Prim2DBase.widthProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "width", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 8, function (pi) { return Prim2DBase.heightProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "height", null);
+        __decorate([
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 9, function (pi) { return Prim2DBase.rotationProperty = pi; }, false, true)
         ], Prim2DBase.prototype, "rotation", null);
         __decorate([
-            BABYLON.instanceLevelProperty(5, function (pi) { return Prim2DBase.scaleProperty = pi; }, false, true)
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 10, function (pi) { return Prim2DBase.scaleProperty = pi; }, false, true)
         ], Prim2DBase.prototype, "scale", null);
         __decorate([
-            BABYLON.dynamicLevelProperty(6, function (pi) { return Prim2DBase.originProperty = pi; }, false, true)
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 11, function (pi) { return Prim2DBase.actualSizeProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "actualSize", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 12, function (pi) { return Prim2DBase.actualWidthProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "actualWidth", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 13, function (pi) { return Prim2DBase.actualHeightProperty = pi; }, false, true)
+        ], Prim2DBase.prototype, "actualHeight", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 14, function (pi) { return Prim2DBase.originProperty = pi; }, false, true)
         ], Prim2DBase.prototype, "origin", null);
         __decorate([
-            BABYLON.dynamicLevelProperty(7, function (pi) { return Prim2DBase.levelVisibleProperty = pi; })
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 15, function (pi) { return Prim2DBase.levelVisibleProperty = pi; })
         ], Prim2DBase.prototype, "levelVisible", null);
         __decorate([
-            BABYLON.instanceLevelProperty(8, function (pi) { return Prim2DBase.isVisibleProperty = pi; })
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 16, function (pi) { return Prim2DBase.isVisibleProperty = pi; })
         ], Prim2DBase.prototype, "isVisible", null);
         __decorate([
-            BABYLON.instanceLevelProperty(9, function (pi) { return Prim2DBase.zOrderProperty = pi; })
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 17, function (pi) { return Prim2DBase.zOrderProperty = pi; })
         ], Prim2DBase.prototype, "zOrder", null);
         __decorate([
-            BABYLON.dynamicLevelProperty(10, function (pi) { return Prim2DBase.marginProperty = pi; })
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 18, function (pi) { return Prim2DBase.marginProperty = pi; })
         ], Prim2DBase.prototype, "margin", null);
         __decorate([
-            BABYLON.dynamicLevelProperty(11, function (pi) { return Prim2DBase.paddingProperty = pi; })
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 19, function (pi) { return Prim2DBase.paddingProperty = pi; })
         ], Prim2DBase.prototype, "padding", null);
         __decorate([
-            BABYLON.dynamicLevelProperty(12, function (pi) { return Prim2DBase.marginAlignmentProperty = pi; })
+            BABYLON.dynamicLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 20, function (pi) { return Prim2DBase.marginAlignmentProperty = pi; })
         ], Prim2DBase.prototype, "marginAlignment", null);
         __decorate([
-            BABYLON.instanceLevelProperty(13, function (pi) { return Prim2DBase.opacityProperty = pi; })
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 21, function (pi) { return Prim2DBase.opacityProperty = pi; })
         ], Prim2DBase.prototype, "opacity", null);
         __decorate([
-            BABYLON.instanceLevelProperty(14, function (pi) { return Prim2DBase.scaleXProperty = pi; }, false, true)
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 22, function (pi) { return Prim2DBase.scaleXProperty = pi; }, false, true)
         ], Prim2DBase.prototype, "scaleX", null);
         __decorate([
-            BABYLON.instanceLevelProperty(15, function (pi) { return Prim2DBase.scaleYProperty = pi; }, false, true)
+            BABYLON.instanceLevelProperty(BABYLON.SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 23, function (pi) { return Prim2DBase.scaleYProperty = pi; }, false, true)
         ], Prim2DBase.prototype, "scaleY", null);
         Prim2DBase = __decorate([
-            BABYLON.className("Prim2DBase")
+            BABYLON.className("Prim2DBase", "BABYLON")
         ], Prim2DBase);
         return Prim2DBase;
     }(BABYLON.SmartPropertyPrim));

+ 224 - 70
src/Canvas2d/babylon.prim2dBase.ts

@@ -305,8 +305,9 @@
     /**
      * Defines the horizontal and vertical alignment information for a Primitive.
      */
+    @className("PrimitiveAlignment", "BABYLON")
     export class PrimitiveAlignment {
-        constructor(changeCallback: () => void) {
+        constructor(changeCallback?: () => void) {
             this._changedCallback = changeCallback;
             this._horizontal = PrimitiveAlignment.AlignLeft;
             this._vertical = PrimitiveAlignment.AlignBottom;
@@ -362,7 +363,7 @@
             }
 
             this._horizontal = value;
-            this._changedCallback();
+            this.onChangeCallback();
         }
 
         /**
@@ -378,7 +379,13 @@
             }
 
             this._vertical = value;
-            this._changedCallback();
+            this.onChangeCallback();
+        }
+
+        private onChangeCallback() {
+            if (this._changedCallback) {
+                this._changedCallback();
+            }
         }
 
         private _changedCallback: () => void;
@@ -435,34 +442,49 @@
          */
         fromString(value: string) {
             let m = value.trim().split(",");
-            for (let v of m) {
-                v = v.toLocaleLowerCase().trim();
+            if (m.length === 1) {
+                this.setHorizontal(m[0]);
+                this.setVertical(m[0]);
+            } else {
+                for (let v of m) {
+                    v = v.toLocaleLowerCase().trim();
 
-                // Horizontal
-                let i = v.indexOf("h:");
-                if (i === -1) {
-                    i = v.indexOf("horizontal:");
-                }
+                    // Horizontal
+                    let i = v.indexOf("h:");
+                    if (i === -1) {
+                        i = v.indexOf("horizontal:");
+                    }
 
-                if (i !== -1) {
-                    v = v.substr(v.indexOf(":") + 1);
-                    this.setHorizontal(v);
-                    continue;
-                }
+                    if (i !== -1) {
+                        v = v.substr(v.indexOf(":") + 1);
+                        this.setHorizontal(v);
+                        continue;
+                    }
 
-                // Vertical
-                i = v.indexOf("v:");
-                if (i === -1) {
-                    i = v.indexOf("vertical:");
-                }
+                    // Vertical
+                    i = v.indexOf("v:");
+                    if (i === -1) {
+                        i = v.indexOf("vertical:");
+                    }
 
-                if (i !== -1) {
-                    v = v.substr(v.indexOf(":") + 1);
-                    this.setVertical(v);
-                    continue;
+                    if (i !== -1) {
+                        v = v.substr(v.indexOf(":") + 1);
+                        this.setVertical(v);
+                        continue;
+                    }
                 }
             }
         }
+
+        copyFrom(pa: PrimitiveAlignment) {
+            this._horizontal = pa._horizontal;
+            this._vertical = pa._vertical;
+            this.onChangeCallback();
+        }
+
+        public get isDefault(): boolean {
+            return this.horizontal === PrimitiveAlignment.AlignLeft && this.vertical === PrimitiveAlignment.AlignBottom;
+        }
     }
 
     /**
@@ -478,8 +500,9 @@
      * Define a thickness toward every edges of a Primitive to allow margin and padding.
      * The thickness can be expressed as pixels, percentages, inherit the value of the parent primitive or be auto.
      */
+    @className("PrimitiveThickness", "BABYLON")
     export class PrimitiveThickness {
-        constructor(parentAccess: () => PrimitiveThickness, changedCallback: () => void) {
+        constructor(parentAccess: () => PrimitiveThickness, changedCallback?: () => void) {
             this._parentAccess = parentAccess;
             this._changedCallback = changedCallback;
             this._pixels = new Array<number>(4);
@@ -511,7 +534,7 @@
                 this._setStringValue(m[0], 2, false);
                 this._setStringValue(m[0], 3, false);
 
-                this._changedCallback();
+                this.onChangeCallback();
                 return;
             }
 
@@ -530,7 +553,7 @@
             if ((this._flags & 0x0F00) === 0) this._flags |= PrimitiveThickness.Pixel << 8;
             if ((this._flags & 0xF000) === 0) this._flags |= PrimitiveThickness.Pixel << 12;
 
-            this._changedCallback();
+            this.onChangeCallback();
 
         }
 
@@ -549,7 +572,7 @@
             this._setStringValue(left, 1, false);
             this._setStringValue(right, 2, false);
             this._setStringValue(bottom, 3, false);
-            this._changedCallback();
+            this.onChangeCallback();
             return this;
         }
 
@@ -567,7 +590,7 @@
             this._pixels[1] = left;
             this._pixels[2] = right;
             this._pixels[3] = bottom;
-            this._changedCallback();
+            this.onChangeCallback();
             return this;
         }
 
@@ -582,10 +605,20 @@
             this._pixels[1] = margin;
             this._pixels[2] = margin;
             this._pixels[3] = margin;
-            this._changedCallback();
+            this.onChangeCallback();
             return this;
         }
 
+        public copyFrom(pt: PrimitiveThickness) {
+            this._clear();
+            for (let i = 0; i < 4; i++) {
+                this._pixels[i] = pt._pixels[i];
+                this._percentages[i] = pt._percentages[i];
+            }
+            this._flags = pt._flags;
+            this.onChangeCallback();
+        }
+
         /**
          * Set all edges in auto
          */
@@ -597,7 +630,7 @@
             this._pixels[1] = 0;
             this._pixels[2] = 0;
             this._pixels[3] = 0;
-            this._changedCallback();
+            this.onChangeCallback();
             return this;
         }
 
@@ -649,7 +682,7 @@
                 this._setType(index, PrimitiveThickness.Auto);
                 this._pixels[index] = 0;
                 if (emitChanged) {
-                    this._changedCallback();
+                    this.onChangeCallback();
                 }
             } else if (v === "inherit") {
                 if (this._isType(index, PrimitiveThickness.Inherit)) {
@@ -658,7 +691,7 @@
                 this._setType(index, PrimitiveThickness.Inherit);
                 this._pixels[index] = null;
                 if (emitChanged) {
-                    this._changedCallback();
+                    this.onChangeCallback();
                 }
             } else {
                 let pI = v.indexOf("%");
@@ -679,7 +712,7 @@
                     this._percentages[index] = number;
 
                     if (emitChanged) {
-                        this._changedCallback();
+                        this.onChangeCallback();
                     }
 
                     return true;
@@ -703,7 +736,7 @@
                 this._pixels[index] = number;
                 this._setType(index, PrimitiveThickness.Pixel);
                 if (emitChanged) {
-                    this._changedCallback();
+                    this.onChangeCallback();
                 }
 
                 return true;
@@ -721,7 +754,7 @@
             this._pixels[index] = value;
 
             if (emitChanged) {
-                this._changedCallback();
+                this.onChangeCallback();
             }
         }
 
@@ -738,7 +771,7 @@
             this._percentages[index] = value;
 
             if (emitChanged) {
-                this._changedCallback();
+                this.onChangeCallback();
             }
         }
 
@@ -1000,6 +1033,10 @@
             this._setType(3, mode);
         }
 
+        public get isDefault(): boolean {
+            return this._flags === 0x1111;
+        }
+
         private _parentAccess: () => PrimitiveThickness;
         private _changedCallback: () => void;
         private _pixels: number[];
@@ -1027,6 +1064,12 @@
             this._pixels[index] = pixels;
 
             if (emitChanged) {
+                this.onChangeCallback();
+            }
+        }
+
+        private onChangeCallback() {
+            if (this._changedCallback) {
                 this._changedCallback();
             }
         }
@@ -1297,12 +1340,13 @@
         }
     }
 
-    @className("Prim2DBase")
+    @className("Prim2DBase", "BABYLON")
     /**
      * Base class for a Primitive of the Canvas2D feature
      */
     export class Prim2DBase extends SmartPropertyPrim {
-        static PRIM2DBASE_PROPCOUNT: number = 16;
+        static PRIM2DBASE_PROPCOUNT: number = 24;
+
         public  static _bigInt = Math.pow(2, 30);
 
         constructor(settings: {
@@ -1392,7 +1436,6 @@
             this._padding = null;
             this._marginAlignment = null;
             this._id = settings.id;
-            this.propertyChanged = new Observable<PropertyChangedInfo>();
             this._children = new Array<Prim2DBase>();
             this._localTransform = new Matrix();
             this._globalTransform = null;
@@ -1614,16 +1657,46 @@
         public static positionProperty: Prim2DPropInfo;
 
         /**
+         * Metadata of the left property
+         */
+        public static xProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the bottom property
+         */
+        public static yProperty: Prim2DPropInfo;
+
+        /**
          * Metadata of the actualPosition property
          */
         public static actualPositionProperty: Prim2DPropInfo;
 
         /**
+         * Metadata of the actualX (Left) property
+         */
+        public static actualXProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the actualY (Bottom) property
+         */
+        public static actualYProperty: Prim2DPropInfo;
+
+        /**
          * Metadata of the size property
          */
         public static sizeProperty: Prim2DPropInfo;
 
         /**
+         * Metadata of the width property
+         */
+        public static widthProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the height property
+         */
+        public static heightProperty: Prim2DPropInfo;
+
+        /**
          * Metadata of the rotation property
          */
         public static rotationProperty: Prim2DPropInfo;
@@ -1634,6 +1707,21 @@
         public static scaleProperty: Prim2DPropInfo;
 
         /**
+         * Metadata of the actualSize property
+         */
+        public static actualSizeProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the actualWidth property
+         */
+        public static actualWidthProperty: Prim2DPropInfo;
+
+        /**
+         * Metadata of the actualHeight property
+         */
+        public static actualHeightProperty: Prim2DPropInfo;
+
+        /**
          * Metadata of the origin property
          */
         public static originProperty: Prim2DPropInfo;
@@ -1711,24 +1799,36 @@
         /**
          * Shortcut to actualPosition.x
          */
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 1, pi => Prim2DBase.actualXProperty = pi, false, false, true)
         public get actualX(): number {
             return this.actualPosition.x;
         }
 
+        public set actualX(val: number) {
+            this._actualPosition.x = val;
+            this._triggerPropertyChanged(Prim2DBase.actualPositionProperty, this._actualPosition);
+        }
+
         /**
          * Shortcut to actualPosition.y
          */
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 2, pi => Prim2DBase.actualYProperty = pi, false, false, true)
         public get actualY(): number {
             return this.actualPosition.y;
         }
 
+        public set actualY(val: number) {
+            this._actualPosition.y = val;
+            this._triggerPropertyChanged(Prim2DBase.actualPositionProperty, this._actualPosition);
+        }
+
         /**
          * Position of the primitive, relative to its parent.
          * BEWARE: if you change only position.x or y it won't trigger a property change and you won't have the expected behavior.
          * Use this property to set a new Vector2 object, otherwise to change only the x/y use Prim2DBase.x or y properties.
          * Setting this property may have no effect is specific alignment are in effect.
          */
-        @dynamicLevelProperty(2, pi => Prim2DBase.positionProperty = pi, false, false, true)
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 3, pi => Prim2DBase.positionProperty = pi, false, false, true)
         public get position(): Vector2 {
             return this._position || Prim2DBase._nullPosition;
         }
@@ -1745,6 +1845,7 @@
          * Direct access to the position.x value of the primitive
          * Use this property when you only want to change one component of the position property
          */
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 4, pi => Prim2DBase.xProperty = pi, false, false, true)
         public get x(): number {
             if (!this._position) {
                 return null;
@@ -1773,6 +1874,7 @@
          * Direct access to the position.y value of the primitive
          * Use this property when you only want to change one component of the position property
          */
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 5, pi => Prim2DBase.yProperty = pi, false, false, true)
         public get y(): number {
             if (!this._position) {
                 return null;
@@ -1805,7 +1907,7 @@
          * BEWARE: if you change only size.width or height it won't trigger a property change and you won't have the expected behavior.
          * Use this property to set a new Size object, otherwise to change only the width/height use Prim2DBase.width or height properties.
          */
-        @dynamicLevelProperty(3, pi => Prim2DBase.sizeProperty = pi, false, true)
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 6, pi => Prim2DBase.sizeProperty = pi, false, true)
         public get size(): Size {
 
             if (!this._size || this._size.width == null || this._size.height == null) {
@@ -1837,6 +1939,7 @@
          * Direct access to the size.width value of the primitive
          * Use this property when you only want to change one component of the size property
          */
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 7, pi => Prim2DBase.widthProperty = pi, false, true)
         public get width(): number {
             if (!this.size) {
                 return null;
@@ -1845,16 +1948,16 @@
         }
 
         public set width(value: number) {
-            if (!this.size) {
-                this.size = new Size(value, 0);
+            if (this.size && this.size.width === value) {
                 return;
             }
 
-            if (this.size.width === value) {
-                return;
+            if (!this.size) {
+                this.size = new Size(value, 0);
+            } else {
+                this.size.width = value;
             }
 
-            this.size.width = value;
             this._triggerPropertyChanged(Prim2DBase.sizeProperty, value);
             this._positioningDirty();
         }
@@ -1863,6 +1966,7 @@
          * Direct access to the size.height value of the primitive
          * Use this property when you only want to change one component of the size property
          */
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 8, pi => Prim2DBase.heightProperty = pi, false, true)
         public get height(): number {
             if (!this.size) {
                 return null;
@@ -1871,21 +1975,21 @@
         }
 
         public set height(value: number) {
-            if (!this.size) {
-                this.size = new Size(0, value);
+            if (this.size && this.size.height === value) {
                 return;
             }
 
-            if (this.size.height === value) {
-                return;
+            if (!this.size) {
+                this.size = new Size(0, value);
+            } else {
+                this.size.height = value;
             }
 
-            this.size.height = value;
             this._triggerPropertyChanged(Prim2DBase.sizeProperty, value);
             this._positioningDirty();
         }
 
-        @instanceLevelProperty(4, pi => Prim2DBase.rotationProperty = pi, false, true)
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 9, pi => Prim2DBase.rotationProperty = pi, false, true)
         /**
          * Rotation of the primitive, in radian, along the Z axis
          */
@@ -1897,7 +2001,7 @@
             this._rotation = value;
         }
 
-        @instanceLevelProperty(5, pi => Prim2DBase.scaleProperty = pi, false, true)
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 10, pi => Prim2DBase.scaleProperty = pi, false, true)
         /**
          * Uniform scale applied on the primitive. If a non-uniform scale is applied through scaleX/scaleY property the getter of this property will return scaleX.
          */
@@ -1917,6 +2021,7 @@
          * BEWARE: don't use the setter, it's for internal purpose only
          * Note to implementers: you have to override this property and declare if necessary a @xxxxInstanceLevel decorator
          */
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 11, pi => Prim2DBase.actualSizeProperty = pi, false, true)
         public get actualSize(): Size {
             if (this._actualSize) {
                 return this._actualSize;
@@ -1932,6 +2037,32 @@
             this._actualSize = value;
         }
 
+        /**
+         * Shortcut to actualSize.width
+         */
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 12, pi => Prim2DBase.actualWidthProperty = pi, false, true)
+        public get actualWidth(): number {
+            return this.actualSize.width;
+        }
+
+        public set actualWidth(val: number) {
+            this._actualSize.width = val;
+            this._triggerPropertyChanged(Prim2DBase.actualSizeProperty, this._actualSize);
+        }
+
+        /**
+         * Shortcut to actualPosition.height
+         */
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 13, pi => Prim2DBase.actualHeightProperty = pi, false, true)
+        public get actualHeight(): number {
+            return this.actualSize.width;
+        }
+
+        public set actualHeight(val: number) {
+            this._actualSize.height = val;
+            this._triggerPropertyChanged(Prim2DBase.actualPositionProperty, this._actualSize);
+        }
+
         public get actualZOffset(): number {
             if (this._manualZOrder!=null) {
                 return this._manualZOrder;
@@ -1987,7 +2118,7 @@
          * 0,1 means the center is top/left
          * @returns The normalized center.
          */
-        @dynamicLevelProperty(6, pi => Prim2DBase.originProperty = pi, false, true)
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 14, pi => Prim2DBase.originProperty = pi, false, true)
         public get origin(): Vector2 {
             return this._origin;
         }
@@ -1996,7 +2127,7 @@
             this._origin = value;
         }
 
-        @dynamicLevelProperty(7, pi => Prim2DBase.levelVisibleProperty = pi)
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 15, pi => Prim2DBase.levelVisibleProperty = pi)
         /**
          * Let the user defines if the Primitive is hidden or not at its level. As Primitives inherit the hidden status from their parent, only the isVisible property give properly the real visible state.
          * Default is true, setting to false will hide this primitive and its children.
@@ -2009,7 +2140,7 @@
             this._changeFlags(SmartPropertyPrim.flagLevelVisible, value);
         }
 
-        @instanceLevelProperty(8, pi => Prim2DBase.isVisibleProperty = pi)
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 16, pi => Prim2DBase.isVisibleProperty = pi)
         /**
          * Use ONLY THE GETTER to determine if the primitive is visible or not.
          * The Setter is for internal purpose only!
@@ -2022,7 +2153,7 @@
             this._changeFlags(SmartPropertyPrim.flagIsVisible, value);
         }
 
-        @instanceLevelProperty(9, pi => Prim2DBase.zOrderProperty = pi)
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 17, pi => Prim2DBase.zOrderProperty = pi)
         /**
          * You can override the default Z Order through this property, but most of the time the default behavior is acceptable
          */
@@ -2046,7 +2177,7 @@
             return this._manualZOrder != null;
         }
 
-        @dynamicLevelProperty(10, pi => Prim2DBase.marginProperty = pi)
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 18, pi => Prim2DBase.marginProperty = pi)
         /**
          * You can get/set a margin on the primitive through this property
          * @returns the margin object, if there was none, a default one is created and returned
@@ -2063,11 +2194,18 @@
             return this._margin;
         }
 
+        public set margin(value: PrimitiveThickness) {
+            this.margin.copyFrom(value);
+        }
+
+        /**
+         * Check for both margin and marginAlignment, return true if at least one of them is specified with a non default value
+         */
         public get _hasMargin(): boolean {
-            return (this._margin !== null) || (this._marginAlignment !== null);
+            return (this._margin !== null && !this._margin.isDefault) || (this._marginAlignment !== null && !this._marginAlignment.isDefault);
         }
 
-        @dynamicLevelProperty(11, pi => Prim2DBase.paddingProperty = pi)
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 19, pi => Prim2DBase.paddingProperty = pi)
         /**
          * You can get/set a margin on the primitive through this property
          * @returns the margin object, if there was none, a default one is created and returned
@@ -2084,11 +2222,15 @@
             return this._padding;
         }
 
+        public set padding(value: PrimitiveThickness) {
+            this.padding.copyFrom(value);
+        }
+
         private get _hasPadding(): boolean {
-            return this._padding !== null;
+            return this._padding !== null && !this._padding.isDefault;
         }
 
-        @dynamicLevelProperty(12, pi => Prim2DBase.marginAlignmentProperty = pi)
+        @dynamicLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 20, pi => Prim2DBase.marginAlignmentProperty = pi)
         /**
          * You can get/set the margin alignment through this property
          */
@@ -2099,7 +2241,18 @@
             return this._marginAlignment;
         }
 
-        @instanceLevelProperty(13, pi => Prim2DBase.opacityProperty = pi)
+        public set marginAlignment(value: PrimitiveAlignment) {
+            this.marginAlignment.copyFrom(value);
+        }
+
+        /**
+         * Check if there a marginAlignment specified (non null and not default)
+         */
+        public get _hasMarginAlignment(): boolean {
+            return (this._marginAlignment !== null && !this._marginAlignment.isDefault);
+        }
+
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 21, pi => Prim2DBase.opacityProperty = pi)
         /**
          * Get/set the opacity of the whole primitive
          */
@@ -2124,7 +2277,7 @@
             this._updateRenderMode();
         }
 
-        @instanceLevelProperty(14, pi => Prim2DBase.scaleXProperty = pi, false, true)
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 22, pi => Prim2DBase.scaleXProperty = pi, false, true)
         /**
          * Scale applied on the X axis of the primitive
          */
@@ -2138,7 +2291,7 @@
             return this._scale.x;
         }
 
-        @instanceLevelProperty(15, pi => Prim2DBase.scaleYProperty = pi, false, true)
+        @instanceLevelProperty(SmartPropertyPrim.SMARTPROPERTYPRIM_PROPCOUNT + 23, pi => Prim2DBase.scaleYProperty = pi, false, true)
         /**
          * Scale applied on the Y axis of the primitive
          */
@@ -2992,13 +3145,15 @@
             //  1. Determine the PaddingArea and the ActualPosition based on the margin/marginAlignment properties, which will also set the size property of the primitive
             //  2. Determine the contentArea based on the padding property.
 
+            let isSizeAuto = this.isSizeAuto;
+
             // Auto Create PaddingArea if there's no actualSize on width&|height to allocate the whole content available to the paddingArea where the actualSize is null
-            if (!this._hasMargin && (this.actualSize.width == null || this.actualSize.height == null)) {
-                if (this.actualSize.width == null) {
+            if (!this._hasMarginAlignment && (isSizeAuto || (this.actualSize.width == null || this.actualSize.height == null))) {
+                if (isSizeAuto || this.actualSize.width == null) {
                     this.marginAlignment.horizontal = PrimitiveAlignment.AlignStretch;
                 }
 
-                if (this.actualSize.height == null) {
+                if (isSizeAuto || this.actualSize.height == null) {
                     this.marginAlignment.vertical = PrimitiveAlignment.AlignStretch;
                 }
             }
@@ -3009,7 +3164,6 @@
                 this.actualSize = Prim2DBase._size.clone();
             }
 
-            let isSizeAuto = this.isSizeAuto;
             if (this._hasPadding) {
                 // Two cases from here: the size of the Primitive is Auto, its content can't be shrink, so me resize the primitive itself
                 if (isSizeAuto) {

+ 1 - 1
src/Canvas2d/babylon.rectangle2d.js

@@ -445,7 +445,7 @@ var BABYLON;
             BABYLON.instanceLevelProperty(BABYLON.Shape2D.SHAPE2D_PROPCOUNT + 3, function (pi) { return Rectangle2D.roundRadiusProperty = pi; })
         ], Rectangle2D.prototype, "roundRadius", null);
         Rectangle2D = __decorate([
-            BABYLON.className("Rectangle2D")
+            BABYLON.className("Rectangle2D", "BABYLON")
         ], Rectangle2D);
         return Rectangle2D;
     }(BABYLON.Shape2D));

+ 1 - 1
src/Canvas2d/babylon.rectangle2d.ts

@@ -160,7 +160,7 @@
         }
     }
 
-    @className("Rectangle2D")
+    @className("Rectangle2D", "BABYLON")
     /**
      * The Rectangle Primitive type
      */

+ 1 - 1
src/Canvas2d/babylon.renderablePrim2d.js

@@ -885,7 +885,7 @@ var BABYLON;
             BABYLON.dynamicLevelProperty(BABYLON.Prim2DBase.PRIM2DBASE_PROPCOUNT + 1, function (pi) { return RenderablePrim2D.isTransparentProperty = pi; })
         ], RenderablePrim2D.prototype, "isTransparent", null);
         RenderablePrim2D = __decorate([
-            BABYLON.className("RenderablePrim2D")
+            BABYLON.className("RenderablePrim2D", "BABYLON")
         ], RenderablePrim2D);
         return RenderablePrim2D;
     }(BABYLON.Prim2DBase));

+ 1 - 1
src/Canvas2d/babylon.renderablePrim2d.ts

@@ -341,7 +341,7 @@
         private _dataElementCount: number;
     }
 
-    @className("RenderablePrim2D")
+    @className("RenderablePrim2D", "BABYLON")
     /**
      * The abstract class for primitive that render into the Canvas2D
      */

+ 1 - 1
src/Canvas2d/babylon.shape2d.js

@@ -182,7 +182,7 @@ var BABYLON;
             BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 3, function (pi) { return Shape2D.borderThicknessProperty = pi; })
         ], Shape2D.prototype, "borderThickness", null);
         Shape2D = __decorate([
-            BABYLON.className("Shape2D")
+            BABYLON.className("Shape2D", "BABYLON")
         ], Shape2D);
         return Shape2D;
     }(BABYLON.RenderablePrim2D));

+ 1 - 1
src/Canvas2d/babylon.shape2d.ts

@@ -1,6 +1,6 @@
 module BABYLON {
 
-    @className("Shape2D")
+    @className("Shape2D", "BABYLON")
     /**
      * The abstract class for parametric shape based Primitives types.
      * Shape2D based primitives are composed of two parts: fill and border, both are optional but at least one must be specified.

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1146 - 0
canvas2D/src/Engine/babylon.smartPropertyPrim.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1292 - 0
canvas2D/src/Engine/babylon.smartPropertyPrim.ts


+ 1 - 1
src/Canvas2d/babylon.sprite2d.js

@@ -478,7 +478,7 @@ var BABYLON;
             BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 7, function (pi) { return Sprite2D.spriteScaleFactorProperty = pi; })
         ], Sprite2D.prototype, "spriteScaleFactor", null);
         Sprite2D = __decorate([
-            BABYLON.className("Sprite2D")
+            BABYLON.className("Sprite2D", "BABYLON")
         ], Sprite2D);
         return Sprite2D;
     }(BABYLON.RenderablePrim2D));

+ 1 - 1
src/Canvas2d/babylon.sprite2d.ts

@@ -130,7 +130,7 @@
         }
     }
 
-    @className("Sprite2D")
+    @className("Sprite2D", "BABYLON")
     /**
      * Primitive that displays a Sprite/Picture
      */

+ 12 - 2
src/Canvas2d/babylon.text2d.js

@@ -234,6 +234,9 @@ var BABYLON;
                 return this._text;
             },
             set: function (value) {
+                if (!value) {
+                    value = "";
+                }
                 this._text = value;
                 this._textSize = null; // A change of text will reset the TextSize which will be recomputed next time it's used
                 this._size = null;
@@ -257,6 +260,13 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(Text2D.prototype, "isSizeAuto", {
+            get: function () {
+                return false;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(Text2D.prototype, "actualSize", {
             /**
              * Get the actual size of the Text2D primitive
@@ -276,7 +286,7 @@ var BABYLON;
              */
             get: function () {
                 if (!this._textSize) {
-                    if (this.owner) {
+                    if (this.owner && this._text) {
                         var newSize = this.fontTexture.measureText(this._text, this._tabulationSize);
                         if (!newSize.equals(this._textSize)) {
                             this.onPrimitivePropertyDirty(BABYLON.Prim2DBase.sizeProperty.flagId);
@@ -455,7 +465,7 @@ var BABYLON;
             BABYLON.instanceLevelProperty(BABYLON.RenderablePrim2D.RENDERABLEPRIM2D_PROPCOUNT + 4, function (pi) { return Text2D.sizeProperty = pi; })
         ], Text2D.prototype, "size", null);
         Text2D = __decorate([
-            BABYLON.className("Text2D")
+            BABYLON.className("Text2D", "BABYLON")
         ], Text2D);
         return Text2D;
     }(BABYLON.RenderablePrim2D));

+ 9 - 2
src/Canvas2d/babylon.text2d.ts

@@ -125,7 +125,7 @@
         }
     }
 
-    @className("Text2D")
+    @className("Text2D", "BABYLON")
     /**
      * Primitive that render text using a specific font
      */
@@ -175,6 +175,9 @@
         }
 
         public set text(value: string) {
+            if (!value) {
+                value = "";
+            }
             this._text = value;
             this._textSize = null;    // A change of text will reset the TextSize which will be recomputed next time it's used
             this._size = null;
@@ -200,6 +203,10 @@
             this._size = value;
         }
 
+        public get isSizeAuto(): boolean {
+            return false;
+        }
+
         /**
          * Get the actual size of the Text2D primitive
          */
@@ -215,7 +222,7 @@
          */
         public get textSize(): Size {
             if (!this._textSize) {
-                if (this.owner) {
+                if (this.owner && this._text) {
                     let newSize = this.fontTexture.measureText(this._text, this._tabulationSize);
                     if (!newSize.equals(this._textSize)) {
                         this.onPrimitivePropertyDirty(Prim2DBase.sizeProperty.flagId);

src/Canvas2d/babylon.worldSpaceCanvas2dNode.js → canvas2D/src/Engine/babylon.worldSpaceCanvas2dNode.js


src/Canvas2d/babylon.worldSpaceCanvas2dNode.ts → canvas2D/src/Engine/babylon.worldSpaceCanvas2dNode.ts


+ 779 - 0
canvas2D/src/GUI/babylon.gui.UIElement.js

@@ -0,0 +1,779 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Command = (function () {
+        function Command(execute, canExecute) {
+            if (!execute) {
+                throw Error("At least an execute lambda must be given at Command creation time");
+            }
+            this._canExecuteChanged = null;
+            this._lastCanExecuteResult = null;
+            this.execute = execute;
+            this.canExecute = canExecute;
+        }
+        Command.prototype.canExecute = function (parameter) {
+            var res = true;
+            if (this._canExecute) {
+                res = this._canExecute(parameter);
+            }
+            if (res !== this._lastCanExecuteResult) {
+                if (this._canExecuteChanged && this._canExecuteChanged.hasObservers()) {
+                    this._canExecuteChanged.notifyObservers(null);
+                }
+                this._lastCanExecuteResult = res;
+            }
+            return res;
+        };
+        Command.prototype.execute = function (parameter) {
+            this._execute(parameter);
+        };
+        Object.defineProperty(Command.prototype, "canExecuteChanged", {
+            get: function () {
+                if (!this._canExecuteChanged) {
+                    this._canExecuteChanged = new BABYLON.Observable();
+                }
+                return this._canExecuteChanged;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        return Command;
+    }());
+    BABYLON.Command = Command;
+    var UIElement = (function (_super) {
+        __extends(UIElement, _super);
+        function UIElement(settings) {
+            _super.call(this);
+            if (!settings) {
+                throw Error("A settings object must be passed with at least either a parent or owner parameter");
+            }
+            var type = BABYLON.Tools.getFullClassName(this);
+            this._ownerWindow = null;
+            this._parent = null;
+            this._visualPlaceholder = null;
+            this._visualTemplateRoot = null;
+            this._visualChildrenPlaceholder = null;
+            this._hierarchyDepth = 0;
+            this._style = (settings.styleName != null) ? UIElementStyleManager.getStyle(type, settings.styleName) : null;
+            this._flags = 0;
+            this._id = (settings.id != null) ? settings.id : null;
+            this._uid = null;
+            this._width = (settings.width != null) ? settings.width : null;
+            this._height = (settings.height != null) ? settings.height : null;
+            this._minWidth = (settings.minWidth != null) ? settings.minWidth : 0;
+            this._minHeight = (settings.minHeight != null) ? settings.minHeight : 0;
+            this._maxWidth = (settings.maxWidth != null) ? settings.maxWidth : Number.MAX_VALUE;
+            this._maxHeight = (settings.maxHeight != null) ? settings.maxHeight : Number.MAX_VALUE;
+            this._margin = null;
+            this._padding = null;
+            this._marginAlignment = null;
+            this._isEnabled = true;
+            this._isFocused = false;
+            this._isMouseOver = false;
+            // Default Margin Alignment for UIElement is stretch for horizontal/vertical and not left/bottom (which is the default for Canvas2D Primitives)
+            //this.marginAlignment.horizontal = PrimitiveAlignment.AlignStretch;
+            //this.marginAlignment.vertical   = PrimitiveAlignment.AlignStretch;
+            // Set the layout/margin stuffs
+            if (settings.marginTop) {
+                this.margin.setTop(settings.marginTop);
+            }
+            if (settings.marginLeft) {
+                this.margin.setLeft(settings.marginLeft);
+            }
+            if (settings.marginRight) {
+                this.margin.setRight(settings.marginRight);
+            }
+            if (settings.marginBottom) {
+                this.margin.setBottom(settings.marginBottom);
+            }
+            if (settings.margin) {
+                if (typeof settings.margin === "string") {
+                    this.margin.fromString(settings.margin);
+                }
+                else {
+                    this.margin.fromUniformPixels(settings.margin);
+                }
+            }
+            if (settings.marginHAlignment) {
+                this.marginAlignment.horizontal = settings.marginHAlignment;
+            }
+            if (settings.marginVAlignment) {
+                this.marginAlignment.vertical = settings.marginVAlignment;
+            }
+            if (settings.marginAlignment) {
+                this.marginAlignment.fromString(settings.marginAlignment);
+            }
+            if (settings.paddingTop) {
+                this.padding.setTop(settings.paddingTop);
+            }
+            if (settings.paddingLeft) {
+                this.padding.setLeft(settings.paddingLeft);
+            }
+            if (settings.paddingRight) {
+                this.padding.setRight(settings.paddingRight);
+            }
+            if (settings.paddingBottom) {
+                this.padding.setBottom(settings.paddingBottom);
+            }
+            if (settings.padding) {
+                this.padding.fromString(settings.padding);
+            }
+            this._assignTemplate(settings.templateName);
+            if (settings.parent != null) {
+                this._parent = settings.parent;
+                this._hierarchyDepth = this._parent._hierarchyDepth + 1;
+            }
+        }
+        UIElement.prototype.dispose = function () {
+            if (this.isDisposed) {
+                return false;
+            }
+            if (this._renderingTemplate) {
+                this._renderingTemplate.detach();
+                this._renderingTemplate = null;
+            }
+            _super.prototype.dispose.call(this);
+            // Don't set to null, it may upset somebody...
+            this.animations.splice(0);
+            return true;
+        };
+        /**
+         * Returns as a new array populated with the Animatable used by the primitive. Must be overloaded by derived primitives.
+         * Look at Sprite2D for more information
+         */
+        UIElement.prototype.getAnimatables = function () {
+            return new Array();
+        };
+        Object.defineProperty(UIElement.prototype, "ownerWindows", {
+            // TODO
+            // PROPERTIES
+            // Style
+            // Id
+            // Parent/Children
+            // ActualWidth/Height, MinWidth/Height, MaxWidth/Height,
+            // Alignment/Margin
+            // Visibility, IsVisible
+            // IsEnabled (is false, control is disabled, no interaction and a specific render state)
+            // CacheMode of Visual Elements
+            // Focusable/IsFocused
+            // IsPointerCaptured, CapturePointer, IsPointerDirectlyOver, IsPointerOver. De-correlate mouse, stylus, touch?
+            // ContextMenu
+            // Cursor
+            // DesiredSize
+            // IsInputEnable ?
+            // Opacity, OpacityMask ?
+            // SnapToDevicePixels
+            // Tag
+            // ToolTip
+            // METHODS
+            // BringIntoView (for scrollable content, to move the scroll to bring the given element visible in the parent's area)
+            // Capture/ReleaseCapture (mouse, touch, stylus)
+            // Focus
+            // PointFrom/ToScreen to translate coordinates
+            // EVENTS
+            // ContextMenuOpening/Closing/Changed
+            // DragEnter/LeaveOver, Drop
+            // Got/LostFocus
+            // IsEnabledChanged
+            // IsPointerOver/DirectlyOverChanged
+            // IsVisibleChanged
+            // KeyDown/Up
+            // LayoutUpdated ?
+            // Pointer related events
+            // SizeChanged
+            // ToolTipOpening/Closing
+            get: function () {
+                return this._ownerWindow;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "style", {
+            get: function () {
+                if (!this.style) {
+                    return UIElementStyleManager.DefaultStyleName;
+                }
+                return this._style.name;
+            },
+            set: function (value) {
+                if (this._style && (this._style.name === value)) {
+                    return;
+                }
+                var newStyle = null;
+                if (value) {
+                    newStyle = UIElementStyleManager.getStyle(BABYLON.Tools.getFullClassName(this), value);
+                    if (!newStyle) {
+                        throw Error("Couldn't find Style " + value + " for UIElement " + BABYLON.Tools.getFullClassName(this));
+                    }
+                }
+                if (this._style) {
+                    this._style.removeStyle(this);
+                }
+                if (newStyle) {
+                    newStyle.applyStyle(this);
+                }
+                this._style = newStyle;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "id", {
+            /**
+             * A string that identifies the UIElement.
+             * The id is optional and there's possible collision with other UIElement's id as the uniqueness is not supported.
+             */
+            get: function () {
+                return this._id;
+            },
+            set: function (value) {
+                if (this._id === value) {
+                    return;
+                }
+                this._id = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "uid", {
+            /**
+             * Return a unique id automatically generated.
+             * This property is mainly used for serialization to ensure a perfect way of identifying a UIElement
+             */
+            get: function () {
+                if (!this._uid) {
+                    this._uid = BABYLON.Tools.RandomId();
+                }
+                return this._uid;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "hierarchyDepth", {
+            get: function () {
+                return this._hierarchyDepth;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "parent", {
+            get: function () {
+                return this._parent;
+            },
+            set: function (value) {
+                this._parent = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "width", {
+            get: function () {
+                return this._width;
+            },
+            set: function (value) {
+                this._width = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "height", {
+            get: function () {
+                return this._height;
+            },
+            set: function (value) {
+                this._height = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "minWidth", {
+            get: function () {
+                return this._minWidth;
+            },
+            set: function (value) {
+                this._minWidth = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "minHheight", {
+            get: function () {
+                return this._minHeight;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "minHeight", {
+            set: function (value) {
+                this._minHeight = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "maxWidth", {
+            get: function () {
+                return this._maxWidth;
+            },
+            set: function (value) {
+                this._maxWidth = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "maxHeight", {
+            get: function () {
+                return this._maxHeight;
+            },
+            set: function (value) {
+                this._maxHeight = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "actualWidth", {
+            get: function () {
+                return this._actualWidth;
+            },
+            set: function (value) {
+                this._actualWidth = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "actualHeight", {
+            get: function () {
+                return this._actualHeight;
+            },
+            set: function (value) {
+                this._actualHeight = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "margin", {
+            get: function () {
+                var _this = this;
+                if (!this._margin) {
+                    this._margin = new BABYLON.PrimitiveThickness(function () {
+                        if (!_this.parent) {
+                            return null;
+                        }
+                        return _this.parent.margin;
+                    });
+                }
+                return this._margin;
+            },
+            set: function (value) {
+                this.margin.copyFrom(value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "_hasMargin", {
+            get: function () {
+                return (this._margin !== null && !this._margin.isDefault) || (this._marginAlignment !== null && !this._marginAlignment.isDefault);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "padding", {
+            get: function () {
+                var _this = this;
+                if (!this._padding) {
+                    this._padding = new BABYLON.PrimitiveThickness(function () {
+                        if (!_this.parent) {
+                            return null;
+                        }
+                        return _this.parent.padding;
+                    });
+                }
+                return this._padding;
+            },
+            set: function (value) {
+                this.padding.copyFrom(value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "_hasPadding", {
+            get: function () {
+                return this._padding !== null && !this._padding.isDefault;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "marginAlignment", {
+            get: function () {
+                if (!this._marginAlignment) {
+                    this._marginAlignment = new BABYLON.PrimitiveAlignment();
+                }
+                return this._marginAlignment;
+            },
+            set: function (value) {
+                this.marginAlignment.copyFrom(value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "_hasMarginAlignment", {
+            /**
+             * Check if there a marginAlignment specified (non null and not default)
+             */
+            get: function () {
+                return (this._marginAlignment !== null && !this._marginAlignment.isDefault);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "isEnabled", {
+            get: function () {
+                return this._isEnabled;
+            },
+            set: function (value) {
+                this._isEnabled = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "isFocused", {
+            get: function () {
+                return this._isFocused;
+            },
+            set: function (value) {
+                this._isFocused = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "isMouseOver", {
+            get: function () {
+                return this._isMouseOver;
+            },
+            set: function (value) {
+                this._isMouseOver = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Check if a given flag is set
+         * @param flag the flag value
+         * @return true if set, false otherwise
+         */
+        UIElement.prototype._isFlagSet = function (flag) {
+            return (this._flags & flag) !== 0;
+        };
+        /**
+         * Check if all given flags are set
+         * @param flags the flags ORed
+         * @return true if all the flags are set, false otherwise
+         */
+        UIElement.prototype._areAllFlagsSet = function (flags) {
+            return (this._flags & flags) === flags;
+        };
+        /**
+         * Check if at least one flag of the given flags is set
+         * @param flags the flags ORed
+         * @return true if at least one flag is set, false otherwise
+         */
+        UIElement.prototype._areSomeFlagsSet = function (flags) {
+            return (this._flags & flags) !== 0;
+        };
+        /**
+         * Clear the given flags
+         * @param flags the flags to clear
+         */
+        UIElement.prototype._clearFlags = function (flags) {
+            this._flags &= ~flags;
+        };
+        /**
+         * Set the given flags to true state
+         * @param flags the flags ORed to set
+         * @return the flags state before this call
+         */
+        UIElement.prototype._setFlags = function (flags) {
+            var cur = this._flags;
+            this._flags |= flags;
+            return cur;
+        };
+        /**
+         * Change the state of the given flags
+         * @param flags the flags ORed to change
+         * @param state true to set them, false to clear them
+         */
+        UIElement.prototype._changeFlags = function (flags, state) {
+            if (state) {
+                this._flags |= flags;
+            }
+            else {
+                this._flags &= ~flags;
+            }
+        };
+        UIElement.prototype._assignTemplate = function (templateName) {
+            if (!templateName) {
+                templateName = UIElementRenderingTemplateManager.DefaultTemplateName;
+            }
+            var className = BABYLON.Tools.getFullClassName(this);
+            if (!className) {
+                throw Error("Couldn't access class name of this UIElement, you have to decorate the type with the className decorator");
+            }
+            var factory = UIElementRenderingTemplateManager.getRenderingTemplate(className, templateName);
+            if (!factory) {
+                throw Error("Couldn't get the renderingTemplate " + templateName + " of class " + className);
+            }
+            this._renderingTemplate = factory();
+            this._renderingTemplate.attach(this);
+        };
+        UIElement.prototype._createVisualTree = function () {
+            var parentPrim = this.ownerWindows.canvas;
+            if (this.parent) {
+                parentPrim = this.parent.visualChildrenPlaceholder;
+            }
+            this._visualPlaceholder = new BABYLON.Group2D({ parent: parentPrim, id: "GUI Visual Placeholder of " + this.id });
+            var p = this._visualPlaceholder;
+            p.addExternalData("_GUIOwnerElement_", this);
+            p.dataSource = this;
+            p.createSimpleDataBinding(BABYLON.Prim2DBase.widthProperty, "width", BABYLON.DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(BABYLON.Prim2DBase.heightProperty, "height", BABYLON.DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(BABYLON.Prim2DBase.actualWidthProperty, "actualWidth", BABYLON.DataBinding.MODE_ONEWAYTOSOURCE);
+            p.createSimpleDataBinding(BABYLON.Prim2DBase.actualHeightProperty, "actualHeight", BABYLON.DataBinding.MODE_ONEWAYTOSOURCE);
+            p.createSimpleDataBinding(BABYLON.Prim2DBase.marginProperty, "margin", BABYLON.DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(BABYLON.Prim2DBase.paddingProperty, "padding", BABYLON.DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(BABYLON.Prim2DBase.marginAlignmentProperty, "marginAlignment", BABYLON.DataBinding.MODE_ONEWAY);
+            this.createVisualTree();
+        };
+        UIElement.prototype._patchUIElement = function (ownerWindow, parent) {
+            if (ownerWindow) {
+                if (!this._ownerWindow) {
+                    ownerWindow._registerVisualToBuild(this);
+                }
+                this._ownerWindow = ownerWindow;
+            }
+            this._parent = parent;
+            if (parent) {
+                this._hierarchyDepth = parent.hierarchyDepth + 1;
+            }
+            var children = this._getChildren();
+            if (children) {
+                for (var _i = 0, children_1 = children; _i < children_1.length; _i++) {
+                    var curChild = children_1[_i];
+                    curChild._patchUIElement(ownerWindow, this);
+                }
+            }
+        };
+        // Overload the SmartPropertyBase's method to provide the additional logic of returning the parent's dataSource if there's no dataSource specified at this level.
+        UIElement.prototype._getDataSource = function () {
+            var levelDS = _super.prototype._getDataSource.call(this);
+            if (levelDS != null) {
+                return levelDS;
+            }
+            var p = this.parent;
+            if (p != null) {
+                return p.dataSource;
+            }
+            return null;
+        };
+        UIElement.prototype.createVisualTree = function () {
+            var res = this._renderingTemplate.createVisualTree(this, this._visualPlaceholder);
+            this._visualTemplateRoot = res.root;
+            this._visualChildrenPlaceholder = res.contentPlaceholder;
+        };
+        Object.defineProperty(UIElement.prototype, "visualPlaceholder", {
+            get: function () {
+                return this._visualPlaceholder;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "visualTemplateRoot", {
+            get: function () {
+                return this._visualTemplateRoot;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "visualChildrenPlaceholder", {
+            get: function () {
+                return this._visualChildrenPlaceholder;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(UIElement.prototype, "_position", {
+            get: function () { return null; } // TODO use abstract keyword when TS 2.0 will be approved
+            ,
+            enumerable: true,
+            configurable: true
+        });
+        UIElement.UIELEMENT_PROPCOUNT = 15;
+        UIElement.flagVisualToBuild = 0x0000001; // set if the UIElement visual must be updated
+        __decorate([
+            BABYLON.dependencyProperty(0, function (pi) { return UIElement.parentProperty = pi; })
+        ], UIElement.prototype, "parent", null);
+        __decorate([
+            BABYLON.dependencyProperty(1, function (pi) { return UIElement.widthProperty = pi; })
+        ], UIElement.prototype, "width", null);
+        __decorate([
+            BABYLON.dependencyProperty(2, function (pi) { return UIElement.heightProperty = pi; })
+        ], UIElement.prototype, "height", null);
+        __decorate([
+            BABYLON.dependencyProperty(3, function (pi) { return UIElement.minWidthProperty = pi; })
+        ], UIElement.prototype, "minWidth", null);
+        __decorate([
+            BABYLON.dependencyProperty(4, function (pi) { return UIElement.minHeightProperty = pi; })
+        ], UIElement.prototype, "minHheight", null);
+        __decorate([
+            BABYLON.dependencyProperty(5, function (pi) { return UIElement.maxWidthProperty = pi; })
+        ], UIElement.prototype, "maxWidth", null);
+        __decorate([
+            BABYLON.dependencyProperty(6, function (pi) { return UIElement.maxHeightProperty = pi; })
+        ], UIElement.prototype, "maxHeight", null);
+        __decorate([
+            BABYLON.dependencyProperty(7, function (pi) { return UIElement.actualWidthProperty = pi; })
+        ], UIElement.prototype, "actualWidth", null);
+        __decorate([
+            BABYLON.dependencyProperty(8, function (pi) { return UIElement.actualHeightProperty = pi; })
+        ], UIElement.prototype, "actualHeight", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(9, function (pi) { return UIElement.marginProperty = pi; })
+        ], UIElement.prototype, "margin", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(10, function (pi) { return UIElement.paddingProperty = pi; })
+        ], UIElement.prototype, "padding", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(11, function (pi) { return UIElement.marginAlignmentProperty = pi; })
+        ], UIElement.prototype, "marginAlignment", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(12, function (pi) { return UIElement.isEnabledProperty = pi; })
+        ], UIElement.prototype, "isEnabled", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(13, function (pi) { return UIElement.isFocusedProperty = pi; })
+        ], UIElement.prototype, "isFocused", null);
+        __decorate([
+            BABYLON.dynamicLevelProperty(14, function (pi) { return UIElement.isMouseOverProperty = pi; })
+        ], UIElement.prototype, "isMouseOver", null);
+        return UIElement;
+    }(BABYLON.SmartPropertyBase));
+    BABYLON.UIElement = UIElement;
+    var UIElementStyle = (function () {
+        function UIElementStyle() {
+        }
+        Object.defineProperty(UIElementStyle.prototype, "name", {
+            get: function () { return null; } // TODO use abstract keyword when TS 2.0 will be approved
+            ,
+            enumerable: true,
+            configurable: true
+        });
+        return UIElementStyle;
+    }());
+    BABYLON.UIElementStyle = UIElementStyle;
+    var UIElementStyleManager = (function () {
+        function UIElementStyleManager() {
+        }
+        UIElementStyleManager.getStyle = function (uiElType, styleName) {
+            var styles = UIElementStyleManager.stylesByUIElement.get(uiElType);
+            if (!styles) {
+                throw Error("The type " + uiElType + " is unknown, no style were registered for it.");
+            }
+            var style = styles.get(styleName);
+            if (!style) {
+                throw Error("Couldn't find Template " + styleName + " of UIElement type " + uiElType);
+            }
+            return style;
+        };
+        UIElementStyleManager.registerStyle = function (uiElType, templateName, style) {
+            var templates = UIElementStyleManager.stylesByUIElement.getOrAddWithFactory(uiElType, function () { return new BABYLON.StringDictionary(); });
+            if (templates.contains(templateName)) {
+                templates[templateName] = style;
+            }
+            else {
+                templates.add(templateName, style);
+            }
+        };
+        Object.defineProperty(UIElementStyleManager, "DefaultStyleName", {
+            get: function () {
+                return UIElementStyleManager._defaultStyleName;
+            },
+            set: function (value) {
+                UIElementStyleManager._defaultStyleName = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        UIElementStyleManager.stylesByUIElement = new BABYLON.StringDictionary();
+        UIElementStyleManager._defaultStyleName = "Default";
+        return UIElementStyleManager;
+    }());
+    BABYLON.UIElementStyleManager = UIElementStyleManager;
+    var UIElementRenderingTemplateManager = (function () {
+        function UIElementRenderingTemplateManager() {
+        }
+        UIElementRenderingTemplateManager.getRenderingTemplate = function (uiElType, templateName) {
+            var templates = UIElementRenderingTemplateManager.renderingTemplatesByUIElement.get(uiElType);
+            if (!templates) {
+                throw Error("The type " + uiElType + " is unknown, no Rendering Template were registered for it.");
+            }
+            var templateFactory = templates.get(templateName);
+            if (!templateFactory) {
+                throw Error("Couldn't find Template " + templateName + " of UI Element type " + uiElType);
+            }
+            return templateFactory;
+        };
+        UIElementRenderingTemplateManager.registerRenderingTemplate = function (uiElType, templateName, factory) {
+            var templates = UIElementRenderingTemplateManager.renderingTemplatesByUIElement.getOrAddWithFactory(uiElType, function () { return new BABYLON.StringDictionary(); });
+            if (templates.contains(templateName)) {
+                templates[templateName] = factory;
+            }
+            else {
+                templates.add(templateName, factory);
+            }
+        };
+        Object.defineProperty(UIElementRenderingTemplateManager, "DefaultTemplateName", {
+            get: function () {
+                return UIElementRenderingTemplateManager._defaultTemplateName;
+            },
+            set: function (value) {
+                UIElementRenderingTemplateManager._defaultTemplateName = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        UIElementRenderingTemplateManager.renderingTemplatesByUIElement = new BABYLON.StringDictionary();
+        UIElementRenderingTemplateManager._defaultTemplateName = "Default";
+        return UIElementRenderingTemplateManager;
+    }());
+    BABYLON.UIElementRenderingTemplateManager = UIElementRenderingTemplateManager;
+    var UIElementRenderingTemplateBase = (function () {
+        function UIElementRenderingTemplateBase() {
+        }
+        UIElementRenderingTemplateBase.prototype.attach = function (owner) {
+            this._owner = owner;
+        };
+        UIElementRenderingTemplateBase.prototype.detach = function () {
+        };
+        Object.defineProperty(UIElementRenderingTemplateBase.prototype, "owner", {
+            get: function () {
+                return this._owner;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        return UIElementRenderingTemplateBase;
+    }());
+    BABYLON.UIElementRenderingTemplateBase = UIElementRenderingTemplateBase;
+    function registerWindowRenderingTemplate(uiElType, templateName, factory) {
+        return function () {
+            UIElementRenderingTemplateManager.registerRenderingTemplate(uiElType, templateName, factory);
+        };
+    }
+    BABYLON.registerWindowRenderingTemplate = registerWindowRenderingTemplate;
+})(BABYLON || (BABYLON = {}));

+ 806 - 0
canvas2D/src/GUI/babylon.gui.UIElement.ts

@@ -0,0 +1,806 @@
+module BABYLON {
+
+    export interface ICommand {
+        canExecute(parameter: any): boolean;
+        execute(parameter: any): void;
+        canExecuteChanged: Observable<void>;
+    }
+
+    export class Command implements ICommand {
+        constructor(execute: (p) => void, canExecute: (p) => boolean) {
+            if (!execute) {
+                throw Error("At least an execute lambda must be given at Command creation time");
+            }
+
+            this._canExecuteChanged    = null;
+            this._lastCanExecuteResult = null;
+            this.execute               = execute;
+            this.canExecute            = canExecute;
+        }
+
+        canExecute(parameter): boolean {
+            let res = true;
+
+            if (this._canExecute) {
+                res = this._canExecute(parameter);
+            }
+
+            if (res !== this._lastCanExecuteResult) {
+                if (this._canExecuteChanged && this._canExecuteChanged.hasObservers()) {
+                    this._canExecuteChanged.notifyObservers(null);
+                }
+
+                this._lastCanExecuteResult = res;
+            }
+
+            return res;
+        }
+
+        execute(parameter): void {
+            this._execute(parameter);
+        }
+
+        get canExecuteChanged(): Observable<void> {
+            if (!this._canExecuteChanged) {
+                this._canExecuteChanged = new Observable<void>();
+            }
+            return this._canExecuteChanged;
+        }
+
+        private _lastCanExecuteResult: boolean;
+        private _execute: (p) => void;
+        private _canExecute: (p) => boolean;
+        private _canExecuteChanged: Observable<void>;
+    }
+
+    export abstract class UIElement extends SmartPropertyBase {
+
+        static UIELEMENT_PROPCOUNT: number = 15;
+
+        static parentProperty         : Prim2DPropInfo;
+        static widthProperty          : Prim2DPropInfo;
+        static heightProperty         : Prim2DPropInfo;
+        static minWidthProperty       : Prim2DPropInfo;
+        static minHeightProperty      : Prim2DPropInfo;
+        static maxWidthProperty       : Prim2DPropInfo;
+        static maxHeightProperty      : Prim2DPropInfo;
+        static actualWidthProperty    : Prim2DPropInfo;
+        static actualHeightProperty   : Prim2DPropInfo;
+        static marginProperty         : Prim2DPropInfo;
+        static paddingProperty        : Prim2DPropInfo;
+        static marginAlignmentProperty: Prim2DPropInfo;
+        static isEnabledProperty      : Prim2DPropInfo;
+        static isFocusedProperty      : Prim2DPropInfo;
+        static isMouseOverProperty    : Prim2DPropInfo;
+
+        constructor(settings: {
+            id              ?: string,
+            parent          ?: UIElement,
+            templateName    ?: string,
+            styleName       ?: string,
+            minWidth        ?: number,
+            minHeight       ?: number,
+            maxWidth        ?: number,
+            maxHeight       ?: number,
+            width           ?: number,
+            height          ?: number,
+            marginTop       ?: number | string,
+            marginLeft      ?: number | string,
+            marginRight     ?: number | string,
+            marginBottom    ?: number | string,
+            margin          ?: number | string,
+            marginHAlignment?: number,
+            marginVAlignment?: number,
+            marginAlignment ?: string,
+            paddingTop      ?: number | string,
+            paddingLeft     ?: number | string,
+            paddingRight    ?: number | string,
+            paddingBottom   ?: number | string,
+            padding         ?: string,
+        }) {
+            super();
+
+            if (!settings) {
+                throw Error("A settings object must be passed with at least either a parent or owner parameter");
+            }
+
+            let type                        = Tools.getFullClassName(this);
+            this._ownerWindow               = null;
+            this._parent                    = null;
+            this._visualPlaceholder         = null;
+            this._visualTemplateRoot        = null;
+            this._visualChildrenPlaceholder = null;
+            this._hierarchyDepth            = 0;
+            this._style                     = (settings.styleName!=null) ? UIElementStyleManager.getStyle(type, settings.styleName) : null;
+            this._flags                     = 0;
+            this._id                        = (settings.id!=null) ? settings.id : null;
+            this._uid                       = null;
+            this._width                     = (settings.width     != null) ? settings.width     : null;
+            this._height                    = (settings.height    != null) ? settings.height    : null;
+            this._minWidth                  = (settings.minWidth  != null) ? settings.minWidth  : 0;
+            this._minHeight                 = (settings.minHeight != null) ? settings.minHeight : 0;
+            this._maxWidth                  = (settings.maxWidth  != null) ? settings.maxWidth  : Number.MAX_VALUE;
+            this._maxHeight                 = (settings.maxHeight != null) ? settings.maxHeight : Number.MAX_VALUE;
+            this._margin                    = null;
+            this._padding                   = null;
+            this._marginAlignment           = null;
+            this._isEnabled                 = true;
+            this._isFocused                 = false;
+            this._isMouseOver               = false;
+
+            // Default Margin Alignment for UIElement is stretch for horizontal/vertical and not left/bottom (which is the default for Canvas2D Primitives)
+            //this.marginAlignment.horizontal = PrimitiveAlignment.AlignStretch;
+            //this.marginAlignment.vertical   = PrimitiveAlignment.AlignStretch;
+
+            // Set the layout/margin stuffs
+            if (settings.marginTop) {
+                this.margin.setTop(settings.marginTop);
+            }
+            if (settings.marginLeft) {
+                this.margin.setLeft(settings.marginLeft);
+            }
+            if (settings.marginRight) {
+                this.margin.setRight(settings.marginRight);
+            }
+            if (settings.marginBottom) {
+                this.margin.setBottom(settings.marginBottom);
+            }
+
+            if (settings.margin) {
+                if (typeof settings.margin === "string") {
+                    this.margin.fromString(<string>settings.margin);
+                } else {
+                    this.margin.fromUniformPixels(<number>settings.margin);
+                }
+            }
+
+            if (settings.marginHAlignment) {
+                this.marginAlignment.horizontal = settings.marginHAlignment;
+            }
+
+            if (settings.marginVAlignment) {
+                this.marginAlignment.vertical = settings.marginVAlignment;
+            }
+
+            if (settings.marginAlignment) {
+                this.marginAlignment.fromString(settings.marginAlignment);
+            }
+
+            if (settings.paddingTop) {
+                this.padding.setTop(settings.paddingTop);
+            }
+            if (settings.paddingLeft) {
+                this.padding.setLeft(settings.paddingLeft);
+            }
+            if (settings.paddingRight) {
+                this.padding.setRight(settings.paddingRight);
+            }
+            if (settings.paddingBottom) {
+                this.padding.setBottom(settings.paddingBottom);
+            }
+
+            if (settings.padding) {
+                this.padding.fromString(settings.padding);
+            }
+
+            this._assignTemplate(settings.templateName);
+
+            if (settings.parent != null) {
+                this._parent = settings.parent;
+                this._hierarchyDepth = this._parent._hierarchyDepth + 1;
+            }
+        }
+
+        public dispose(): boolean {
+            if (this.isDisposed) {
+                return false;
+            }
+
+            if (this._renderingTemplate) {
+                this._renderingTemplate.detach();
+                this._renderingTemplate = null;
+            }
+
+            super.dispose();
+
+            // Don't set to null, it may upset somebody...
+            this.animations.splice(0);
+
+            return true;
+        }
+
+        /**
+         * Animation array, more info: http://doc.babylonjs.com/tutorials/Animations
+         */
+        public animations: Animation[];
+
+        /**
+         * Returns as a new array populated with the Animatable used by the primitive. Must be overloaded by derived primitives.
+         * Look at Sprite2D for more information
+         */
+        public getAnimatables(): IAnimatable[] {
+            return new Array<IAnimatable>();
+        }
+
+        // TODO
+
+        // PROPERTIES
+
+        // Style
+        // Id
+        // Parent/Children
+        // ActualWidth/Height, MinWidth/Height, MaxWidth/Height,
+        // Alignment/Margin
+        // Visibility, IsVisible
+        // IsEnabled (is false, control is disabled, no interaction and a specific render state)
+        // CacheMode of Visual Elements
+        // Focusable/IsFocused
+        // IsPointerCaptured, CapturePointer, IsPointerDirectlyOver, IsPointerOver. De-correlate mouse, stylus, touch?
+        // ContextMenu
+        // Cursor
+        // DesiredSize
+        // IsInputEnable ?
+        // Opacity, OpacityMask ?
+        // SnapToDevicePixels
+        // Tag
+        // ToolTip
+
+        // METHODS
+
+        // BringIntoView (for scrollable content, to move the scroll to bring the given element visible in the parent's area)
+        // Capture/ReleaseCapture (mouse, touch, stylus)
+        // Focus
+        // PointFrom/ToScreen to translate coordinates
+
+        // EVENTS
+
+        // ContextMenuOpening/Closing/Changed
+        // DragEnter/LeaveOver, Drop
+        // Got/LostFocus
+        // IsEnabledChanged
+        // IsPointerOver/DirectlyOverChanged
+        // IsVisibleChanged
+        // KeyDown/Up
+        // LayoutUpdated ?
+        // Pointer related events
+        // SizeChanged
+        // ToolTipOpening/Closing
+
+        public get ownerWindows(): Window {
+            return this._ownerWindow;
+        }
+
+        public get style(): string {
+            if (!this.style) {
+                return UIElementStyleManager.DefaultStyleName;
+            }
+            return this._style.name;
+        }
+
+        public set style(value: string) {
+            if (this._style && (this._style.name === value)) {
+                return;
+            }
+
+            let newStyle: UIElementStyle = null;
+            if (value) {
+                newStyle = UIElementStyleManager.getStyle(Tools.getFullClassName(this), value);
+                if (!newStyle) {
+                    throw Error(`Couldn't find Style ${value} for UIElement ${Tools.getFullClassName(this)}`);
+                }
+            }
+
+            if (this._style) {
+                this._style.removeStyle(this);
+            }
+
+            if (newStyle) {
+                newStyle.applyStyle(this);
+            }
+            
+            this._style = newStyle;
+        }
+
+        /**
+         * A string that identifies the UIElement.
+         * The id is optional and there's possible collision with other UIElement's id as the uniqueness is not supported.
+         */
+        public get id(): string {
+            return this._id;
+        }
+
+        public set id(value: string) {
+            if (this._id === value) {
+                return;
+            }
+
+            this._id = value;
+        }
+
+        /**
+         * Return a unique id automatically generated.
+         * This property is mainly used for serialization to ensure a perfect way of identifying a UIElement
+         */
+        public get uid(): string {
+            if (!this._uid) {
+                this._uid = Tools.RandomId();
+            }
+            return this._uid;
+        }
+
+        public get hierarchyDepth(): number {
+            return this._hierarchyDepth;
+        }
+
+        @dependencyProperty(0, pi => UIElement.parentProperty = pi)
+        public get parent(): UIElement {
+            return this._parent;
+        }
+
+        public set parent(value: UIElement) {
+            this._parent = value;
+        }
+
+        @dependencyProperty(1, pi => UIElement.widthProperty = pi)
+        public get width(): number {
+            return this._width;
+        }
+
+        public set width(value: number) {
+            this._width = value;
+        }
+
+        @dependencyProperty(2, pi => UIElement.heightProperty = pi)
+        public get height(): number {
+            return this._height;
+        }
+
+        public set height(value: number) {
+            this._height = value;
+        }
+
+        @dependencyProperty(3, pi => UIElement.minWidthProperty = pi)
+        public get minWidth(): number {
+            return this._minWidth;
+        }
+
+        public set minWidth(value: number) {
+            this._minWidth = value;
+        }
+
+        @dependencyProperty(4, pi => UIElement.minHeightProperty = pi)
+        public get minHheight(): number {
+            return this._minHeight;
+        }
+
+        public set minHeight(value: number) {
+            this._minHeight = value;
+        }
+
+        @dependencyProperty(5, pi => UIElement.maxWidthProperty = pi)
+        public get maxWidth(): number {
+            return this._maxWidth;
+        }
+
+        public set maxWidth(value: number) {
+            this._maxWidth = value;
+        }
+
+        @dependencyProperty(6, pi => UIElement.maxHeightProperty = pi)
+        public get maxHeight(): number {
+            return this._maxHeight;
+        }
+
+        public set maxHeight(value: number) {
+            this._maxHeight = value;
+        }
+
+        @dependencyProperty(7, pi => UIElement.actualWidthProperty = pi)
+        public get actualWidth(): number {
+            return this._actualWidth;
+        }
+
+        public set actualWidth(value: number) {
+            this._actualWidth = value;
+        }
+
+        @dependencyProperty(8, pi => UIElement.actualHeightProperty = pi)
+        public get actualHeight(): number {
+            return this._actualHeight;
+        }
+
+        public set actualHeight(value: number) {
+            this._actualHeight = value;
+        }
+
+        @dynamicLevelProperty(9, pi => UIElement.marginProperty = pi)
+        /**
+         * You can get/set a margin on the primitive through this property
+         * @returns the margin object, if there was none, a default one is created and returned
+         */
+        public get margin(): PrimitiveThickness {
+            if (!this._margin) {
+                this._margin = new PrimitiveThickness(() => {
+                    if (!this.parent) {
+                        return null;
+                    }
+                    return this.parent.margin;
+                });
+            }
+            return this._margin;
+        }
+
+        public set margin(value: PrimitiveThickness) {
+            this.margin.copyFrom(value);
+        }
+
+        public get _hasMargin(): boolean {
+            return (this._margin !== null && !this._margin.isDefault) || (this._marginAlignment !== null && !this._marginAlignment.isDefault);
+        }
+
+        @dynamicLevelProperty(10, pi => UIElement.paddingProperty = pi)
+        /**
+         * You can get/set a margin on the primitive through this property
+         * @returns the margin object, if there was none, a default one is created and returned
+         */
+        public get padding(): PrimitiveThickness {
+            if (!this._padding) {
+                this._padding = new PrimitiveThickness(() => {
+                    if (!this.parent) {
+                        return null;
+                    }
+                    return this.parent.padding;
+                });
+            }
+            return this._padding;
+        }
+
+        public set padding(value: PrimitiveThickness) {
+            this.padding.copyFrom(value);
+        }
+
+        private get _hasPadding(): boolean {
+            return this._padding !== null && !this._padding.isDefault;
+        }
+
+        @dynamicLevelProperty(11, pi => UIElement.marginAlignmentProperty = pi)
+        /**
+         * You can get/set the margin alignment through this property
+         */
+        public get marginAlignment(): PrimitiveAlignment {
+            if (!this._marginAlignment) {
+                this._marginAlignment = new PrimitiveAlignment();
+            }
+            return this._marginAlignment;
+        }
+
+        public set marginAlignment(value: PrimitiveAlignment) {
+            this.marginAlignment.copyFrom(value);
+        }
+
+        /**
+         * Check if there a marginAlignment specified (non null and not default)
+         */
+        public get _hasMarginAlignment(): boolean {
+            return (this._marginAlignment !== null && !this._marginAlignment.isDefault);
+        }
+
+        @dynamicLevelProperty(12, pi => UIElement.isEnabledProperty = pi)
+        /**
+         * True if the UIElement is enabled, false if it's disabled
+         */
+        public get isEnabled(): boolean {
+            return this._isEnabled;
+        }
+
+        public set isEnabled(value: boolean) {
+            this._isEnabled = value;
+        }
+
+        @dynamicLevelProperty(13, pi => UIElement.isFocusedProperty = pi)
+        /**
+         * True if the UIElement has the focus, false if it doesn't
+         */
+        public get isFocused(): boolean {
+            return this._isFocused;
+        }
+
+        public set isFocused(value: boolean) {
+            this._isFocused = value;
+        }
+
+        @dynamicLevelProperty(14, pi => UIElement.isMouseOverProperty = pi)
+        /**
+         * True if the UIElement has the mouse over it
+         */
+        public get isMouseOver(): boolean {
+            return this._isMouseOver;
+        }
+
+        public set isMouseOver(value: boolean) {
+            this._isMouseOver = value;
+        }
+
+        /**
+         * Check if a given flag is set
+         * @param flag the flag value
+         * @return true if set, false otherwise
+         */
+        public _isFlagSet(flag: number): boolean {
+            return (this._flags & flag) !== 0;
+        }
+
+        /**
+         * Check if all given flags are set
+         * @param flags the flags ORed
+         * @return true if all the flags are set, false otherwise
+         */
+        public _areAllFlagsSet(flags: number): boolean {
+            return (this._flags & flags) === flags;
+        }
+
+        /**
+         * Check if at least one flag of the given flags is set
+         * @param flags the flags ORed
+         * @return true if at least one flag is set, false otherwise
+         */
+        public _areSomeFlagsSet(flags: number): boolean {
+            return (this._flags & flags) !== 0;
+        }
+
+        /**
+         * Clear the given flags
+         * @param flags the flags to clear
+         */
+        public _clearFlags(flags: number) {
+            this._flags &= ~flags;
+        }
+
+        /**
+         * Set the given flags to true state
+         * @param flags the flags ORed to set
+         * @return the flags state before this call
+         */
+        public _setFlags(flags: number): number {
+            let cur = this._flags;
+            this._flags |= flags;
+            return cur;
+        }
+
+        /**
+         * Change the state of the given flags
+         * @param flags the flags ORed to change
+         * @param state true to set them, false to clear them
+         */
+        public _changeFlags(flags: number, state: boolean) {
+            if (state) {
+                this._flags |= flags;
+            } else {
+                this._flags &= ~flags;
+            }
+        }
+
+        private _assignTemplate(templateName: string) {
+            if (!templateName) {
+                templateName = UIElementRenderingTemplateManager.DefaultTemplateName;
+            }
+            let className = Tools.getFullClassName(this);
+            if (!className) {
+                throw Error("Couldn't access class name of this UIElement, you have to decorate the type with the className decorator");
+            }
+
+            let factory = UIElementRenderingTemplateManager.getRenderingTemplate(className, templateName);
+            if (!factory) {
+                throw Error(`Couldn't get the renderingTemplate ${templateName} of class ${className}`);
+            }
+
+            this._renderingTemplate = factory();
+            this._renderingTemplate.attach(this);
+        }
+
+        public _createVisualTree() {
+            let parentPrim: Prim2DBase = this.ownerWindows.canvas;
+            if (this.parent) {
+                parentPrim = this.parent.visualChildrenPlaceholder;
+            }
+
+            this._visualPlaceholder = new Group2D({ parent: parentPrim, id: `GUI Visual Placeholder of ${this.id}`});
+            let p = this._visualPlaceholder;
+            p.addExternalData<UIElement>("_GUIOwnerElement_", this);
+            p.dataSource = this;
+            p.createSimpleDataBinding(Prim2DBase.widthProperty, "width", DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.heightProperty, "height", DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.actualWidthProperty, "actualWidth", DataBinding.MODE_ONEWAYTOSOURCE);
+            p.createSimpleDataBinding(Prim2DBase.actualHeightProperty, "actualHeight", DataBinding.MODE_ONEWAYTOSOURCE);
+            p.createSimpleDataBinding(Prim2DBase.marginProperty, "margin", DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.paddingProperty, "padding", DataBinding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.marginAlignmentProperty, "marginAlignment", DataBinding.MODE_ONEWAY);
+            this.createVisualTree();
+        }
+
+        public _patchUIElement(ownerWindow: Window, parent: UIElement) {
+            if (ownerWindow) {
+                if (!this._ownerWindow) {
+                    ownerWindow._registerVisualToBuild(this);
+                }
+                this._ownerWindow = ownerWindow;
+            }
+            this._parent = parent;
+
+            if (parent) {
+                this._hierarchyDepth = parent.hierarchyDepth + 1;
+            }
+
+            let children = this._getChildren();
+            if (children) {
+                for (let curChild of children) {
+                    curChild._patchUIElement(ownerWindow, this);
+                }
+            }
+        }
+
+        // Overload the SmartPropertyBase's method to provide the additional logic of returning the parent's dataSource if there's no dataSource specified at this level.
+        protected _getDataSource(): IPropertyChanged {
+            let levelDS = super._getDataSource();
+            if (levelDS != null) {
+                return levelDS;
+            }
+
+            let p = this.parent;
+            if (p != null) {
+                return p.dataSource;
+            }
+
+            return null;
+        }
+
+        protected createVisualTree() {
+            let res = this._renderingTemplate.createVisualTree(this, this._visualPlaceholder);
+            this._visualTemplateRoot = res.root;
+            this._visualChildrenPlaceholder = res.contentPlaceholder;
+        }
+
+        protected get visualPlaceholder(): Prim2DBase {
+            return this._visualPlaceholder;
+        }
+
+        protected get visualTemplateRoot(): Prim2DBase {
+            return this._visualTemplateRoot;
+        }
+
+        protected get visualChildrenPlaceholder(): Prim2DBase {
+            return this._visualChildrenPlaceholder;
+        }
+
+        protected get _position(): Vector2 { return null; } // TODO use abstract keyword when TS 2.0 will be approved
+        protected abstract _getChildren(): Array<UIElement>;
+
+        public static flagVisualToBuild = 0x0000001;    // set if the UIElement visual must be updated
+
+        protected _visualPlaceholder: Group2D;
+        protected _visualTemplateRoot: Prim2DBase;
+        protected _visualChildrenPlaceholder: Prim2DBase;
+        private _renderingTemplate: UIElementRenderingTemplateBase;
+        private _parent: UIElement;
+        private _hierarchyDepth: number;
+        private _flags: number;
+        private _style: UIElementStyle;
+        private _ownerWindow: Window;
+        private _id: string;
+        private _uid: string;
+        private _actualWidth: number;
+        private _actualHeight: number;
+        private _minWidth: number;
+        private _minHeight: number;
+        private _maxWidth: number;
+        private _maxHeight: number;
+        private _width: number;
+        private _height: number;
+        private _margin: PrimitiveThickness;
+        private _padding: PrimitiveThickness;
+        private _marginAlignment: PrimitiveAlignment;
+        private _isEnabled: boolean;
+        private _isFocused: boolean;
+        private _isMouseOver: boolean;
+    }
+
+    export abstract class UIElementStyle {
+        abstract removeStyle(uiel: UIElement);
+        abstract applyStyle(uiel: UIElement);
+        get name(): string { return null; } // TODO use abstract keyword when TS 2.0 will be approved
+    }
+
+    export class UIElementStyleManager {
+        static getStyle(uiElType: string, styleName: string): UIElementStyle {
+            let styles = UIElementStyleManager.stylesByUIElement.get(uiElType);
+            if (!styles) {
+                throw Error(`The type ${uiElType} is unknown, no style were registered for it.`);
+            }
+            let style = styles.get(styleName);
+            if (!style) {
+                throw Error(`Couldn't find Template ${styleName} of UIElement type ${uiElType}`);
+            }
+            return style;
+        }
+
+        static registerStyle(uiElType: string, templateName: string, style: UIElementStyle) {
+            let templates = UIElementStyleManager.stylesByUIElement.getOrAddWithFactory(uiElType, () => new StringDictionary<UIElementStyle>());
+            if (templates.contains(templateName)) {
+                templates[templateName] = style;
+            } else {
+                templates.add(templateName, style);
+            }
+        }
+
+        static stylesByUIElement: StringDictionary<StringDictionary<UIElementStyle>> = new StringDictionary<StringDictionary<UIElementStyle>>();
+
+        public static get DefaultStyleName(): string {
+            return UIElementStyleManager._defaultStyleName;
+        }
+
+        public static set DefaultStyleName(value: string) {
+            UIElementStyleManager._defaultStyleName = value;
+        }
+
+        private static _defaultStyleName = "Default";
+    }
+
+    export class UIElementRenderingTemplateManager {
+        static getRenderingTemplate(uiElType: string, templateName: string): () => UIElementRenderingTemplateBase {
+            let templates = UIElementRenderingTemplateManager.renderingTemplatesByUIElement.get(uiElType);
+            if (!templates) {
+                throw Error(`The type ${uiElType} is unknown, no Rendering Template were registered for it.`);
+            }
+            let templateFactory = templates.get(templateName);
+            if (!templateFactory) {
+                throw Error(`Couldn't find Template ${templateName} of UI Element type ${uiElType}`);
+            }
+            return templateFactory;
+        }
+
+        static registerRenderingTemplate(uiElType: string, templateName: string, factory: () => UIElementRenderingTemplateBase) {
+            let templates = UIElementRenderingTemplateManager.renderingTemplatesByUIElement.getOrAddWithFactory(uiElType, () => new StringDictionary<() => UIElementRenderingTemplateBase>());
+            if (templates.contains(templateName)) {
+                templates[templateName] = factory;
+            } else {
+                templates.add(templateName, factory);
+            }
+        }
+
+        static renderingTemplatesByUIElement: StringDictionary<StringDictionary<() => UIElementRenderingTemplateBase>> = new StringDictionary<StringDictionary<() => UIElementRenderingTemplateBase>>();
+
+        public static get DefaultTemplateName(): string {
+            return UIElementRenderingTemplateManager._defaultTemplateName;
+        }
+
+        public static set DefaultTemplateName(value: string) {
+            UIElementRenderingTemplateManager._defaultTemplateName = value;
+        }
+        
+        private static _defaultTemplateName = "Default";
+    }
+
+    export abstract class UIElementRenderingTemplateBase {
+        attach(owner: UIElement) {
+            this._owner = owner;
+        }
+        detach() {
+            
+        }
+
+        public get owner(): UIElement {
+            return this._owner;
+        }
+
+        abstract createVisualTree(owner: UIElement, visualPlaceholder: Group2D): { root: Prim2DBase, contentPlaceholder: Prim2DBase };
+
+        private _owner: UIElement;
+    }
+
+    export function registerWindowRenderingTemplate(uiElType: string, templateName: string, factory: () => UIElementRenderingTemplateBase): (target: Object) => void {
+        return () => {
+            UIElementRenderingTemplateManager.registerRenderingTemplate(uiElType, templateName, factory);
+        }
+    }
+
+}

+ 191 - 0
canvas2D/src/GUI/babylon.gui.button.js

@@ -0,0 +1,191 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Button = (function (_super) {
+        __extends(Button, _super);
+        function Button(settings) {
+            if (!settings) {
+                settings = {};
+            }
+            _super.call(this, settings);
+            // For a button the default contentAlignemnt is center/center
+            if (settings.contentAlignment == null) {
+                this.contentAlignment.horizontal = BABYLON.PrimitiveAlignment.AlignCenter;
+                this.contentAlignment.vertical = BABYLON.PrimitiveAlignment.AlignCenter;
+            }
+            this.normalEnabledBackground = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#337AB7FF");
+            this.normalDisabledBackground = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#7BA9D0FF");
+            this.normalMouseOverBackground = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#286090FF");
+            this.normalPushedBackground = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#1E496EFF");
+            this.normalEnabledBorder = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#2E6DA4FF");
+            this.normalDisabledBorder = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#77A0C4FF");
+            this.normalMouseOverBorder = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#204D74FF");
+            this.normalPushedBorder = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#2E5D9EFF");
+            this.defaultEnabledBackground = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            this.defaultDisabledBackground = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            this.defaultMouseOverBackground = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#E6E6E6FF");
+            this.defaultPushedBackground = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#D4D4D4FF");
+            this.defaultEnabledBorder = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#CCCCCCFF");
+            this.defaultDisabledBorder = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#DEDEDEFF");
+            this.defaultMouseOverBorder = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#ADADADFF");
+            this.defaultPushedBorder = BABYLON.Canvas2D.GetSolidColorBrushFromHex("#6C8EC5FF");
+        }
+        Object.defineProperty(Button.prototype, "isPushed", {
+            get: function () {
+                return this._isPushed;
+            },
+            set: function (value) {
+                this._isPushed = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Button.prototype, "isDefault", {
+            get: function () {
+                return this._isDefault;
+            },
+            set: function (value) {
+                this._isDefault = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Button.prototype, "isOutline", {
+            get: function () {
+                return this._isOutline;
+            },
+            set: function (value) {
+                this._isOutline = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Button.prototype, "clickObservable", {
+            get: function () {
+                if (!this._clickObservable) {
+                    this._clickObservable = new BABYLON.Observable();
+                }
+                return this._clickObservable;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Button.prototype._raiseClick = function () {
+            console.log("click");
+        };
+        Button.prototype.createVisualTree = function () {
+            var _this = this;
+            _super.prototype.createVisualTree.call(this);
+            var p = this._visualPlaceholder;
+            p.pointerEventObservable.add(function (e, s) {
+                // We reject an event coming from the placeholder because it means it's on an empty spot, so it's not valid.
+                if (e.relatedTarget === _this._visualPlaceholder) {
+                    return;
+                }
+                if (s.mask === BABYLON.PrimitivePointerInfo.PointerUp) {
+                    _this._raiseClick();
+                    _this.isPushed = false;
+                }
+                else if (s.mask === BABYLON.PrimitivePointerInfo.PointerDown) {
+                    _this.isPushed = true;
+                }
+            }, BABYLON.PrimitivePointerInfo.PointerUp | BABYLON.PrimitivePointerInfo.PointerDown);
+        };
+        Object.defineProperty(Button.prototype, "_position", {
+            get: function () {
+                return BABYLON.Vector2.Zero();
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Button.BUTTON_PROPCOUNT = BABYLON.ContentControl.CONTENTCONTROL_PROPCOUNT + 3;
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.ContentControl.CONTROL_PROPCOUNT + 0, function (pi) { return Button.isPushedProperty = pi; })
+        ], Button.prototype, "isPushed", null);
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.ContentControl.CONTROL_PROPCOUNT + 1, function (pi) { return Button.isDefaultProperty = pi; })
+        ], Button.prototype, "isDefault", null);
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.ContentControl.CONTROL_PROPCOUNT + 2, function (pi) { return Button.isOutlineProperty = pi; })
+        ], Button.prototype, "isOutline", null);
+        Button = __decorate([
+            BABYLON.className("Button", "BABYLON")
+        ], Button);
+        return Button;
+    }(BABYLON.ContentControl));
+    BABYLON.Button = Button;
+    var DefaultButtonRenderingTemplate = (function (_super) {
+        __extends(DefaultButtonRenderingTemplate, _super);
+        function DefaultButtonRenderingTemplate() {
+            _super.apply(this, arguments);
+        }
+        DefaultButtonRenderingTemplate.prototype.createVisualTree = function (owner, visualPlaceholder) {
+            this._rect = new BABYLON.Rectangle2D({ parent: visualPlaceholder, fill: "#FF8080FF", border: "#FF8080FF", roundRadius: 10, borderThickness: 2 });
+            this.stateChange();
+            return { root: this._rect, contentPlaceholder: this._rect };
+        };
+        DefaultButtonRenderingTemplate.prototype.attach = function (owner) {
+            var _this = this;
+            _super.prototype.attach.call(this, owner);
+            this.owner.propertyChanged.add(function (e, s) { return _this.stateChange(); }, BABYLON.UIElement.isEnabledProperty.flagId |
+                BABYLON.UIElement.isFocusedProperty.flagId |
+                BABYLON.UIElement.isMouseOverProperty.flagId |
+                Button.isDefaultProperty.flagId |
+                Button.isOutlineProperty.flagId |
+                Button.isPushedProperty.flagId);
+        };
+        DefaultButtonRenderingTemplate.prototype.stateChange = function () {
+            var b = this.owner;
+            var bg = b.isDefault ? b.defaultEnabledBackground : b.normalEnabledBackground;
+            var bd = b.isDefault ? b.defaultEnabledBorder : b.normalEnabledBorder;
+            if (b.isPushed) {
+                if (b.isDefault) {
+                    bg = b.defaultPushedBackground;
+                    bd = b.defaultPushedBorder;
+                }
+                else {
+                    bg = b.normalPushedBackground;
+                    bd = b.normalPushedBorder;
+                }
+            }
+            else if (b.isMouseOver) {
+                console.log("MouseOver Style");
+                if (b.isDefault) {
+                    bg = b.defaultMouseOverBackground;
+                    bd = b.defaultMouseOverBorder;
+                }
+                else {
+                    bg = b.normalMouseOverBackground;
+                    bd = b.normalMouseOverBorder;
+                }
+            }
+            else if (!b.isEnabled) {
+                if (b.isDefault) {
+                    bg = b.defaultDisabledBackground;
+                    bd = b.defaultDisabledBorder;
+                }
+                else {
+                    bg = b.normalDisabledBackground;
+                    bd = b.normalDisabledBorder;
+                }
+            }
+            this._rect.fill = bg;
+            this._rect.border = bd;
+        };
+        DefaultButtonRenderingTemplate = __decorate([
+            BABYLON.registerWindowRenderingTemplate("BABYLON.Button", "Default", function () { return new DefaultButtonRenderingTemplate(); })
+        ], DefaultButtonRenderingTemplate);
+        return DefaultButtonRenderingTemplate;
+    }(BABYLON.UIElementRenderingTemplateBase));
+    BABYLON.DefaultButtonRenderingTemplate = DefaultButtonRenderingTemplate;
+})(BABYLON || (BABYLON = {}));

+ 209 - 0
canvas2D/src/GUI/babylon.gui.button.ts

@@ -0,0 +1,209 @@
+module BABYLON {
+
+    @className("Button", "BABYLON")
+    export class Button extends ContentControl {
+
+        static BUTTON_PROPCOUNT = ContentControl.CONTENTCONTROL_PROPCOUNT + 3;
+
+        static isPushedProperty: Prim2DPropInfo;
+        static isDefaultProperty: Prim2DPropInfo;
+        static isOutlineProperty: Prim2DPropInfo;
+
+        constructor(settings?: {
+
+            id              ?: string,
+            parent          ?: UIElement,
+            templateName    ?: string,
+            styleName       ?: string,
+            content         ?: any,
+            contentAlignment?: string,
+            marginTop       ?: number | string,
+            marginLeft      ?: number | string,
+            marginRight     ?: number | string,
+            marginBottom    ?: number | string,
+            margin          ?: number | string,
+            marginHAlignment?: number,
+            marginVAlignment?: number,
+            marginAlignment ?: string,
+            paddingTop      ?: number | string,
+            paddingLeft     ?: number | string,
+            paddingRight    ?: number | string,
+            paddingBottom   ?: number | string,
+            padding         ?: string,
+        }) {
+            if (!settings) {
+                settings = {};
+            }
+
+            super(settings);
+
+            // For a button the default contentAlignemnt is center/center
+            if (settings.contentAlignment == null) {
+                this.contentAlignment.horizontal = PrimitiveAlignment.AlignCenter;
+                this.contentAlignment.vertical = PrimitiveAlignment.AlignCenter;
+            }
+            this.normalEnabledBackground    = Canvas2D.GetSolidColorBrushFromHex("#337AB7FF");
+            this.normalDisabledBackground   = Canvas2D.GetSolidColorBrushFromHex("#7BA9D0FF");
+            this.normalMouseOverBackground  = Canvas2D.GetSolidColorBrushFromHex("#286090FF");
+            this.normalPushedBackground     = Canvas2D.GetSolidColorBrushFromHex("#1E496EFF");
+            this.normalEnabledBorder        = Canvas2D.GetSolidColorBrushFromHex("#2E6DA4FF");
+            this.normalDisabledBorder       = Canvas2D.GetSolidColorBrushFromHex("#77A0C4FF");
+            this.normalMouseOverBorder      = Canvas2D.GetSolidColorBrushFromHex("#204D74FF");
+            this.normalPushedBorder         = Canvas2D.GetSolidColorBrushFromHex("#2E5D9EFF");
+
+            this.defaultEnabledBackground   = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            this.defaultDisabledBackground  = Canvas2D.GetSolidColorBrushFromHex("#FFFFFFFF");
+            this.defaultMouseOverBackground = Canvas2D.GetSolidColorBrushFromHex("#E6E6E6FF");
+            this.defaultPushedBackground    = Canvas2D.GetSolidColorBrushFromHex("#D4D4D4FF");
+            this.defaultEnabledBorder       = Canvas2D.GetSolidColorBrushFromHex("#CCCCCCFF");
+            this.defaultDisabledBorder      = Canvas2D.GetSolidColorBrushFromHex("#DEDEDEFF");
+            this.defaultMouseOverBorder     = Canvas2D.GetSolidColorBrushFromHex("#ADADADFF");
+            this.defaultPushedBorder        = Canvas2D.GetSolidColorBrushFromHex("#6C8EC5FF");
+        }
+
+        @dependencyProperty(ContentControl.CONTROL_PROPCOUNT + 0, pi => Button.isPushedProperty = pi)
+        public get isPushed(): boolean {
+            return this._isPushed;
+        }
+
+        public set isPushed(value: boolean) {
+            this._isPushed = value;
+        }
+
+        @dependencyProperty(ContentControl.CONTROL_PROPCOUNT + 1, pi => Button.isDefaultProperty = pi)
+        public get isDefault(): boolean {
+            return this._isDefault;
+        }
+
+        public set isDefault(value: boolean) {
+            this._isDefault = value;
+        }
+
+        @dependencyProperty(ContentControl.CONTROL_PROPCOUNT + 2, pi => Button.isOutlineProperty = pi)
+        public get isOutline(): boolean {
+            return this._isOutline;
+        }
+
+        public set isOutline(value: boolean) {
+            this._isOutline = value;
+        }
+
+        public get clickObservable(): Observable<Button> {
+            if (!this._clickObservable) {
+                this._clickObservable = new Observable<Button>();
+            }
+            return this._clickObservable;
+        }
+
+        public _raiseClick() {
+            console.log("click");
+        }
+
+        protected createVisualTree() {
+            super.createVisualTree();
+            let p = this._visualPlaceholder;
+            p.pointerEventObservable.add((e, s) => {
+                // We reject an event coming from the placeholder because it means it's on an empty spot, so it's not valid.
+                if (e.relatedTarget === this._visualPlaceholder) {
+                    return;
+                }
+
+                if (s.mask === PrimitivePointerInfo.PointerUp) {
+                    this._raiseClick();
+                    this.isPushed = false;
+                } else if (s.mask === PrimitivePointerInfo.PointerDown) {
+                    this.isPushed = true;
+                }
+
+            }, PrimitivePointerInfo.PointerUp|PrimitivePointerInfo.PointerDown);
+        }
+
+        private _isPushed: boolean;
+        private _isDefault: boolean;
+        private _isOutline: boolean;
+        private _clickObservable: Observable<Button>;
+
+        protected get _position(): Vector2 {
+            return Vector2.Zero();
+        }
+
+        public normalEnabledBackground  : IBrush2D;
+        public normalDisabledBackground : IBrush2D;
+        public normalMouseOverBackground: IBrush2D;
+        public normalPushedBackground   : IBrush2D;
+        public normalEnabledBorder      : IBrush2D;
+        public normalDisabledBorder     : IBrush2D;
+        public normalMouseOverBorder    : IBrush2D;
+        public normalPushedBorder       : IBrush2D;
+
+        public defaultEnabledBackground  : IBrush2D;
+        public defaultDisabledBackground : IBrush2D;
+        public defaultMouseOverBackground: IBrush2D;
+        public defaultPushedBackground   : IBrush2D;
+        public defaultEnabledBorder      : IBrush2D;
+        public defaultDisabledBorder     : IBrush2D;
+        public defaultMouseOverBorder    : IBrush2D;
+        public defaultPushedBorder       : IBrush2D;
+    }
+
+    @registerWindowRenderingTemplate("BABYLON.Button", "Default", () => new DefaultButtonRenderingTemplate())
+    export class DefaultButtonRenderingTemplate extends UIElementRenderingTemplateBase {
+
+        createVisualTree(owner: UIElement, visualPlaceholder: Group2D): { root: Prim2DBase; contentPlaceholder: Prim2DBase } {
+            this._rect = new Rectangle2D({ parent: visualPlaceholder, fill: "#FF8080FF", border: "#FF8080FF", roundRadius: 10, borderThickness: 2 });
+
+            this.stateChange();
+            return { root: this._rect, contentPlaceholder: this._rect };
+        }
+
+        attach(owner: UIElement): void {
+            super.attach(owner);
+
+            this.owner.propertyChanged.add((e, s) => this.stateChange(),
+                UIElement.isEnabledProperty.flagId   |
+                UIElement.isFocusedProperty.flagId   |
+                UIElement.isMouseOverProperty.flagId |
+                Button.isDefaultProperty.flagId      |
+                Button.isOutlineProperty.flagId      |
+                Button.isPushedProperty.flagId);
+        }
+
+        stateChange(): void {
+            let b = <Button>this.owner;
+            let bg = b.isDefault ? b.defaultEnabledBackground : b.normalEnabledBackground;
+            let bd = b.isDefault ? b.defaultEnabledBorder : b.normalEnabledBorder;
+
+            if (b.isPushed) {
+                if (b.isDefault) {
+                    bg = b.defaultPushedBackground;
+                    bd = b.defaultPushedBorder;
+                } else {
+                    bg = b.normalPushedBackground;
+                    bd = b.normalPushedBorder;
+                }
+            } else if (b.isMouseOver) {
+                console.log("MouseOver Style");
+                if (b.isDefault) {
+                    bg = b.defaultMouseOverBackground;
+                    bd = b.defaultMouseOverBorder;
+                } else {
+                    bg = b.normalMouseOverBackground;
+                    bd = b.normalMouseOverBorder;
+                }
+            } else if (!b.isEnabled) {
+                if (b.isDefault) {
+                    bg = b.defaultDisabledBackground;
+                    bd = b.defaultDisabledBorder;
+                } else {
+                    bg = b.normalDisabledBackground;
+                    bd = b.normalDisabledBorder;
+                }
+            }
+
+            this._rect.fill = bg;
+            this._rect.border = bd;
+        }
+
+        private _rect: Rectangle2D;
+    }
+}

+ 212 - 0
canvas2D/src/GUI/babylon.gui.control.js

@@ -0,0 +1,212 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Control = (function (_super) {
+        __extends(Control, _super);
+        function Control(settings) {
+            _super.call(this, settings);
+        }
+        Object.defineProperty(Control.prototype, "background", {
+            get: function () {
+                if (!this._background) {
+                    this._background = new BABYLON.ObservableStringDictionary(false);
+                }
+                return this._background;
+            },
+            set: function (value) {
+                this.background.copyFrom(value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Control.prototype, "border", {
+            get: function () {
+                return this._border;
+            },
+            set: function (value) {
+                this._border = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Control.prototype, "borderThickness", {
+            get: function () {
+                return this._borderThickness;
+            },
+            set: function (value) {
+                this._borderThickness = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Control.prototype, "fontName", {
+            get: function () {
+                return this._fontName;
+            },
+            set: function (value) {
+                this._fontName = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Control.prototype, "foreground", {
+            get: function () {
+                return this._foreground;
+            },
+            set: function (value) {
+                this._foreground = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Control.CONTROL_PROPCOUNT = BABYLON.UIElement.UIELEMENT_PROPCOUNT + 5;
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.UIElement.UIELEMENT_PROPCOUNT + 0, function (pi) { return Control.backgroundProperty = pi; })
+        ], Control.prototype, "background", null);
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.UIElement.UIELEMENT_PROPCOUNT + 1, function (pi) { return Control.borderProperty = pi; })
+        ], Control.prototype, "border", null);
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.UIElement.UIELEMENT_PROPCOUNT + 2, function (pi) { return Control.borderThicknessProperty = pi; })
+        ], Control.prototype, "borderThickness", null);
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.UIElement.UIELEMENT_PROPCOUNT + 3, function (pi) { return Control.fontNameProperty = pi; })
+        ], Control.prototype, "fontName", null);
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.UIElement.UIELEMENT_PROPCOUNT + 4, function (pi) { return Control.foregroundProperty = pi; })
+        ], Control.prototype, "foreground", null);
+        Control = __decorate([
+            BABYLON.className("Control", "BABYLON")
+        ], Control);
+        return Control;
+    }(BABYLON.UIElement));
+    BABYLON.Control = Control;
+    var ContentControl = (function (_super) {
+        __extends(ContentControl, _super);
+        function ContentControl(settings) {
+            if (!settings) {
+                settings = {};
+            }
+            _super.call(this, settings);
+            if (settings.content != null) {
+                this._content = settings.content;
+            }
+            if (settings.contentAlignment != null) {
+                this.contentAlignment.fromString(settings.contentAlignment);
+            }
+        }
+        ContentControl.prototype.dispose = function () {
+            if (this.isDisposed) {
+                return false;
+            }
+            if (this.content && this.content.dispose) {
+                this.content.dispose();
+                this.content = null;
+            }
+            if (this.__contentUIElement) {
+                this.__contentUIElement.dispose();
+                this.__contentUIElement = null;
+            }
+            _super.prototype.dispose.call(this);
+            return true;
+        };
+        Object.defineProperty(ContentControl.prototype, "content", {
+            get: function () {
+                return this._content;
+            },
+            set: function (value) {
+                this._content = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ContentControl.prototype, "contentAlignment", {
+            get: function () {
+                if (!this._contentAlignment) {
+                    this._contentAlignment = new BABYLON.PrimitiveAlignment();
+                }
+                return this._contentAlignment;
+            },
+            set: function (value) {
+                this.contentAlignment.copyFrom(value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ContentControl.prototype, "_hasContentAlignment", {
+            /**
+             * Check if there a contentAlignment specified (non null and not default)
+             */
+            get: function () {
+                return (this._contentAlignment !== null && !this._contentAlignment.isDefault);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ContentControl.prototype, "_contentUIElement", {
+            get: function () {
+                if (!this.__contentUIElement) {
+                    this._buildContentUIElement();
+                }
+                return this.__contentUIElement;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ContentControl.prototype._buildContentUIElement = function () {
+            var c = this._content;
+            this.__contentUIElement = null;
+            // Already a UIElement
+            if (c instanceof BABYLON.UIElement) {
+                this.__contentUIElement = c;
+            }
+            else if ((typeof c === "string") || (typeof c === "boolean") || (typeof c === "number")) {
+                var l = new BABYLON.Label({ parent: this, id: "Content of " + this.id });
+                var binding = new BABYLON.DataBinding();
+                binding.propertyPathName = "content";
+                binding.stringFormat = function (v) { return ("" + v); };
+                binding.dataSource = this;
+                l.createDataBinding(BABYLON.Label.textProperty, binding);
+                binding = new BABYLON.DataBinding();
+                binding.propertyPathName = "contentAlignment";
+                binding.dataSource = this;
+                l.createDataBinding(BABYLON.Label.marginAlignmentProperty, binding);
+                this.__contentUIElement = l;
+            }
+            else {
+            }
+            if (this.__contentUIElement) {
+                this.__contentUIElement._patchUIElement(this.ownerWindows, this);
+            }
+        };
+        ContentControl.prototype._getChildren = function () {
+            var children = new Array();
+            if (this.content) {
+                children.push(this._contentUIElement);
+            }
+            return children;
+        };
+        ContentControl.CONTENTCONTROL_PROPCOUNT = Control.CONTROL_PROPCOUNT + 2;
+        __decorate([
+            BABYLON.dependencyProperty(Control.CONTROL_PROPCOUNT + 0, function (pi) { return ContentControl.contentProperty = pi; })
+        ], ContentControl.prototype, "content", null);
+        __decorate([
+            BABYLON.dependencyProperty(Control.CONTROL_PROPCOUNT + 1, function (pi) { return ContentControl.contentAlignmentProperty = pi; })
+        ], ContentControl.prototype, "contentAlignment", null);
+        ContentControl = __decorate([
+            BABYLON.className("ContentControl", "BABYLON")
+        ], ContentControl);
+        return ContentControl;
+    }(Control));
+    BABYLON.ContentControl = ContentControl;
+})(BABYLON || (BABYLON = {}));

+ 212 - 0
canvas2D/src/GUI/babylon.gui.control.ts

@@ -0,0 +1,212 @@
+module BABYLON {
+
+    @className("Control", "BABYLON")
+    export abstract class Control extends UIElement {
+        static CONTROL_PROPCOUNT = UIElement.UIELEMENT_PROPCOUNT + 5;
+
+        static backgroundProperty     : Prim2DPropInfo;
+        static borderProperty         : Prim2DPropInfo;
+        static borderThicknessProperty: Prim2DPropInfo;
+        static fontNameProperty       : Prim2DPropInfo;
+        static foregroundProperty     : Prim2DPropInfo;
+
+        constructor(settings: {
+            id?: string,
+            templateName?: string,
+            styleName?: string,
+        }) {
+            super(settings);
+        }
+
+        @dependencyProperty(UIElement.UIELEMENT_PROPCOUNT + 0, pi => Control.backgroundProperty = pi)
+        public get background(): StringDictionary<IBrush2D> {
+            if (!this._background) {
+                this._background = new ObservableStringDictionary<IBrush2D>(false);
+            }
+            return this._background;
+        }
+
+        public set background(value: StringDictionary<IBrush2D>) {
+            this.background.copyFrom(value);
+        }
+
+        @dependencyProperty(UIElement.UIELEMENT_PROPCOUNT + 1, pi => Control.borderProperty = pi)
+        public get border(): IBrush2D {
+            return this._border;
+        }
+
+        public set border(value: IBrush2D) {
+            this._border = value;
+        }
+
+        @dependencyProperty(UIElement.UIELEMENT_PROPCOUNT + 2, pi => Control.borderThicknessProperty = pi)
+        public get borderThickness(): number {
+            return this._borderThickness;
+        }
+
+        public set borderThickness(value: number) {
+            this._borderThickness = value;
+        }
+
+        @dependencyProperty(UIElement.UIELEMENT_PROPCOUNT + 3, pi => Control.fontNameProperty = pi)
+        public get fontName(): string {
+            return this._fontName;
+        }
+
+        public set fontName(value: string) {
+            this._fontName = value;
+        }
+
+        @dependencyProperty(UIElement.UIELEMENT_PROPCOUNT + 4, pi => Control.foregroundProperty = pi)
+        public get foreground(): IBrush2D {
+            return this._foreground;
+        }
+
+        public set foreground(value: IBrush2D) {
+            this._foreground = value;
+        }
+
+        private _background: ObservableStringDictionary<IBrush2D>;
+        private _border: IBrush2D;
+        private _borderThickness: number;
+        private _fontName: string;
+        private _foreground: IBrush2D;
+    }
+
+
+    @className("ContentControl", "BABYLON")
+    export abstract class ContentControl extends Control {
+        static CONTENTCONTROL_PROPCOUNT = Control.CONTROL_PROPCOUNT + 2;
+
+        static contentProperty: Prim2DPropInfo;
+        static contentAlignmentProperty: Prim2DPropInfo;
+
+        constructor(settings?: {
+            id              ?: string,
+            templateName    ?: string,
+            styleName       ?: string,
+            content         ?: any,
+            contentAlignment?: string,
+        }) {
+            if (!settings) {
+                settings = {};
+            }
+
+            super(settings);
+
+            if (settings.content!=null) {
+                this._content = settings.content;
+            }
+
+            if (settings.contentAlignment != null) {
+                this.contentAlignment.fromString(settings.contentAlignment);
+            }
+        }
+
+        dispose(): boolean {
+            if (this.isDisposed) {
+                return false;
+            }
+
+            if (this.content && this.content.dispose) {
+                this.content.dispose();
+                this.content = null;
+            }
+
+            if (this.__contentUIElement) {
+                this.__contentUIElement.dispose();
+                this.__contentUIElement = null;
+            }
+
+            super.dispose();
+
+            return true;
+        }
+
+        @dependencyProperty(Control.CONTROL_PROPCOUNT + 0, pi => ContentControl.contentProperty = pi)
+        public get content(): any {
+            return this._content;
+        }
+
+        public set content(value: any) {
+            this._content = value;
+        }
+
+        @dependencyProperty(Control.CONTROL_PROPCOUNT + 1, pi => ContentControl.contentAlignmentProperty = pi)
+        public get contentAlignment(): PrimitiveAlignment {
+            if (!this._contentAlignment) {
+                this._contentAlignment = new PrimitiveAlignment();
+            }
+            return this._contentAlignment;
+        }
+
+        public set contentAlignment(value: PrimitiveAlignment) {
+            this.contentAlignment.copyFrom(value);
+        }
+
+        /**
+         * Check if there a contentAlignment specified (non null and not default)
+         */
+        public get _hasContentAlignment(): boolean {
+            return (this._contentAlignment !== null && !this._contentAlignment.isDefault);
+        }
+
+        protected get _contentUIElement(): UIElement {
+            if (!this.__contentUIElement) {
+                this._buildContentUIElement();
+            }
+
+            return this.__contentUIElement;
+        }
+
+        private _buildContentUIElement() {
+            let c = this._content;
+            this.__contentUIElement = null;
+
+            // Already a UIElement
+            if (c instanceof UIElement) {
+                this.__contentUIElement = c;
+            }
+
+            // Test primary types
+            else if ((typeof c === "string") || (typeof c === "boolean") || (typeof c === "number")) {
+                let l = new Label({ parent: this, id: "Content of " + this.id });
+                let binding = new DataBinding();
+                binding.propertyPathName = "content";
+                binding.stringFormat = v => `${v}`;
+                binding.dataSource = this;
+                l.createDataBinding(Label.textProperty, binding);
+
+                binding = new DataBinding();
+                binding.propertyPathName = "contentAlignment";
+                binding.dataSource = this;
+                l.createDataBinding(Label.marginAlignmentProperty, binding);
+
+                this.__contentUIElement = l;
+            }
+
+            // Data Template!
+            else {
+                // TODO: DataTemplate lookup and create instance
+            }
+
+            if (this.__contentUIElement) {
+                this.__contentUIElement._patchUIElement(this.ownerWindows, this);               
+            }
+        }
+
+        private _content: any;
+        private _contentAlignment: PrimitiveAlignment;
+        private __contentUIElement: UIElement;
+
+        protected _getChildren(): Array<UIElement> {
+            let children = new Array<UIElement>();
+
+            if (this.content) {
+                children.push(this._contentUIElement);
+            }
+
+            return children;
+        }
+    }
+}

+ 76 - 0
canvas2D/src/GUI/babylon.gui.label.js

@@ -0,0 +1,76 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Label = (function (_super) {
+        __extends(Label, _super);
+        function Label(settings) {
+            if (!settings) {
+                settings = {};
+            }
+            _super.call(this, settings);
+            if (settings.text != null) {
+                this.text = settings.text;
+            }
+        }
+        Object.defineProperty(Label.prototype, "_position", {
+            get: function () {
+                return BABYLON.Vector2.Zero();
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Label.prototype._getChildren = function () {
+            return Label._emptyArray;
+        };
+        Label.prototype.createVisualTree = function () {
+            _super.prototype.createVisualTree.call(this);
+            var p = this._visualChildrenPlaceholder;
+        };
+        Object.defineProperty(Label.prototype, "text", {
+            get: function () {
+                return this._text;
+            },
+            set: function (value) {
+                this._text = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Label._emptyArray = new Array();
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.Control.CONTROL_PROPCOUNT + 0, function (pi) { return Label.textProperty = pi; })
+        ], Label.prototype, "text", null);
+        Label = __decorate([
+            BABYLON.className("Label", "BABYLON")
+        ], Label);
+        return Label;
+    }(BABYLON.Control));
+    BABYLON.Label = Label;
+    var DefaultLabelRenderingTemplate = (function (_super) {
+        __extends(DefaultLabelRenderingTemplate, _super);
+        function DefaultLabelRenderingTemplate() {
+            _super.apply(this, arguments);
+        }
+        DefaultLabelRenderingTemplate.prototype.createVisualTree = function (owner, visualPlaceholder) {
+            var r = new BABYLON.Text2D("", { parent: visualPlaceholder });
+            r.createSimpleDataBinding(BABYLON.Text2D.textProperty, "text");
+            r.dataSource = owner;
+            return { root: r, contentPlaceholder: r };
+        };
+        DefaultLabelRenderingTemplate = __decorate([
+            BABYLON.registerWindowRenderingTemplate("BABYLON.Label", "Default", function () { return new DefaultLabelRenderingTemplate(); })
+        ], DefaultLabelRenderingTemplate);
+        return DefaultLabelRenderingTemplate;
+    }(BABYLON.UIElementRenderingTemplateBase));
+    BABYLON.DefaultLabelRenderingTemplate = DefaultLabelRenderingTemplate;
+})(BABYLON || (BABYLON = {}));

+ 78 - 0
canvas2D/src/GUI/babylon.gui.label.ts

@@ -0,0 +1,78 @@
+module BABYLON {
+
+    @className("Label", "BABYLON")
+    export class Label extends Control {
+
+        static textProperty: Prim2DPropInfo;
+
+        constructor(settings?: {
+
+            id              ?: string,
+            parent          ?: UIElement,
+            templateName    ?: string,
+            styleName       ?: string,
+            text            ?: string,
+            marginTop       ?: number | string,
+            marginLeft      ?: number | string,
+            marginRight     ?: number | string,
+            marginBottom    ?: number | string,
+            margin          ?: number | string,
+            marginHAlignment?: number,
+            marginVAlignment?: number,
+            marginAlignment ?: string,
+            paddingTop      ?: number | string,
+            paddingLeft     ?: number | string,
+            paddingRight    ?: number | string,
+            paddingBottom   ?: number | string,
+            padding         ?: string,
+        }) {
+            if (!settings) {
+                settings = {};
+            }
+            super(settings);
+
+            if (settings.text != null) {
+                this.text = settings.text;
+            }
+        }
+
+        protected get _position(): Vector2 {
+            return Vector2.Zero();
+        }
+
+        private static _emptyArray = new Array<UIElement>();
+        protected _getChildren(): UIElement[] {
+            return Label._emptyArray;
+        }
+
+        protected createVisualTree() {
+            super.createVisualTree();
+            let p = this._visualChildrenPlaceholder;
+
+        }
+
+        @dependencyProperty(Control.CONTROL_PROPCOUNT + 0, pi => Label.textProperty = pi)
+        public get text(): string {
+            return this._text;
+        }
+
+        public set text(value: string) {
+            this._text = value;
+        }
+
+        private _text: string;
+
+    }
+
+    @registerWindowRenderingTemplate("BABYLON.Label", "Default", () => new DefaultLabelRenderingTemplate())
+    export class DefaultLabelRenderingTemplate extends UIElementRenderingTemplateBase {
+
+        createVisualTree(owner: UIElement, visualPlaceholder: Group2D): { root: Prim2DBase; contentPlaceholder: Prim2DBase } {
+            let r = new Text2D("", { parent: visualPlaceholder });
+            r.createSimpleDataBinding(Text2D.textProperty, "text");
+            r.dataSource = <any>owner;
+
+            return { root: r, contentPlaceholder: r };
+        }
+    }
+}

+ 195 - 0
canvas2D/src/GUI/babylon.gui.window.js

@@ -0,0 +1,195 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
+    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
+    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
+    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
+    return c > 3 && r && Object.defineProperty(target, key, r), r;
+};
+var BABYLON;
+(function (BABYLON) {
+    var Window = (function (_super) {
+        __extends(Window, _super);
+        function Window(scene, settings) {
+            var _this = this;
+            if (!settings) {
+                settings = {};
+            }
+            _super.call(this, settings);
+            if (!this._UIElementVisualToBuildList) {
+                this._UIElementVisualToBuildList = new Array();
+            }
+            // Patch the owner and also the parent property through the whole tree
+            this._patchUIElement(this, null);
+            // Screen Space UI
+            if (!settings.worldPosition && !settings.worldRotation) {
+                this._canvas = Window.getScreenCanvas(scene);
+                this._isWorldSpaceCanvas = false;
+                this._left = (settings.left != null) ? settings.left : 0;
+                this._bottom = (settings.bottom != null) ? settings.bottom : 0;
+            }
+            else {
+                var w = (settings.width == null) ? 100 : settings.width;
+                var h = (settings.height == null) ? 100 : settings.height;
+                var wpos = (settings.worldPosition == null) ? BABYLON.Vector3.Zero() : settings.worldPosition;
+                var wrot = (settings.worldRotation == null) ? BABYLON.Quaternion.Identity() : settings.worldRotation;
+                this._canvas = new BABYLON.WorldSpaceCanvas2D(scene, new BABYLON.Size(w, h), { id: "GUI Canvas", cachingStrategy: BABYLON.Canvas2D.CACHESTRATEGY_DONTCACHE, worldPosition: wpos, worldRotation: wrot });
+                this._isWorldSpaceCanvas = true;
+            }
+            this._renderObserver = this._canvas.renderObservable.add(function (e, s) { return _this._canvasPreRender(); }, BABYLON.Canvas2D.RENDEROBSERVABLE_PRE);
+            this._disposeObserver = this._canvas.disposeObservable.add(function (e, s) { return _this._canvasDisposed(); });
+            this._canvas.propertyChanged.add(function (e, s) {
+                if (e.propertyName === "overPrim") {
+                    _this._overPrimChanged(e.oldValue, e.newValue);
+                }
+            });
+            this._mouseOverUIElement = null;
+        }
+        Object.defineProperty(Window.prototype, "canvas", {
+            get: function () {
+                return this._canvas;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Window.prototype, "left", {
+            get: function () {
+                return this._left;
+            },
+            set: function (value) {
+                var old = new BABYLON.Vector2(this._left, this._bottom);
+                this._left = value;
+                this.onPropertyChanged("_position", old, this._position);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Window.prototype, "bottom", {
+            get: function () {
+                return this._bottom;
+            },
+            set: function (value) {
+                var old = new BABYLON.Vector2(this._left, this._bottom);
+                this._bottom = value;
+                this.onPropertyChanged("_position", old, this._position);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Window.prototype, "position", {
+            get: function () {
+                return this._position;
+            },
+            set: function (value) {
+                this._left = value.x;
+                this._bottom = value.y;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Window.prototype, "_position", {
+            get: function () {
+                return new BABYLON.Vector2(this.left, this.bottom);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Window.prototype.createVisualTree = function () {
+            _super.prototype.createVisualTree.call(this);
+            var p = this._visualPlaceholder;
+            p.createSimpleDataBinding(BABYLON.Group2D.positionProperty, "position");
+        };
+        Window.prototype._registerVisualToBuild = function (uiel) {
+            if (uiel._isFlagSet(BABYLON.UIElement.flagVisualToBuild)) {
+                return;
+            }
+            if (!this._UIElementVisualToBuildList) {
+                this._UIElementVisualToBuildList = new Array();
+            }
+            this._UIElementVisualToBuildList.push(uiel);
+            uiel._setFlags(BABYLON.UIElement.flagVisualToBuild);
+        };
+        Window.prototype._overPrimChanged = function (oldPrim, newPrim) {
+            var curOverEl = this._mouseOverUIElement;
+            var newOverEl = null;
+            var curGroup = newPrim ? newPrim.traverseUp(function (p) { return p instanceof BABYLON.Group2D; }) : null;
+            while (curGroup) {
+                var uiel = curGroup.getExternalData("_GUIOwnerElement_");
+                if (uiel) {
+                    newOverEl = uiel;
+                    break;
+                }
+                curGroup = curGroup.parent ? curGroup.parent.traverseUp(function (p) { return p instanceof BABYLON.Group2D; }) : null;
+            }
+            if (curOverEl === newOverEl) {
+                return;
+            }
+            if (curOverEl) {
+                curOverEl.isMouseOver = false;
+            }
+            if (newOverEl) {
+                newOverEl.isMouseOver = true;
+            }
+            this._mouseOverUIElement = newOverEl;
+        };
+        Window.prototype._canvasPreRender = function () {
+            // Check if we have visual to create
+            if (this._UIElementVisualToBuildList.length > 0) {
+                // Sort the UI Element to get the highest (so lowest hierarchy depth) in the hierarchy tree first
+                var sortedElementList = this._UIElementVisualToBuildList.sort(function (a, b) { return a.hierarchyDepth - b.hierarchyDepth; });
+                for (var _i = 0, sortedElementList_1 = sortedElementList; _i < sortedElementList_1.length; _i++) {
+                    var el = sortedElementList_1[_i];
+                    el._createVisualTree();
+                }
+                this._UIElementVisualToBuildList.splice(0);
+            }
+        };
+        Window.prototype._canvasDisposed = function () {
+            this._canvas.disposeObservable.remove(this._disposeObserver);
+            this._canvas.renderObservable.remove(this._renderObserver);
+        };
+        Window.getScreenCanvas = function (scene) {
+            var canvas = BABYLON.Tools.first(Window._screenCanvasList, function (c) { return c.scene === scene; });
+            if (canvas) {
+                return canvas;
+            }
+            canvas = new BABYLON.ScreenSpaceCanvas2D(scene, { id: "GUI Canvas", cachingStrategy: BABYLON.Canvas2D.CACHESTRATEGY_DONTCACHE });
+            Window._screenCanvasList.push(canvas);
+            return canvas;
+        };
+        Window.WINDOW_PROPCOUNT = BABYLON.ContentControl.CONTENTCONTROL_PROPCOUNT + 2;
+        Window._screenCanvasList = new Array();
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.ContentControl.CONTENTCONTROL_PROPCOUNT + 0, function (pi) { return Window.leftProperty = pi; })
+        ], Window.prototype, "left", null);
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.ContentControl.CONTENTCONTROL_PROPCOUNT + 1, function (pi) { return Window.bottomProperty = pi; })
+        ], Window.prototype, "bottom", null);
+        __decorate([
+            BABYLON.dependencyProperty(BABYLON.ContentControl.CONTENTCONTROL_PROPCOUNT + 2, function (pi) { return Window.positionProperty = pi; })
+        ], Window.prototype, "position", null);
+        Window = __decorate([
+            BABYLON.className("Window", "BABYLON")
+        ], Window);
+        return Window;
+    }(BABYLON.ContentControl));
+    BABYLON.Window = Window;
+    var DefaultWindowRenderingTemplate = (function (_super) {
+        __extends(DefaultWindowRenderingTemplate, _super);
+        function DefaultWindowRenderingTemplate() {
+            _super.apply(this, arguments);
+        }
+        DefaultWindowRenderingTemplate.prototype.createVisualTree = function (owner, visualPlaceholder) {
+            var r = new BABYLON.Rectangle2D({ parent: visualPlaceholder, fill: "#808080FF" });
+            return { root: r, contentPlaceholder: r };
+        };
+        DefaultWindowRenderingTemplate = __decorate([
+            BABYLON.registerWindowRenderingTemplate("BABYLON.Window", "Default", function () { return new DefaultWindowRenderingTemplate(); })
+        ], DefaultWindowRenderingTemplate);
+        return DefaultWindowRenderingTemplate;
+    }(BABYLON.UIElementRenderingTemplateBase));
+    BABYLON.DefaultWindowRenderingTemplate = DefaultWindowRenderingTemplate;
+})(BABYLON || (BABYLON = {}));

+ 214 - 0
canvas2D/src/GUI/babylon.gui.window.ts

@@ -0,0 +1,214 @@
+module BABYLON {
+    @className("Window", "BABYLON")
+    export class Window extends ContentControl {
+        static WINDOW_PROPCOUNT = ContentControl.CONTENTCONTROL_PROPCOUNT + 2;
+
+        static leftProperty: Prim2DPropInfo;
+        static bottomProperty: Prim2DPropInfo;
+        static positionProperty: Prim2DPropInfo;
+
+        constructor(scene: Scene, settings?: {
+
+            id              ?: string,
+            templateName    ?: string,
+            styleName       ?: string,
+            content         ?: any,
+            contentAlignment?: string,
+            left            ?: number,
+            bottom          ?: number,
+            minWidth        ?: number,
+            minHeight       ?: number,
+            maxWidth        ?: number,
+            maxHeight       ?: number,
+            width           ?: number,
+            height          ?: number,
+            worldPosition   ?: Vector3,
+            worldRotation   ?: Quaternion,
+        }) {
+
+            if (!settings) {
+                settings = {};
+            }
+
+            super(settings);
+
+            if (!this._UIElementVisualToBuildList) {
+                this._UIElementVisualToBuildList = new Array<UIElement>();
+            }
+
+            // Patch the owner and also the parent property through the whole tree
+            this._patchUIElement(this, null);
+
+            // Screen Space UI
+            if (!settings.worldPosition && !settings.worldRotation) {
+                this._canvas = Window.getScreenCanvas(scene);
+                this._isWorldSpaceCanvas = false;
+                this._left = (settings.left != null) ? settings.left : 0;
+                this._bottom = (settings.bottom != null) ? settings.bottom : 0;
+            }
+
+            // WorldSpace UI
+            else {
+                let w = (settings.width == null) ? 100 : settings.width;
+                let h = (settings.height == null) ? 100 : settings.height;
+                let wpos = (settings.worldPosition == null) ? Vector3.Zero() : settings.worldPosition;
+                let wrot = (settings.worldRotation == null) ? Quaternion.Identity() : settings.worldRotation;
+                this._canvas = new WorldSpaceCanvas2D(scene, new Size(w, h), { id: "GUI Canvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE, worldPosition: wpos, worldRotation: wrot });
+                this._isWorldSpaceCanvas = true;
+            }
+
+            this._renderObserver = this._canvas.renderObservable.add((e, s) => this._canvasPreRender(), Canvas2D.RENDEROBSERVABLE_PRE);
+            this._disposeObserver = this._canvas.disposeObservable.add((e, s) => this._canvasDisposed());
+            this._canvas.propertyChanged.add((e, s) => {
+                if (e.propertyName === "overPrim") {
+                    this._overPrimChanged(e.oldValue, e.newValue);
+                }
+            });
+            this._mouseOverUIElement = null;
+        }
+
+        public get canvas(): Canvas2D {
+            return this._canvas;
+        }
+
+        @dependencyProperty(ContentControl.CONTENTCONTROL_PROPCOUNT + 0, pi => Window.leftProperty = pi)
+        public get left(): number {
+            return this._left;
+        }
+
+        public set left(value: number) {
+            let old = new Vector2(this._left, this._bottom);
+            this._left = value;
+            this.onPropertyChanged("_position", old, this._position);
+        }
+
+        @dependencyProperty(ContentControl.CONTENTCONTROL_PROPCOUNT + 1, pi => Window.bottomProperty = pi)
+        public get bottom(): number {
+            return this._bottom;
+        }
+
+        public set bottom(value: number) {
+            let old = new Vector2(this._left, this._bottom);
+            this._bottom = value;
+            this.onPropertyChanged("_position", old, this._position);
+        }
+
+        @dependencyProperty(ContentControl.CONTENTCONTROL_PROPCOUNT + 2, pi => Window.positionProperty = pi)
+        public get position(): Vector2 {
+            return this._position;
+        }
+
+        public set position(value: Vector2) {
+            this._left = value.x;
+            this._bottom = value.y;
+        }
+
+        protected get _position(): Vector2 {
+            return new Vector2(this.left, this.bottom);
+        }
+
+        protected createVisualTree() {
+            super.createVisualTree();
+            let p = this._visualPlaceholder;
+            p.createSimpleDataBinding(Group2D.positionProperty, "position");
+        }
+
+        public _registerVisualToBuild(uiel: UIElement) {
+            if (uiel._isFlagSet(UIElement.flagVisualToBuild)) {
+                return;
+            }
+
+            if (!this._UIElementVisualToBuildList) {
+                this._UIElementVisualToBuildList = new Array<UIElement>();
+            }
+
+            this._UIElementVisualToBuildList.push(uiel);
+            uiel._setFlags(UIElement.flagVisualToBuild);
+        }
+
+        private _overPrimChanged(oldPrim: Prim2DBase, newPrim: Prim2DBase) {
+            let curOverEl = this._mouseOverUIElement;
+            let newOverEl: UIElement = null;
+
+            let curGroup = newPrim ? newPrim.traverseUp(p => p instanceof Group2D) : null;
+            while (curGroup) {
+                let uiel = curGroup.getExternalData<UIElement>("_GUIOwnerElement_");
+                if (uiel) {
+                    newOverEl = uiel;
+                    break;
+                }
+                curGroup = curGroup.parent ? curGroup.parent.traverseUp(p => p instanceof Group2D) : null;
+            }
+
+            if (curOverEl === newOverEl) {
+                return;
+            }
+
+            if (curOverEl) {
+                curOverEl.isMouseOver = false;
+            }
+
+            if (newOverEl) {
+                newOverEl.isMouseOver = true;
+            }
+
+            this._mouseOverUIElement = newOverEl;
+        }
+
+        private _canvasPreRender() {
+
+            // Check if we have visual to create
+            if (this._UIElementVisualToBuildList.length > 0) {
+                // Sort the UI Element to get the highest (so lowest hierarchy depth) in the hierarchy tree first
+                let sortedElementList = this._UIElementVisualToBuildList.sort((a, b) => a.hierarchyDepth - b.hierarchyDepth);
+
+                for (let el of sortedElementList) {
+                    el._createVisualTree();
+                }
+
+                this._UIElementVisualToBuildList.splice(0);
+            }
+        }
+
+        private _canvasDisposed() {
+
+
+            this._canvas.disposeObservable.remove(this._disposeObserver);
+            this._canvas.renderObservable.remove(this._renderObserver);
+        }
+
+        private _canvas: Canvas2D;
+        private _left: number;
+        private _bottom: number;
+        private _isWorldSpaceCanvas: boolean;
+        private _renderObserver: Observer<Canvas2D>;
+        private _disposeObserver: Observer<SmartPropertyBase>;
+        private _UIElementVisualToBuildList: Array<UIElement>;
+        private _mouseOverUIElement: UIElement;
+
+        private static getScreenCanvas(scene: Scene): ScreenSpaceCanvas2D {
+            let canvas = Tools.first(Window._screenCanvasList, c => c.scene === scene);
+            if (canvas) {
+                return canvas;
+            }
+
+            canvas = new ScreenSpaceCanvas2D(scene, { id: "GUI Canvas", cachingStrategy: Canvas2D.CACHESTRATEGY_DONTCACHE });
+            Window._screenCanvasList.push(canvas);
+
+            return canvas;
+        }
+
+        private static _screenCanvasList: Array<ScreenSpaceCanvas2D> = new Array<ScreenSpaceCanvas2D>();
+    }
+
+    @registerWindowRenderingTemplate("BABYLON.Window", "Default", () => new DefaultWindowRenderingTemplate ())
+    export class DefaultWindowRenderingTemplate extends UIElementRenderingTemplateBase {
+
+        createVisualTree(owner: UIElement, visualPlaceholder: Group2D): { root: Prim2DBase; contentPlaceholder: Prim2DBase } {
+
+            let r = new Rectangle2D({ parent: visualPlaceholder, fill: "#808080FF" });
+
+            return { root: r, contentPlaceholder: r };
+        }
+    }
+}

+ 617 - 0
canvas2D/src/Tools/babylon.observable.js

@@ -0,0 +1,617 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Custom type of the propertyChanged observable
+     */
+    var PropertyChangedInfo = (function () {
+        function PropertyChangedInfo() {
+        }
+        return PropertyChangedInfo;
+    }());
+    BABYLON.PropertyChangedInfo = PropertyChangedInfo;
+    /**
+     * The purpose of this class is to provide a base implementation of the IPropertyChanged interface for the user to avoid rewriting a code needlessly.
+     * Typical use of this class is to check for equality in a property set(), then call the onPropertyChanged method if values are different after the new value is set. The protected method will notify observers of the change.
+     * Remark: onPropertyChanged detects reentrant code and acts in a way to make sure everything is fine, fast and allocation friendly (when there no reentrant code which should be 99% of the time)
+     */
+    var PropertyChangedBase = (function () {
+        function PropertyChangedBase() {
+            this._propertyChanged = null;
+        }
+        /**
+         * Protected method to call when there's a change of value in a property set
+         * @param propName the name of the concerned property
+         * @param oldValue its old value
+         * @param newValue its new value
+         * @param mask an optional observable mask
+         */
+        PropertyChangedBase.prototype.onPropertyChanged = function (propName, oldValue, newValue, mask) {
+            if (this.propertyChanged.hasObservers()) {
+                var pci = PropertyChangedBase.calling ? new PropertyChangedInfo() : PropertyChangedBase.pci;
+                pci.oldValue = oldValue;
+                pci.newValue = newValue;
+                pci.propertyName = propName;
+                try {
+                    PropertyChangedBase.calling = true;
+                    this.propertyChanged.notifyObservers(pci, mask);
+                }
+                finally {
+                    PropertyChangedBase.calling = false;
+                }
+            }
+        };
+        Object.defineProperty(PropertyChangedBase.prototype, "propertyChanged", {
+            /**
+             * An observable that is triggered when a property (using of the XXXXLevelProperty decorator) has its value changing.
+             * You can add an observer that will be triggered only for a given set of Properties using the Mask feature of the Observable and the corresponding Prim2DPropInfo.flagid value (e.g. Prim2DBase.positionProperty.flagid|Prim2DBase.rotationProperty.flagid to be notified only about position or rotation change)
+             */
+            get: function () {
+                if (!this._propertyChanged) {
+                    this._propertyChanged = new BABYLON.Observable();
+                }
+                return this._propertyChanged;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        PropertyChangedBase.pci = new PropertyChangedInfo();
+        PropertyChangedBase.calling = false;
+        return PropertyChangedBase;
+    }());
+    BABYLON.PropertyChangedBase = PropertyChangedBase;
+    /**
+     * Class for the ObservableArray.onArrayChanged observable
+     */
+    var ArrayChanged = (function () {
+        function ArrayChanged() {
+            this.action = 0;
+            this.newItems = new Array();
+            this.removedItems = new Array();
+            this.changedItems = new Array();
+            this.newStartingIndex = -1;
+            this.removedStartingIndex = -1;
+        }
+        Object.defineProperty(ArrayChanged, "clearAction", {
+            /**
+             * The content of the array was totally cleared
+             */
+            get: function () {
+                return ArrayChanged._clearAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArrayChanged, "newItemsAction", {
+            /**
+             * A new item was added, the newItems field contains the key/value pairs
+             */
+            get: function () {
+                return ArrayChanged._newItemsAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArrayChanged, "removedItemsAction", {
+            /**
+             * An existing item was removed, the removedKey field contains its key
+             */
+            get: function () {
+                return ArrayChanged._removedItemsAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArrayChanged, "changedItemAction", {
+            /**
+             * One or many items in the array were changed, the
+             */
+            get: function () {
+                return ArrayChanged._changedItemAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArrayChanged, "replacedArrayAction", {
+            /**
+             * The array's content was totally changed
+             * Depending on the method that used this mode the ChangedArray object may contains more information
+             */
+            get: function () {
+                return ArrayChanged._replacedArrayAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(ArrayChanged, "lengthChangedAction", {
+            /**
+             * The length of the array changed
+             */
+            get: function () {
+                return ArrayChanged._lengthChangedAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ArrayChanged.prototype.clear = function () {
+            this.action = 0;
+            this.newItems.splice(0);
+            this.removedItems.splice(0);
+            this.changedItems.splice(0);
+            this.removedStartingIndex = this.removedStartingIndex = this.changedStartingIndex = 0;
+        };
+        ArrayChanged._clearAction = 0x1;
+        ArrayChanged._newItemsAction = 0x2;
+        ArrayChanged._removedItemsAction = 0x4;
+        ArrayChanged._replacedArrayAction = 0x8;
+        ArrayChanged._lengthChangedAction = 0x10;
+        ArrayChanged._changedItemAction = 0x20;
+        return ArrayChanged;
+    }());
+    BABYLON.ArrayChanged = ArrayChanged;
+    var OAWatchedObjectChangedInfo = (function () {
+        function OAWatchedObjectChangedInfo() {
+        }
+        return OAWatchedObjectChangedInfo;
+    }());
+    BABYLON.OAWatchedObjectChangedInfo = OAWatchedObjectChangedInfo;
+    /**
+     * This class mimics the Javascript Array and TypeScript Array<T> classes, adding new features concerning the Observable pattern.
+     *
+     */
+    var ObservableArray = (function (_super) {
+        __extends(ObservableArray, _super);
+        /**
+         * Create an Observable Array.
+         * @param watchObjectsPropertyChange
+         * @param array and optional array that will be encapsulated by this ObservableArray instance. That's right, it's NOT a copy!
+         */
+        function ObservableArray(watchObjectsPropertyChange, array) {
+            _super.call(this);
+            this.dci = new ArrayChanged();
+            this._callingArrayChanged = false;
+            this._array = (array != null) ? array : new Array();
+            this.dci = new ArrayChanged();
+            this._callingArrayChanged = false;
+            this._arrayChanged = null;
+            this._callingWatchedObjectChanged = false;
+            this._watchObjectsPropertyChange = watchObjectsPropertyChange;
+            this._watchedObjectList = this._watchObjectsPropertyChange ? new BABYLON.StringDictionary() : null;
+            this._woci = new OAWatchedObjectChangedInfo();
+        }
+        Object.defineProperty(ObservableArray.prototype, "length", {
+            /**
+              * Gets or sets the length of the array. This is a number one higher than the highest element defined in an array.
+              */
+            get: function () {
+                return this._array.length;
+            },
+            set: function (value) {
+                if (value === this._array.length) {
+                    return;
+                }
+                var oldLength = this._array.length;
+                this._array.length = value;
+                this.onPropertyChanged("length", oldLength, this._array.length);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ObservableArray.prototype.getAt = function (index) {
+            return this._array[index];
+        };
+        ObservableArray.prototype.setAt = function (index, value) {
+            if (index < 0) {
+                return false;
+            }
+            var insertion = (index >= this._array.length) || this._array[index] === undefined;
+            var oldLength = 0;
+            if (insertion) {
+                oldLength = this._array.length;
+            }
+            else if (this._watchObjectsPropertyChange) {
+                this._removeWatchedElement(this._array[index]);
+            }
+            this._array[index] = value;
+            if (this._watchObjectsPropertyChange) {
+                this._addWatchedElement(value);
+            }
+            if (insertion) {
+                this.onPropertyChanged("length", oldLength, this._array.length);
+            }
+            var ac = this.getArrayChangedObject();
+            if (ac) {
+                ac.action = insertion ? ArrayChanged.newItemsAction : ArrayChanged.changedItemAction;
+                if (insertion) {
+                    ac.newItems.splice(0, ac.newItems.length, { index: index, value: value });
+                    ac.newStartingIndex = index;
+                    ac.changedItems.splice(0);
+                }
+                else {
+                    ac.newItems.splice(0);
+                    ac.changedStartingIndex = index;
+                    ac.changedItems.splice(0, ac.changedItems.length, { index: index, value: value });
+                }
+                ac.removedItems.splice(0);
+                ac.removedStartingIndex = -1;
+                this.callArrayChanged(ac);
+            }
+        };
+        /**
+          * Returns a string representation of an array.
+          */
+        ObservableArray.prototype.toString = function () {
+            return this._array.toString();
+        };
+        ObservableArray.prototype.toLocaleString = function () {
+            return this._array.toLocaleString();
+        };
+        /**
+          * Appends new elements to an array, and returns the new length of the array.
+          * @param items New elements of the Array.
+          */
+        ObservableArray.prototype.push = function () {
+            var items = [];
+            for (var _i = 0; _i < arguments.length; _i++) {
+                items[_i - 0] = arguments[_i];
+            }
+            var oldLength = this._array.length;
+            var n = (_a = this._array).push.apply(_a, items);
+            if (this._watchObjectsPropertyChange) {
+                this._addWatchedElement.apply(this, items);
+            }
+            this.onPropertyChanged("length", oldLength, this._array.length);
+            var ac = this.getArrayChangedObject();
+            if (ac) {
+                ac.action = ArrayChanged.newItemsAction;
+                ac.newStartingIndex = oldLength;
+                this.feedNotifArray.apply(this, [ac.newItems, oldLength].concat(items));
+                this.callArrayChanged(ac);
+            }
+            return n;
+            var _a;
+        };
+        /**
+          * Removes the last element from an array and returns it.
+          */
+        ObservableArray.prototype.pop = function () {
+            var firstRemove = this._array.length - 1;
+            var res = this._array.pop();
+            if (res && this._watchObjectsPropertyChange) {
+                this._removeWatchedElement(res);
+            }
+            if (firstRemove !== -1) {
+                this.onPropertyChanged("length", this._array.length + 1, this._array.length);
+                var ac = this.getArrayChangedObject();
+                if (ac) {
+                    ac.action = ArrayChanged.removedItemsAction;
+                    ac.removedStartingIndex = firstRemove;
+                    this.feedNotifArray(ac.removedItems, firstRemove, res);
+                }
+            }
+            return res;
+        };
+        /**
+          * Combines two or more arrays.
+          * @param items Additional items to add to the end of array1.
+          */
+        ObservableArray.prototype.concat = function () {
+            var items = [];
+            for (var _i = 0; _i < arguments.length; _i++) {
+                items[_i - 0] = arguments[_i];
+            }
+            return new ObservableArray(this._watchObjectsPropertyChange, (_a = this._array).concat.apply(_a, items));
+            var _a;
+        };
+        /**
+          * Adds all the elements of an array separated by the specified separator string.
+          * @param separator A string used to separate one element of an array from the next in the resulting String. If omitted, the array elements are separated with a comma.
+          */
+        ObservableArray.prototype.join = function (separator) {
+            return this._array.join(separator);
+        };
+        /**
+          * Reverses the elements in an Array.
+         * The arrayChanged action is
+          */
+        ObservableArray.prototype.reverse = function () {
+            var res = this._array.reverse();
+            var ac = this.getArrayChangedObject();
+            ac.action = ArrayChanged.replacedArrayAction;
+            return res;
+        };
+        /**
+          * Removes the first element from an array and returns it, shift all subsequents element one element before.
+         * The ArrayChange action is replacedArrayAction, the whole array changes and must be reevaluate as such, the removed element is in removedItems.
+         *
+          */
+        ObservableArray.prototype.shift = function () {
+            var oldLength = this._array.length;
+            var res = this._array.shift();
+            if (this._watchedObjectChanged && res != null) {
+                this._removeWatchedElement(res);
+            }
+            if (oldLength !== 0) {
+                this.onPropertyChanged("length", oldLength, this._array.length);
+                var ac = this.getArrayChangedObject();
+                if (ac) {
+                    ac.action = ArrayChanged.replacedArrayAction;
+                    ac.removedItems.splice(0, ac.removedItems.length, { index: 0, value: res });
+                    ac.newItems.splice(0);
+                    ac.changedItems.splice(0);
+                    ac.removedStartingIndex = 0;
+                    this.callArrayChanged(ac);
+                }
+            }
+            return res;
+        };
+        /**
+          * Returns a section of an array.
+          * @param start The beginning of the specified portion of the array.
+          * @param end The end of the specified portion of the array.
+          */
+        ObservableArray.prototype.slice = function (start, end) {
+            return new ObservableArray(this._watchObjectsPropertyChange, this._array.slice(start, end));
+        };
+        /**
+          * Sorts an array.
+          * @param compareFn The name of the function used to determine the order of the elements. If omitted, the elements are sorted in ascending, ASCII character order.
+         * On the contrary of the Javascript Array's implementation, this method returns nothing
+          */
+        ObservableArray.prototype.sort = function (compareFn) {
+            var oldLength = this._array.length;
+            this._array.sort(compareFn);
+            if (oldLength !== 0) {
+                var ac = this.getArrayChangedObject();
+                if (ac) {
+                    ac.clear();
+                    ac.action = ArrayChanged.replacedArrayAction;
+                    this.callArrayChanged(ac);
+                }
+            }
+        };
+        /**
+          * Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
+          * @param start The zero-based location in the array from which to start removing elements.
+          * @param deleteCount The number of elements to remove.
+          * @param items Elements to insert into the array in place of the deleted elements.
+          */
+        ObservableArray.prototype.splice = function (start, deleteCount) {
+            var items = [];
+            for (var _i = 2; _i < arguments.length; _i++) {
+                items[_i - 2] = arguments[_i];
+            }
+            var oldLength = this._array.length;
+            if (this._watchObjectsPropertyChange) {
+                for (var i = start; i < start + deleteCount; i++) {
+                    var val = this._array[i];
+                    if (this._watchObjectsPropertyChange && val != null) {
+                        this._removeWatchedElement(val);
+                    }
+                }
+            }
+            var res = (_a = this._array).splice.apply(_a, [start, deleteCount].concat(items));
+            if (this._watchObjectsPropertyChange) {
+                this._addWatchedElement.apply(this, items);
+            }
+            if (oldLength !== this._array.length) {
+                this.onPropertyChanged("length", oldLength, this._array.length);
+            }
+            var ac = this.getArrayChangedObject();
+            if (ac) {
+                ac.clear();
+                ac.action = ArrayChanged.replacedArrayAction;
+                this.callArrayChanged(ac);
+            }
+            return res;
+            var _a;
+        };
+        /**
+          * Inserts new elements at the start of an array.
+          * @param items  Elements to insert at the start of the Array.
+          * The ChangedArray action is replacedArrayAction, newItems contains the list of the added items
+          */
+        ObservableArray.prototype.unshift = function () {
+            var items = [];
+            for (var _i = 0; _i < arguments.length; _i++) {
+                items[_i - 0] = arguments[_i];
+            }
+            var oldLength = this._array.length;
+            var res = (_a = this._array).unshift.apply(_a, items);
+            if (this._watchObjectsPropertyChange) {
+                this._addWatchedElement.apply(this, items);
+            }
+            this.onPropertyChanged("length", oldLength, this._array.length);
+            var ac = this.getArrayChangedObject();
+            if (ac) {
+                ac.clear();
+                ac.action = ArrayChanged.replacedArrayAction;
+                ac.newStartingIndex = 0,
+                    this.feedNotifArray.apply(this, [ac.newItems, 0].concat(items));
+                this.callArrayChanged(ac);
+            }
+            return res;
+            var _a;
+        };
+        /**
+          * Returns the index of the first occurrence of a value in an array.
+          * @param searchElement The value to locate in the array.
+          * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
+          */
+        ObservableArray.prototype.indexOf = function (searchElement, fromIndex) {
+            return this._array.indexOf(searchElement, fromIndex);
+        };
+        /**
+          * Returns the index of the last occurrence of a specified value in an array.
+          * @param searchElement The value to locate in the array.
+          * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at the last index in the array.
+          */
+        ObservableArray.prototype.lastIndexOf = function (searchElement, fromIndex) {
+            return this._array.lastIndexOf(searchElement, fromIndex);
+        };
+        /**
+          * Determines whether all the members of an array satisfy the specified test.
+          * @param callbackfn A function that accepts up to three arguments. The every method calls the callbackfn function for each element in array1 until the callbackfn returns false, or until the end of the array.
+          * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        ObservableArray.prototype.every = function (callbackfn, thisArg) {
+            return this._array.every(callbackfn, thisArg);
+        };
+        /**
+          * Determines whether the specified callback function returns true for any element of an array.
+          * @param callbackfn A function that accepts up to three arguments. The some method calls the callbackfn function for each element in array1 until the callbackfn returns true, or until the end of the array.
+          * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        ObservableArray.prototype.some = function (callbackfn, thisArg) {
+            return this._array.some(callbackfn, thisArg);
+        };
+        /**
+          * Performs the specified action for each element in an array.
+          * @param callbackfn  A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array.
+          * @param thisArg  An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        ObservableArray.prototype.forEach = function (callbackfn, thisArg) {
+            return this._array.forEach(callbackfn, thisArg);
+        };
+        /**
+          * Calls a defined callback function on each element of an array, and returns an array that contains the results.
+          * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array.
+          * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        ObservableArray.prototype.map = function (callbackfn, thisArg) {
+            return this._array.map(callbackfn, thisArg);
+        };
+        /**
+          * Returns the elements of an array that meet the condition specified in a callback function.
+          * @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array.
+          * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        ObservableArray.prototype.filter = function (callbackfn, thisArg) {
+            return this._array.filter(callbackfn, thisArg);
+        };
+        /**
+          * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
+          * @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
+          * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
+          */
+        ObservableArray.prototype.reduce = function (callbackfn, initialValue) {
+            return this._array.reduce(callbackfn);
+        };
+        /**
+          * Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
+          * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array.
+          * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
+          */
+        ObservableArray.prototype.reduceRight = function (callbackfn, initialValue) {
+            return this._array.reduceRight(callbackfn);
+        };
+        Object.defineProperty(ObservableArray.prototype, "arrayChanged", {
+            get: function () {
+                if (!this._arrayChanged) {
+                    this._arrayChanged = new BABYLON.Observable();
+                }
+                return this._arrayChanged;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ObservableArray.prototype.getArrayChangedObject = function () {
+            if (this._arrayChanged && this._arrayChanged.hasObservers()) {
+                var ac = this._callingArrayChanged ? new ArrayChanged() : this.dci;
+                return ac;
+            }
+            return null;
+        };
+        ObservableArray.prototype.feedNotifArray = function (array, startindIndex) {
+            var items = [];
+            for (var _i = 2; _i < arguments.length; _i++) {
+                items[_i - 2] = arguments[_i];
+            }
+            array.splice(0);
+            for (var i = 0; i < items.length; i++) {
+                var value = this._array[i + startindIndex];
+                if (value !== undefined) {
+                    array.push({ index: i + startindIndex, value: value });
+                }
+            }
+        };
+        ObservableArray.prototype.callArrayChanged = function (ac) {
+            try {
+                this._callingArrayChanged = true;
+                this.arrayChanged.notifyObservers(ac, ac.action);
+            }
+            finally {
+                this._callingArrayChanged = false;
+            }
+        };
+        Object.defineProperty(ObservableArray.prototype, "watchedObjectChanged", {
+            get: function () {
+                if (!this._watchedObjectChanged) {
+                    this._watchedObjectChanged = new BABYLON.Observable();
+                }
+                return this._watchedObjectChanged;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ObservableArray.prototype._addWatchedElement = function () {
+            var _this = this;
+            var items = [];
+            for (var _i = 0; _i < arguments.length; _i++) {
+                items[_i - 0] = arguments[_i];
+            }
+            var _loop_1 = function(curItem) {
+                if (curItem["propertyChanged"]) {
+                    var key_1 = curItem["__ObsArrayObjID__"];
+                    // The object may already be part of another ObsArray, so there already be a valid ID
+                    if (!key_1) {
+                        key_1 = BABYLON.Tools.RandomId();
+                        curItem["__ObsArrayObjID__"] = key_1;
+                    }
+                    this_1._watchedObjectList.add(key_1, curItem.propertyChanged.add(function (e, d) {
+                        _this.onWatchedObjectChanged(key_1, curItem, e);
+                    }));
+                }
+            };
+            var this_1 = this;
+            for (var _a = 0, items_1 = items; _a < items_1.length; _a++) {
+                var curItem = items_1[_a];
+                _loop_1(curItem);
+            }
+        };
+        ObservableArray.prototype._removeWatchedElement = function () {
+            var items = [];
+            for (var _i = 0; _i < arguments.length; _i++) {
+                items[_i - 0] = arguments[_i];
+            }
+            for (var _a = 0, items_2 = items; _a < items_2.length; _a++) {
+                var curItem = items_2[_a];
+                var key = curItem["__ObsArrayObjID__"];
+                if (key != null) {
+                    var observer = this._watchedObjectList.getAndRemove(key);
+                    curItem.propertyChanged.remove(observer);
+                }
+            }
+        };
+        ObservableArray.prototype.onWatchedObjectChanged = function (key, object, propChanged) {
+            if (this._watchedObjectChanged && this._watchedObjectChanged.hasObservers()) {
+                var woci = this._callingWatchedObjectChanged ? new OAWatchedObjectChangedInfo() : this._woci;
+                woci.object = object;
+                woci.propertyChanged = propChanged;
+                try {
+                    this._callingWatchedObjectChanged = true;
+                    this.watchedObjectChanged.notifyObservers(woci);
+                }
+                finally {
+                    this._callingWatchedObjectChanged = false;
+                }
+            }
+        };
+        return ObservableArray;
+    }(PropertyChangedBase));
+    BABYLON.ObservableArray = ObservableArray;
+})(BABYLON || (BABYLON = {}));

+ 671 - 0
canvas2D/src/Tools/babylon.observable.ts

@@ -0,0 +1,671 @@
+module BABYLON {
+
+    /**
+     * Custom type of the propertyChanged observable
+     */
+    export class PropertyChangedInfo {
+        /**
+         * Previous value of the property
+         */
+        oldValue: any;
+        /**
+         * New value of the property
+         */
+        newValue: any;
+
+        /**
+         * Name of the property that changed its value
+         */
+        propertyName: string;
+    }
+
+    /**
+     * Property Changed interface
+     */
+    export interface IPropertyChanged {
+        /**
+         * PropertyChanged observable
+         */
+        propertyChanged: Observable<PropertyChangedInfo>;
+    }
+
+    /**
+     * The purpose of this class is to provide a base implementation of the IPropertyChanged interface for the user to avoid rewriting a code needlessly.
+     * Typical use of this class is to check for equality in a property set(), then call the onPropertyChanged method if values are different after the new value is set. The protected method will notify observers of the change.
+     * Remark: onPropertyChanged detects reentrant code and acts in a way to make sure everything is fine, fast and allocation friendly (when there no reentrant code which should be 99% of the time)
+     */
+    export abstract class PropertyChangedBase implements IPropertyChanged {
+
+        /**
+         * Protected method to call when there's a change of value in a property set
+         * @param propName the name of the concerned property
+         * @param oldValue its old value
+         * @param newValue its new value
+         * @param mask an optional observable mask
+         */
+        protected onPropertyChanged<T>(propName: string, oldValue: T, newValue: T, mask?: number) {
+            if (this.propertyChanged.hasObservers()) {
+
+                let pci = PropertyChangedBase.calling ? new PropertyChangedInfo() : PropertyChangedBase.pci;
+
+                pci.oldValue = oldValue;
+                pci.newValue = newValue;
+                pci.propertyName = propName;
+
+                try {
+                    PropertyChangedBase.calling = true;
+                    this.propertyChanged.notifyObservers(pci, mask);
+                } finally {
+                    PropertyChangedBase.calling = false;
+                }
+            }
+        }
+
+        /**
+         * An observable that is triggered when a property (using of the XXXXLevelProperty decorator) has its value changing.
+         * You can add an observer that will be triggered only for a given set of Properties using the Mask feature of the Observable and the corresponding Prim2DPropInfo.flagid value (e.g. Prim2DBase.positionProperty.flagid|Prim2DBase.rotationProperty.flagid to be notified only about position or rotation change)
+         */
+        public get propertyChanged(): Observable<PropertyChangedInfo> {
+            if (!this._propertyChanged) {
+                this._propertyChanged = new Observable<PropertyChangedInfo>();
+            }
+            return this._propertyChanged;
+        }
+
+        public _propertyChanged: Observable<PropertyChangedInfo> = null;
+        private static pci = new PropertyChangedInfo();
+        private static calling: boolean = false;
+    }
+
+    /**
+     * Class for the ObservableArray.onArrayChanged observable
+     */
+    export class ArrayChanged<T> {
+        constructor() {
+            this.action = 0;
+            this.newItems = new Array<{index: number, value: T }>();
+            this.removedItems = new Array<{ index: number, value: T }>();
+            this.changedItems = new Array<{ index: number, value: T }>();
+            this.newStartingIndex = -1;
+            this.removedStartingIndex = -1;
+        }
+
+        /**
+         * Contain the action that were made on the ObservableArray, it's one of the ArrayChanged.xxxAction members.
+         * Note the action's value can be used in the "mask" field of the Observable to only be notified about given action(s)
+         */
+        public action: number;
+
+        /**
+         * Only valid if the action is newItemsAction
+         */
+        public newItems: { index: number, value: T }[];
+
+        /**
+         * Only valid if the action is removedItemsAction
+         */
+        public removedItems: { index: number, value: T }[];
+
+        /**
+         * Only valid if the action is changedItemAction
+         */
+        public changedItems: { index: number, value: T }[];
+
+        /**
+         * Get the index of the first item inserted
+         */
+        public newStartingIndex: number;
+
+        /**
+         * Get the index of the first item removed
+         */
+        public removedStartingIndex: number;
+
+        /**
+         * Get the index of the first changed item
+         */
+        public changedStartingIndex: number;
+
+        /**
+         * The content of the array was totally cleared
+         */
+        public static get clearAction() {
+            return ArrayChanged._clearAction;
+        }
+
+        /**
+         * A new item was added, the newItems field contains the key/value pairs
+         */
+        public static get newItemsAction() {
+            return ArrayChanged._newItemsAction;
+        }
+
+        /**
+         * An existing item was removed, the removedKey field contains its key
+         */
+        public static get removedItemsAction() {
+            return ArrayChanged._removedItemsAction;
+        }
+
+        /**
+         * One or many items in the array were changed, the 
+         */
+        public static get changedItemAction() {
+            return ArrayChanged._changedItemAction;
+        }
+
+        /**
+         * The array's content was totally changed
+         * Depending on the method that used this mode the ChangedArray object may contains more information
+         */
+        public static get replacedArrayAction() {
+            return ArrayChanged._replacedArrayAction;
+        }
+
+        /**
+         * The length of the array changed
+         */
+        public static get lengthChangedAction() {
+            return ArrayChanged._lengthChangedAction;
+        }
+
+        private static _clearAction            = 0x1;
+        private static _newItemsAction         = 0x2;
+        private static _removedItemsAction     = 0x4;
+        private static _replacedArrayAction    = 0x8;
+        private static _lengthChangedAction    = 0x10;
+        private static _changedItemAction      = 0x20;
+
+        clear() {
+            this.action = 0;
+            this.newItems.splice(0);
+            this.removedItems.splice(0);
+            this.changedItems.splice(0);
+            this.removedStartingIndex = this.removedStartingIndex = this.changedStartingIndex = 0;
+        }
+    }
+
+    export class OAWatchedObjectChangedInfo<T> {
+        object: T;
+        propertyChanged: PropertyChangedInfo;
+    }
+
+    /**
+     * This class mimics the Javascript Array and TypeScript Array<T> classes, adding new features concerning the Observable pattern.
+     * 
+     */
+    export class ObservableArray<T> extends PropertyChangedBase {
+        /**
+         * Create an Observable Array.
+         * @param watchObjectsPropertyChange
+         * @param array and optional array that will be encapsulated by this ObservableArray instance. That's right, it's NOT a copy!
+         */
+        constructor(watchObjectsPropertyChange: boolean, array?: Array<T>) {
+            super();
+            this._array = (array!=null) ? array : new Array<T>();
+            this.dci = new ArrayChanged<T>();
+            this._callingArrayChanged = false;
+            this._arrayChanged = null;
+
+            this._callingWatchedObjectChanged = false;
+            this._watchObjectsPropertyChange = watchObjectsPropertyChange;
+            this._watchedObjectList = this._watchObjectsPropertyChange ? new StringDictionary<Observer<PropertyChangedInfo>>() : null;
+            this._woci = new OAWatchedObjectChangedInfo<T>();
+        }
+
+        /**
+          * Gets or sets the length of the array. This is a number one higher than the highest element defined in an array.
+          */
+        get length(): number {
+            return this._array.length;
+        }
+
+        set length(value: number) {
+            if (value === this._array.length) {
+                return;
+            }
+
+            let oldLength = this._array.length;
+            this._array.length = value;
+
+            this.onPropertyChanged("length", oldLength, this._array.length);
+        }
+
+        getAt(index: number): T {
+            return this._array[index];
+        }
+
+        setAt(index: number, value: T): boolean {
+            if (index < 0) {
+                return false;
+            }
+
+            let insertion = (index >= this._array.length) || this._array[index] === undefined;
+            let oldLength = 0;
+            if (insertion) {
+                oldLength = this._array.length;
+            } else if (this._watchObjectsPropertyChange) {
+                this._removeWatchedElement(this._array[index]);
+            }
+
+            this._array[index] = value;
+
+            if (this._watchObjectsPropertyChange) {
+                this._addWatchedElement(value);
+            }
+
+            if (insertion) {
+                this.onPropertyChanged("length", oldLength, this._array.length);
+            }
+
+            let ac = this.getArrayChangedObject();
+            if (ac) {
+                ac.action = insertion ? ArrayChanged.newItemsAction : ArrayChanged.changedItemAction;
+                if (insertion) {
+                    ac.newItems.splice(0, ac.newItems.length, { index: index, value: value });
+                    ac.newStartingIndex = index;
+                    ac.changedItems.splice(0);
+                } else {
+                    ac.newItems.splice(0);
+                    ac.changedStartingIndex = index;
+                    ac.changedItems.splice(0, ac.changedItems.length, { index: index, value: value });
+                }
+                ac.removedItems.splice(0);
+                ac.removedStartingIndex = -1;
+                this.callArrayChanged(ac);
+            }
+        }
+
+        /**
+          * Returns a string representation of an array.
+          */
+        toString(): string {
+            return this._array.toString();
+        }
+
+        toLocaleString(): string {
+            return this._array.toLocaleString();
+        }
+
+        /**
+          * Appends new elements to an array, and returns the new length of the array.
+          * @param items New elements of the Array.
+          */
+        push(...items: T[]): number {
+            let oldLength = this._array.length;
+            let n = this._array.push(...items);
+
+            if (this._watchObjectsPropertyChange) {
+                this._addWatchedElement(...items);
+            }
+
+            this.onPropertyChanged("length", oldLength, this._array.length);
+            let ac = this.getArrayChangedObject();
+            if (ac) {
+                ac.action = ArrayChanged.newItemsAction;
+                ac.newStartingIndex = oldLength;
+                this.feedNotifArray(ac.newItems, oldLength, ...items);
+                this.callArrayChanged(ac);
+            }
+
+            return n;
+        }
+
+        /**
+          * Removes the last element from an array and returns it.
+          */
+        pop(): T {
+            let firstRemove = this._array.length - 1;
+            let res = this._array.pop();
+
+            if (res && this._watchObjectsPropertyChange) {
+                this._removeWatchedElement(res);
+            }
+
+            if (firstRemove !== -1) {
+                this.onPropertyChanged("length", this._array.length + 1, this._array.length);
+
+                let ac = this.getArrayChangedObject();
+                if (ac) {
+                    ac.action = ArrayChanged.removedItemsAction;
+                    ac.removedStartingIndex = firstRemove;
+                    this.feedNotifArray(ac.removedItems, firstRemove, res);
+                }
+            }
+
+            return res;
+        }
+
+        /**
+          * Combines two or more arrays.
+          * @param items Additional items to add to the end of array1.
+          */
+        concat(...items: T[]): ObservableArray<T> {
+            return new ObservableArray<T>(this._watchObjectsPropertyChange, this._array.concat(...items));
+        }
+
+        /**
+          * Adds all the elements of an array separated by the specified separator string.
+          * @param separator A string used to separate one element of an array from the next in the resulting String. If omitted, the array elements are separated with a comma.
+          */
+        join(separator?: string): string {
+            return this._array.join(separator);
+        }
+
+        /**
+          * Reverses the elements in an Array.
+         * The arrayChanged action is 
+          */
+        reverse(): T[] {
+            let res = this._array.reverse();
+
+            let ac = this.getArrayChangedObject();
+            ac.action = ArrayChanged.replacedArrayAction;
+
+            return res;
+        }
+
+        /**
+          * Removes the first element from an array and returns it, shift all subsequents element one element before.
+         * The ArrayChange action is replacedArrayAction, the whole array changes and must be reevaluate as such, the removed element is in removedItems.
+         * 
+          */
+        shift(): T {
+            let oldLength = this._array.length;
+            let res = this._array.shift();
+
+            if (this._watchedObjectChanged && res!=null) {
+                this._removeWatchedElement(res);
+            }
+
+            if (oldLength !== 0) {
+                this.onPropertyChanged("length", oldLength, this._array.length);
+
+                let ac = this.getArrayChangedObject();
+                if (ac) {
+                    ac.action = ArrayChanged.replacedArrayAction;
+                    ac.removedItems.splice(0, ac.removedItems.length, { index: 0, value: res });
+                    ac.newItems.splice(0);
+                    ac.changedItems.splice(0);
+                    ac.removedStartingIndex = 0;
+                    this.callArrayChanged(ac);
+                }
+            }
+
+            return res;
+        }
+
+        /** 
+          * Returns a section of an array.
+          * @param start The beginning of the specified portion of the array.
+          * @param end The end of the specified portion of the array.
+          */
+        slice(start?: number, end?: number): ObservableArray<T> {
+            return new ObservableArray<T>(this._watchObjectsPropertyChange, this._array.slice(start, end));
+        }
+
+        /**
+          * Sorts an array.
+          * @param compareFn The name of the function used to determine the order of the elements. If omitted, the elements are sorted in ascending, ASCII character order.
+         * On the contrary of the Javascript Array's implementation, this method returns nothing
+          */
+        sort(compareFn?: (a: T, b: T) => number): void {
+            let oldLength = this._array.length;
+
+            this._array.sort(compareFn);
+
+            if (oldLength !== 0) {
+                let ac = this.getArrayChangedObject();
+                if (ac) {
+                    ac.clear();
+                    ac.action = ArrayChanged.replacedArrayAction;
+                    this.callArrayChanged(ac);
+                }
+            }
+        }
+
+        /**
+          * Removes elements from an array and, if necessary, inserts new elements in their place, returning the deleted elements.
+          * @param start The zero-based location in the array from which to start removing elements.
+          * @param deleteCount The number of elements to remove.
+          * @param items Elements to insert into the array in place of the deleted elements.
+          */
+        splice(start: number, deleteCount: number, ...items: T[]): T[] {
+            let oldLength = this._array.length;
+
+            if (this._watchObjectsPropertyChange) {
+                for (let i = start; i < start+deleteCount; i++) {
+                    let val = this._array[i];
+                    if (this._watchObjectsPropertyChange && val != null) {
+                        this._removeWatchedElement(val);
+                    }
+                }
+            }
+
+            let res = this._array.splice(start, deleteCount, ...items);
+
+            if (this._watchObjectsPropertyChange) {
+                this._addWatchedElement(...items);
+            }
+
+            if (oldLength !== this._array.length) {
+                this.onPropertyChanged("length", oldLength, this._array.length);
+            }
+
+            let ac = this.getArrayChangedObject();
+            if (ac) {
+                ac.clear();
+                ac.action = ArrayChanged.replacedArrayAction;
+                this.callArrayChanged(ac);
+            }
+
+            return res;
+        }
+
+        /**
+          * Inserts new elements at the start of an array.
+          * @param items  Elements to insert at the start of the Array.
+          * The ChangedArray action is replacedArrayAction, newItems contains the list of the added items
+          */
+        unshift(...items: T[]): number {
+            let oldLength = this._array.length;
+            
+            let res = this._array.unshift(...items);
+
+            if (this._watchObjectsPropertyChange) {
+                this._addWatchedElement(...items);
+            }
+
+            this.onPropertyChanged("length", oldLength, this._array.length);
+            let ac = this.getArrayChangedObject();
+            if (ac) {
+                ac.clear();
+                ac.action = ArrayChanged.replacedArrayAction;
+                ac.newStartingIndex = 0,
+                this.feedNotifArray(ac.newItems, 0, ...items);
+                this.callArrayChanged(ac);
+            }
+
+            return res;
+        }
+
+        /**
+          * Returns the index of the first occurrence of a value in an array.
+          * @param searchElement The value to locate in the array.
+          * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at index 0.
+          */
+        indexOf(searchElement: T, fromIndex?: number): number {
+            return this._array.indexOf(searchElement, fromIndex);
+        }
+
+        /**
+          * Returns the index of the last occurrence of a specified value in an array.
+          * @param searchElement The value to locate in the array.
+          * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the search starts at the last index in the array.
+          */
+        lastIndexOf(searchElement: T, fromIndex?: number): number {
+            return this._array.lastIndexOf(searchElement, fromIndex);
+        }
+
+        /**
+          * Determines whether all the members of an array satisfy the specified test.
+          * @param callbackfn A function that accepts up to three arguments. The every method calls the callbackfn function for each element in array1 until the callbackfn returns false, or until the end of the array.
+          * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        every(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean {
+            return this._array.every(callbackfn, thisArg);
+        }
+
+        /**
+          * Determines whether the specified callback function returns true for any element of an array.
+          * @param callbackfn A function that accepts up to three arguments. The some method calls the callbackfn function for each element in array1 until the callbackfn returns true, or until the end of the array.
+          * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        some(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean {
+            return this._array.some(callbackfn, thisArg);
+        }
+
+        /**
+          * Performs the specified action for each element in an array.
+          * @param callbackfn  A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array. 
+          * @param thisArg  An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void {
+            return this._array.forEach(callbackfn, thisArg);
+        }
+
+        /**
+          * Calls a defined callback function on each element of an array, and returns an array that contains the results.
+          * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array. 
+          * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[] {
+            return this._array.map(callbackfn, thisArg);
+        }
+
+        /**
+          * Returns the elements of an array that meet the condition specified in a callback function. 
+          * @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array. 
+          * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
+          */
+        filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[] {
+            return this._array.filter(callbackfn, thisArg);
+        }
+
+        /**
+          * Calls the specified callback function for all the elements in an array. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
+          * @param callbackfn A function that accepts up to four arguments. The reduce method calls the callbackfn function one time for each element in the array.
+          * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
+          */
+        reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T {
+            return this._array.reduce(callbackfn);
+        }
+
+        /** 
+          * Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function.
+          * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array. 
+          * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value.
+          */
+        reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T {
+            return this._array.reduceRight(callbackfn);
+        }
+
+        get arrayChanged(): Observable<ArrayChanged<T>> {
+            if (!this._arrayChanged) {
+                this._arrayChanged = new Observable<ArrayChanged<T>>();
+            }
+            return this._arrayChanged;
+        }
+
+        protected getArrayChangedObject(): ArrayChanged<T> {
+            if (this._arrayChanged && this._arrayChanged.hasObservers()) {
+                let ac = this._callingArrayChanged ? new ArrayChanged<T>() : this.dci;
+                return ac;
+            }
+            return null;
+        }
+
+        protected feedNotifArray(array: { index: number, value: T }[], startindIndex: number, ...items: T[]) {
+            array.splice(0);
+            for (let i = 0; i < items.length; i++) {
+                let value = this._array[i + startindIndex];
+                if (value !== undefined) {
+                    array.push({ index: i + startindIndex, value: value });
+                }
+            }
+        }
+
+        protected callArrayChanged(ac: ArrayChanged<T>) {
+            try {
+                this._callingArrayChanged = true;
+                this.arrayChanged.notifyObservers(ac, ac.action);
+            } finally {
+                this._callingArrayChanged = false;
+            }
+        }
+
+        get watchedObjectChanged(): Observable<OAWatchedObjectChangedInfo<T>> {
+            if (!this._watchedObjectChanged) {
+                this._watchedObjectChanged = new Observable<OAWatchedObjectChangedInfo<T>>();
+            }
+            return this._watchedObjectChanged;
+        }
+
+        private _addWatchedElement(...items: T[]) {
+            for (let curItem of items) {
+                if (curItem["propertyChanged"]) {
+                    let key = curItem["__ObsArrayObjID__"] as string;
+
+                    // The object may already be part of another ObsArray, so there already be a valid ID
+                    if (!key) {
+                        key = Tools.RandomId();
+                        curItem["__ObsArrayObjID__"] = key;
+                    }
+
+                    this._watchedObjectList.add(key, (<IPropertyChanged><any>curItem).propertyChanged.add((e, d) => {
+                        this.onWatchedObjectChanged(key, curItem, e);
+                    }));
+                }
+            }
+        }
+
+        private _removeWatchedElement(...items: T[]) {
+            for (let curItem of items) {
+                let key = curItem["__ObsArrayObjID__"] as string;
+                if (key != null) {
+                    let observer = this._watchedObjectList.getAndRemove(key);
+                    (<IPropertyChanged><any>curItem).propertyChanged.remove(observer);
+                }
+            }
+        }
+
+        protected onWatchedObjectChanged(key: string, object: T, propChanged: PropertyChangedInfo) {
+            if (this._watchedObjectChanged && this._watchedObjectChanged.hasObservers()) {
+
+                let woci = this._callingWatchedObjectChanged ? new OAWatchedObjectChangedInfo<T>() : this._woci;
+                woci.object = object;
+                woci.propertyChanged = propChanged;
+
+                try {
+                    this._callingWatchedObjectChanged = true;
+                    this.watchedObjectChanged.notifyObservers(woci);
+                } finally {
+                    this._callingWatchedObjectChanged = false;
+                }
+            }
+        }
+
+        private _array: Array<T>;
+
+        private _arrayChanged: Observable<ArrayChanged<T>>;
+        private dci = new ArrayChanged<T>();
+        private _callingArrayChanged: boolean = false;
+
+        private _watchedObjectChanged: Observable<OAWatchedObjectChangedInfo<T>>;
+        private _woci: OAWatchedObjectChangedInfo<T>;
+        private _callingWatchedObjectChanged: boolean;
+        private _watchObjectsPropertyChange: boolean;
+        private _watchedObjectList: StringDictionary<Observer<PropertyChangedInfo>>;
+
+    }
+}

+ 296 - 0
canvas2D/src/Tools/babylon.stringDictionary.js

@@ -0,0 +1,296 @@
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+};
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Class for the ObservableStringDictionary.onDictionaryChanged observable
+     */
+    var DictionaryChanged = (function () {
+        function DictionaryChanged() {
+        }
+        Object.defineProperty(DictionaryChanged, "clearAction", {
+            /**
+             * The content of the dictionary was totally cleared
+             */
+            get: function () {
+                return DictionaryChanged._clearAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(DictionaryChanged, "newItemAction", {
+            /**
+             * A new item was added, the newItem field contains the key/value pair
+             */
+            get: function () {
+                return DictionaryChanged._newItemAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(DictionaryChanged, "removedItemAction", {
+            /**
+             * An existing item was removed, the removedKey field contains its key
+             */
+            get: function () {
+                return DictionaryChanged._removedItemAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(DictionaryChanged, "itemValueChangedAction", {
+            /**
+             * An existing item had a value change, the changedItem field contains the key/value
+             */
+            get: function () {
+                return DictionaryChanged._itemValueChangedAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(DictionaryChanged, "replacedAction", {
+            /**
+             * The dictionary's content was reset and replaced by the content of another dictionary.
+             * DictionaryChanged<T> contains no further information about this action
+             */
+            get: function () {
+                return DictionaryChanged._replacedAction;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        DictionaryChanged._clearAction = 0x1;
+        DictionaryChanged._newItemAction = 0x2;
+        DictionaryChanged._removedItemAction = 0x4;
+        DictionaryChanged._itemValueChangedAction = 0x8;
+        DictionaryChanged._replacedAction = 0x10;
+        return DictionaryChanged;
+    }());
+    BABYLON.DictionaryChanged = DictionaryChanged;
+    var OSDWatchedObjectChangedInfo = (function () {
+        function OSDWatchedObjectChangedInfo() {
+        }
+        return OSDWatchedObjectChangedInfo;
+    }());
+    BABYLON.OSDWatchedObjectChangedInfo = OSDWatchedObjectChangedInfo;
+    var ObservableStringDictionary = (function (_super) {
+        __extends(ObservableStringDictionary, _super);
+        function ObservableStringDictionary(watchObjectsPropertyChange) {
+            _super.call(this);
+            this._propertyChanged = null;
+            this._dictionaryChanged = null;
+            this.dci = new DictionaryChanged();
+            this._callingDicChanged = false;
+            this._watchedObjectChanged = null;
+            this._callingWatchedObjectChanged = false;
+            this._woci = new OSDWatchedObjectChangedInfo();
+            this._watchObjectsPropertyChange = watchObjectsPropertyChange;
+            this._watchedObjectList = this._watchObjectsPropertyChange ? new BABYLON.StringDictionary() : null;
+        }
+        /**
+         * This will clear this dictionary and copy the content from the 'source' one.
+         * If the T value is a custom object, it won't be copied/cloned, the same object will be used
+         * @param source the dictionary to take the content from and copy to this dictionary
+         */
+        ObservableStringDictionary.prototype.copyFrom = function (source) {
+            var _this = this;
+            var oldCount = this.count;
+            // Don't rely on this class' implementation for clear/add otherwise tons of notification will be thrown
+            _super.prototype.clear.call(this);
+            source.forEach(function (t, v) { return _this._add(t, v, false, _this._watchObjectsPropertyChange); });
+            this.onDictionaryChanged(DictionaryChanged.replacedAction, null, null, null);
+            this.onPropertyChanged("count", oldCount, this.count);
+        };
+        /**
+         * Get a value from its key or add it if it doesn't exist.
+         * This method will ensure you that a given key/data will be present in the dictionary.
+         * @param key the given key to get the matching value from
+         * @param factory the factory that will create the value if the key is not present in the dictionary.
+         * The factory will only be invoked if there's no data for the given key.
+         * @return the value corresponding to the key.
+         */
+        ObservableStringDictionary.prototype.getOrAddWithFactory = function (key, factory) {
+            var _this = this;
+            var val = _super.prototype.getOrAddWithFactory.call(this, key, function (k) {
+                var v = factory(key);
+                _this._add(key, v, true, _this._watchObjectsPropertyChange);
+                return v;
+            });
+            return val;
+        };
+        /**
+         * Add a new key and its corresponding value
+         * @param key the key to add
+         * @param value the value corresponding to the key
+         * @return true if the operation completed successfully, false if we couldn't insert the key/value because there was already this key in the dictionary
+         */
+        ObservableStringDictionary.prototype.add = function (key, value) {
+            return this._add(key, value, true, true);
+        };
+        ObservableStringDictionary.prototype.getAndRemove = function (key) {
+            var val = _super.prototype.get.call(this, key);
+            this._remove(key, true, val);
+            return val;
+        };
+        ObservableStringDictionary.prototype._add = function (key, value, fireNotif, registerWatcher) {
+            if (_super.prototype.add.call(this, key, value)) {
+                if (fireNotif) {
+                    this.onDictionaryChanged(DictionaryChanged.newItemAction, { key: key, value: value }, null, null);
+                    this.onPropertyChanged("count", this.count - 1, this.count);
+                }
+                if (registerWatcher) {
+                    this._addWatchedElement(key, value);
+                }
+                return true;
+            }
+            return false;
+        };
+        ObservableStringDictionary.prototype._addWatchedElement = function (key, el) {
+            var _this = this;
+            if (el["propertyChanged"]) {
+                this._watchedObjectList.add(key, el.propertyChanged.add(function (e, d) {
+                    _this.onWatchedObjectChanged(key, el, e);
+                }));
+            }
+        };
+        ObservableStringDictionary.prototype._removeWatchedElement = function (key, el) {
+            var observer = this._watchedObjectList.getAndRemove(key);
+            el.propertyChanged.remove(observer);
+        };
+        ObservableStringDictionary.prototype.set = function (key, value) {
+            var oldValue = this.get(key);
+            if (this._watchObjectsPropertyChange) {
+                this._removeWatchedElement(key, oldValue);
+            }
+            if (_super.prototype.set.call(this, key, value)) {
+                this.onDictionaryChanged(DictionaryChanged.itemValueChangedAction, null, null, { key: key, oldValue: oldValue, newValue: value });
+                this._addWatchedElement(key, value);
+                return true;
+            }
+            return false;
+        };
+        /**
+         * Remove a key/value from the dictionary.
+         * @param key the key to remove
+         * @return true if the item was successfully deleted, false if no item with such key exist in the dictionary
+         */
+        ObservableStringDictionary.prototype.remove = function (key) {
+            return this._remove(key, true);
+        };
+        ObservableStringDictionary.prototype._remove = function (key, fireNotif, element) {
+            if (!element) {
+                element = this.get(key);
+            }
+            if (!element) {
+                return false;
+            }
+            if (_super.prototype.remove.call(this, key) === undefined) {
+                return false;
+            }
+            this.onDictionaryChanged(DictionaryChanged.removedItemAction, null, key, null);
+            this.onPropertyChanged("count", this.count + 1, this.count);
+            if (this._watchObjectsPropertyChange) {
+                this._removeWatchedElement(key, element);
+            }
+            return true;
+        };
+        /**
+         * Clear the whole content of the dictionary
+         */
+        ObservableStringDictionary.prototype.clear = function () {
+            var _this = this;
+            this._watchedObjectList.forEach(function (k, v) {
+                var el = _this.get(k);
+                _this._removeWatchedElement(k, el);
+            });
+            this._watchedObjectList.clear();
+            var oldCount = this.count;
+            _super.prototype.clear.call(this);
+            this.onDictionaryChanged(DictionaryChanged.clearAction, null, null, null);
+            this.onPropertyChanged("count", oldCount, 0);
+        };
+        Object.defineProperty(ObservableStringDictionary.prototype, "propertyChanged", {
+            get: function () {
+                if (!this._propertyChanged) {
+                    this._propertyChanged = new BABYLON.Observable();
+                }
+                return this._propertyChanged;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ObservableStringDictionary.prototype.onPropertyChanged = function (propName, oldValue, newValue, mask) {
+            if (this._propertyChanged && this._propertyChanged.hasObservers()) {
+                var pci = ObservableStringDictionary.callingPropChanged ? new BABYLON.PropertyChangedInfo() : ObservableStringDictionary.pci;
+                pci.oldValue = oldValue;
+                pci.newValue = newValue;
+                pci.propertyName = propName;
+                try {
+                    ObservableStringDictionary.callingPropChanged = true;
+                    this.propertyChanged.notifyObservers(pci, mask);
+                }
+                finally {
+                    ObservableStringDictionary.callingPropChanged = false;
+                }
+            }
+        };
+        Object.defineProperty(ObservableStringDictionary.prototype, "dictionaryChanged", {
+            get: function () {
+                if (!this._dictionaryChanged) {
+                    this._dictionaryChanged = new BABYLON.Observable();
+                }
+                return this._dictionaryChanged;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ObservableStringDictionary.prototype.onDictionaryChanged = function (action, newItem, removedKey, changedItem) {
+            if (this._dictionaryChanged && this._dictionaryChanged.hasObservers()) {
+                var dci = this._callingDicChanged ? new DictionaryChanged() : this.dci;
+                dci.action = action;
+                dci.newItem = newItem;
+                dci.removedKey = removedKey;
+                dci.changedItem = changedItem;
+                try {
+                    this._callingDicChanged = true;
+                    this.dictionaryChanged.notifyObservers(dci, action);
+                }
+                finally {
+                    this._callingDicChanged = false;
+                }
+            }
+        };
+        Object.defineProperty(ObservableStringDictionary.prototype, "watchedObjectChanged", {
+            get: function () {
+                if (!this._watchedObjectChanged) {
+                    this._watchedObjectChanged = new BABYLON.Observable();
+                }
+                return this._watchedObjectChanged;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        ObservableStringDictionary.prototype.onWatchedObjectChanged = function (key, object, propChanged) {
+            if (this._watchedObjectChanged && this._watchedObjectChanged.hasObservers()) {
+                var woci = this._callingWatchedObjectChanged ? new OSDWatchedObjectChangedInfo() : this._woci;
+                woci.key = key;
+                woci.object = object;
+                woci.propertyChanged = propChanged;
+                try {
+                    this._callingWatchedObjectChanged = true;
+                    this.watchedObjectChanged.notifyObservers(woci);
+                }
+                finally {
+                    this._callingWatchedObjectChanged = false;
+                }
+            }
+        };
+        ObservableStringDictionary.pci = new BABYLON.PropertyChangedInfo();
+        ObservableStringDictionary.callingPropChanged = false;
+        return ObservableStringDictionary;
+    }(BABYLON.StringDictionary));
+    BABYLON.ObservableStringDictionary = ObservableStringDictionary;
+})(BABYLON || (BABYLON = {}));

+ 320 - 0
canvas2D/src/Tools/babylon.stringDictionary.ts

@@ -0,0 +1,320 @@
+module BABYLON {
+
+    /**
+     * Class for the ObservableStringDictionary.onDictionaryChanged observable
+     */
+    export class DictionaryChanged<T> {
+        /**
+         * Contain the action that were made on the dictionary, it's one of the DictionaryChanged.xxxAction members.
+         * Note the action's value can be used in the "mask" field of the Observable to only be notified about given action(s)
+         */
+        public action: number;
+
+        /**
+         * Only valid if the action is newItemAction
+         */
+        public newItem: { key: string, value: T }
+
+        /**
+         * Only valid if the action is removedItemAction
+         */
+        public removedKey: string;
+
+        /**
+         * Only valid if the action is itemValueChangedAction
+         */
+        public changedItem: { key: string, oldValue: T, newValue: T }
+
+        /**
+         * The content of the dictionary was totally cleared
+         */
+        public static get clearAction() {
+            return DictionaryChanged._clearAction;
+        }
+
+        /**
+         * A new item was added, the newItem field contains the key/value pair
+         */
+        public static get newItemAction() {
+            return DictionaryChanged._newItemAction;
+        }
+
+        /**
+         * An existing item was removed, the removedKey field contains its key
+         */
+        public static get removedItemAction() {
+            return DictionaryChanged._removedItemAction;
+        }
+
+        /**
+         * An existing item had a value change, the changedItem field contains the key/value
+         */
+        public static get itemValueChangedAction() {
+            return DictionaryChanged._itemValueChangedAction;
+        }
+
+        /**
+         * The dictionary's content was reset and replaced by the content of another dictionary.
+         * DictionaryChanged<T> contains no further information about this action
+         */
+        public static get replacedAction() {
+            return DictionaryChanged._replacedAction;
+        }
+
+        private static _clearAction            = 0x1;
+        private static _newItemAction          = 0x2;
+        private static _removedItemAction      = 0x4;
+        private static _itemValueChangedAction = 0x8;
+        private static _replacedAction         = 0x10;
+    }
+
+    export class OSDWatchedObjectChangedInfo<T> {
+        key: string;
+        object: T;
+        propertyChanged: PropertyChangedInfo;
+    }
+
+    export class ObservableStringDictionary<T> extends StringDictionary<T> implements IPropertyChanged {
+
+        constructor(watchObjectsPropertyChange: boolean) {
+            super();
+
+            this._propertyChanged = null;
+            this._dictionaryChanged = null;
+            this.dci = new DictionaryChanged<T>();
+            this._callingDicChanged = false;
+            this._watchedObjectChanged = null;
+            this._callingWatchedObjectChanged = false;
+            this._woci = new OSDWatchedObjectChangedInfo<T>();
+            this._watchObjectsPropertyChange = watchObjectsPropertyChange;
+            this._watchedObjectList = this._watchObjectsPropertyChange ? new StringDictionary<Observer<PropertyChangedInfo>>() : null;
+        }
+
+        /**
+         * This will clear this dictionary and copy the content from the 'source' one.
+         * If the T value is a custom object, it won't be copied/cloned, the same object will be used
+         * @param source the dictionary to take the content from and copy to this dictionary
+         */
+        public copyFrom(source: StringDictionary<T>) {
+            let oldCount = this.count;
+            // Don't rely on this class' implementation for clear/add otherwise tons of notification will be thrown
+            super.clear();
+            source.forEach((t, v) => this._add(t, v, false, this._watchObjectsPropertyChange));
+            this.onDictionaryChanged(DictionaryChanged.replacedAction, null, null, null);
+            this.onPropertyChanged("count", oldCount, this.count);
+        }
+
+        /**
+         * Get a value from its key or add it if it doesn't exist.
+         * This method will ensure you that a given key/data will be present in the dictionary.
+         * @param key the given key to get the matching value from
+         * @param factory the factory that will create the value if the key is not present in the dictionary.
+         * The factory will only be invoked if there's no data for the given key.
+         * @return the value corresponding to the key.
+         */
+        public getOrAddWithFactory(key: string, factory: (key: string) => T): T {
+            let val = super.getOrAddWithFactory(key, k => {
+                let v = factory(key);
+                this._add(key, v, true, this._watchObjectsPropertyChange);
+                return v;
+            });
+
+            return val;
+        }
+
+        /**
+         * Add a new key and its corresponding value
+         * @param key the key to add
+         * @param value the value corresponding to the key
+         * @return true if the operation completed successfully, false if we couldn't insert the key/value because there was already this key in the dictionary
+         */
+        public add(key: string, value: T): boolean {
+            return this._add(key, value, true, true);
+        }
+
+        public getAndRemove(key: string): T {
+            let val = super.get(key);
+            this._remove(key, true, val);
+            return val;
+        }
+
+        private _add(key: string, value: T, fireNotif: boolean, registerWatcher: boolean): boolean {
+            if (super.add(key, value)) {
+                if (fireNotif) {
+                    this.onDictionaryChanged(DictionaryChanged.newItemAction, { key: key, value: value }, null, null);
+                    this.onPropertyChanged("count", this.count - 1, this.count);
+                }
+                if (registerWatcher) {
+                    this._addWatchedElement(key, value);
+                }
+                return true;
+            }
+            return false;
+        }
+
+        private _addWatchedElement(key: string, el: T) {
+            if (el["propertyChanged"]) {
+                this._watchedObjectList.add(key, (<IPropertyChanged><any>el).propertyChanged.add((e, d) => {
+                    this.onWatchedObjectChanged(key, el, e);
+                }));
+            }            
+        }
+
+        private _removeWatchedElement(key: string, el: T) {
+            let observer = this._watchedObjectList.getAndRemove(key);
+            (<IPropertyChanged><any>el).propertyChanged.remove(observer);
+        }
+
+        public set(key: string, value: T): boolean {
+            let oldValue = this.get(key);
+            if (this._watchObjectsPropertyChange) {
+                this._removeWatchedElement(key, oldValue);
+            }
+
+            if (super.set(key, value)) {
+                this.onDictionaryChanged(DictionaryChanged.itemValueChangedAction, null, null, { key: key, oldValue: oldValue, newValue: value });
+                this._addWatchedElement(key, value);
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Remove a key/value from the dictionary.
+         * @param key the key to remove
+         * @return true if the item was successfully deleted, false if no item with such key exist in the dictionary
+         */
+        public remove(key: string): boolean {
+            return this._remove(key, true);
+        }
+
+        private _remove(key: string, fireNotif: boolean, element?: T): boolean {
+            if (!element) {
+                element = this.get(key);
+            }
+
+            if (!element) {
+                return false;
+            }
+
+            if (super.remove(key) === undefined) {
+                return false;
+            }
+
+            this.onDictionaryChanged(DictionaryChanged.removedItemAction, null, key, null);
+            this.onPropertyChanged("count", this.count + 1, this.count);
+
+            if (this._watchObjectsPropertyChange) {
+                this._removeWatchedElement(key, element);
+            }
+
+            return true;
+        }
+
+        /**
+         * Clear the whole content of the dictionary
+         */
+        public clear() {
+            this._watchedObjectList.forEach((k, v) => {
+                let el = this.get(k);
+                this._removeWatchedElement(k, el);
+            });
+            this._watchedObjectList.clear();
+
+            let oldCount = this.count;
+            super.clear();
+            this.onDictionaryChanged(DictionaryChanged.clearAction, null, null, null);
+            this.onPropertyChanged("count", oldCount, 0);
+        }
+
+        get propertyChanged(): Observable<PropertyChangedInfo> {
+            if (!this._propertyChanged) {
+                this._propertyChanged = new Observable<PropertyChangedInfo>();
+            }
+            return this._propertyChanged;
+        }
+
+        protected onPropertyChanged<T>(propName: string, oldValue: T, newValue: T, mask?: number) {
+            if (this._propertyChanged && this._propertyChanged.hasObservers()) {
+
+                let pci = ObservableStringDictionary.callingPropChanged ? new PropertyChangedInfo() : ObservableStringDictionary.pci;
+
+                pci.oldValue = oldValue;
+                pci.newValue = newValue;
+                pci.propertyName = propName;
+
+                try {
+                    ObservableStringDictionary.callingPropChanged = true;
+                    this.propertyChanged.notifyObservers(pci, mask);
+                } finally {
+                    ObservableStringDictionary.callingPropChanged = false;
+                }
+            }
+        }
+
+        get dictionaryChanged(): Observable<DictionaryChanged<T>> {
+            if (!this._dictionaryChanged) {
+                this._dictionaryChanged = new Observable<DictionaryChanged<T>>();
+            }
+            return this._dictionaryChanged;
+        }
+
+        protected onDictionaryChanged(action: number, newItem: { key: string, value: T }, removedKey: string, changedItem: { key: string, oldValue: T, newValue: T }) {
+            if (this._dictionaryChanged && this._dictionaryChanged.hasObservers()) {
+
+                let dci = this._callingDicChanged ? new DictionaryChanged<T>() : this.dci;
+
+                dci.action = action;
+                dci.newItem = newItem;
+                dci.removedKey = removedKey;
+                dci.changedItem = changedItem;
+
+                try {
+                    this._callingDicChanged = true;
+                    this.dictionaryChanged.notifyObservers(dci, action);
+                } finally {
+                    this._callingDicChanged = false;
+                }
+            }
+        }
+
+        get watchedObjectChanged(): Observable<OSDWatchedObjectChangedInfo<T>> {
+            if (!this._watchedObjectChanged) {
+                this._watchedObjectChanged = new Observable<OSDWatchedObjectChangedInfo<T>>();
+            }
+            return this._watchedObjectChanged;
+        }
+
+        protected onWatchedObjectChanged(key: string, object: T, propChanged: PropertyChangedInfo) {
+            if (this._watchedObjectChanged && this._watchedObjectChanged.hasObservers()) {
+
+                let woci = this._callingWatchedObjectChanged ? new OSDWatchedObjectChangedInfo<T>() : this._woci;
+                woci.key = key;
+                woci.object = object;
+                woci.propertyChanged = propChanged;
+
+                try {
+                    this._callingWatchedObjectChanged = true;
+                    this.watchedObjectChanged.notifyObservers(woci);
+                } finally {
+                    this._callingWatchedObjectChanged = false;
+                }
+            }
+        }
+
+        private _propertyChanged: Observable<PropertyChangedInfo>;
+        private static pci = new PropertyChangedInfo();
+        private static callingPropChanged: boolean = false;
+
+        private _dictionaryChanged: Observable<DictionaryChanged<T>>;
+        private dci: DictionaryChanged<T>;
+        private _callingDicChanged: boolean;
+
+        private _watchedObjectChanged: Observable<OSDWatchedObjectChangedInfo<T>>;
+        private _woci: OSDWatchedObjectChangedInfo<T>;
+        private _callingWatchedObjectChanged: boolean;
+        private _watchObjectsPropertyChange: boolean;
+        private _watchedObjectList: StringDictionary<Observer<PropertyChangedInfo>>;
+    }
+}

src/Shaders/ellipse2d.fragment.fx → canvas2D/src/shaders/ellipse2d.fragment.fx


src/Shaders/ellipse2d.vertex.fx → canvas2D/src/shaders/ellipse2d.vertex.fx


src/Shaders/lines2d.fragment.fx → canvas2D/src/shaders/lines2d.fragment.fx


src/Shaders/lines2d.vertex.fx → canvas2D/src/shaders/lines2d.vertex.fx


src/Shaders/rect2d.fragment.fx → canvas2D/src/shaders/rect2d.fragment.fx


src/Shaders/rect2d.vertex.fx → canvas2D/src/shaders/rect2d.vertex.fx


src/Shaders/sprite2d.fragment.fx → canvas2D/src/shaders/sprite2d.fragment.fx


src/Shaders/sprite2d.vertex.fx → canvas2D/src/shaders/sprite2d.vertex.fx


src/Shaders/text2d.fragment.fx → canvas2D/src/shaders/text2d.fragment.fx


src/Shaders/text2d.vertex.fx → canvas2D/src/shaders/text2d.vertex.fx


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 4051 - 0
dist/preview release/babylon.canvas2d.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 9 - 0
dist/preview release/babylon.canvas2d.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 14291 - 0
dist/preview release/babylon.canvas2d.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 27 - 27
dist/preview release/babylon.core.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 4978 - 7974
dist/preview release/babylon.d.ts


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 37 - 43
dist/preview release/babylon.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 505 - 11497
dist/preview release/babylon.max.js


تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 37 - 43
dist/preview release/babylon.noworker.js


+ 6 - 0
dist/preview release/what's new.md

@@ -5,6 +5,7 @@
 - New `HighlightLayer` object to enable highlights rendering. [Demo](http://www.babylonjs.com/Demos/Highlights/) - ([sebavan](https://github.com/sebavan))
 - Babylon.js now supports right handed system with ```scene.useRightHandedSystem = true``` ([deltakosh](https://github.com/deltakosh))
 - Babylon.js is now compiled with [optimize-js](https://github.com/nolanlawson/optimize-js) to get faster initial load ([deltakosh](https://github.com/deltakosh))
+- Canvas2D moved to a separate folder in main repo. Now you need to also include babylon.cavans2d.js to get Canvas@D feature ([deltakosh](https://github.com/deltakosh))
 
 ### Updates
 - New ```Tools.CreateScreenshot``` function will capture all canvas data. Previous implementation is now called `CreateScreenshotUsingRenderTarget` ([deltakosh](https://github.com/deltakosh)) 
@@ -26,7 +27,11 @@
  - Text2D super sampling to enhance quality in World Space Canvas
  - World Space Canvas is now rendering in an adaptive way for its resolution to fit the on screen projected one to achieve a good rendering quality
  - Transparent Primitives are now drawn with Instanced Array when supported
+ - New property in Canvas2D (instances) that contains all instances of canvas2d [Temechon](https://github.com/Temechon)
 - WebVR Camera was updated to be conform with the current specs. ([RaananW](https://github.com/RaananW)) 
+- New "CubeTextureTask" function will allow you to load a CubeTexture in the assetsManager. ([agallouin](https://github.com/AGallouin)) 
+- Scene.stopAnimation has now an optional second parameter, the name of the animation to kill.
+Usefull if a mesh has multiple animations. ([agallouin](https://github.com/AGallouin)) 
 
 ### Exporters
     
@@ -40,6 +45,7 @@
 - Fixed `Mesh.CreateDashedLines()` missing `instance` parameter on update ([jerome](https://github.com/jbousquie))
 - Added BBox update on each ribbon based shape (ribbon, tube, extrusion, etc) on dynamic updates ([jerome](https://github.com/jbousquie))
 - Fixed model shape initial red vertex color set to zero not formerly being taken in account in the `SolidParticleSystem` ([jerome](https://github.com/jbousquie))
+- Fixed billboard when the SPS mesh is parented in the `SolidParticleSystem` ([jerome](https://github.com/jbousquie))
 - Fixed RenderTargetTexture meshes selection ([deltakosh](https://github.com/deltakosh))
 - Fixed camera speed computation ([deltakosh](https://github.com/deltakosh))
 - Fixed bug with instances, LOD and edgesRendering ([deltakosh](https://github.com/deltakosh))

+ 21 - 0
postProcessLibrary/config.json

@@ -0,0 +1,21 @@
+{
+  "postProcesses": [    
+    {
+      "file": "postProcesses/asciiArt/babylon.asciiArtPostProcess.ts",
+      "shaderFiles": [
+        "postProcesses/asciiArt/asciiart.fragment.fx"
+      ],
+      "output": "babylon.asciiArtPostProcess.js"
+    },
+    {
+      "file": "postProcesses/digitalRain/babylon.digitalRainPostProcess.ts",
+      "shaderFiles": [
+        "postProcesses/digitalRain/digitalrain.fragment.fx"
+      ],
+      "output": "babylon.digitalRainPostProcess.js"
+    }
+  ],
+  "build": {
+    "distOutputDirectory": "dist/"
+  }
+}

proceduralTexturesLibrary/dist/babylon.asciiArtPostProcess.js → postProcessLibrary/dist/babylon.asciiArtPostProcess.js


proceduralTexturesLibrary/dist/babylon.asciiArtPostProcess.min.js → postProcessLibrary/dist/babylon.asciiArtPostProcess.min.js


proceduralTexturesLibrary/dist/babylon.digitalRainPostProcess.js → postProcessLibrary/dist/babylon.digitalRainPostProcess.js


proceduralTexturesLibrary/dist/babylon.digitalRainPostProcess.min.js → postProcessLibrary/dist/babylon.digitalRainPostProcess.min.js


+ 68 - 0
postProcessLibrary/gulp-srcToVariable.js

@@ -0,0 +1,68 @@
+var through = require('through2');
+var gutil = require('gulp-util');
+var PluginError = gutil.PluginError;
+var path = require('path');
+var File = gutil.File;
+
+// Consts
+const PLUGIN_NAME = 'gulp-srcToVariable';
+
+var srcToVariable = function srcToVariable(varName, asMap, namingCallback) {
+
+    var content;
+    var firstFile;
+
+    namingCallback = namingCallback || function (filename) { return filename; };
+
+    function bufferContents(file, enc, cb) {
+        // ignore empty files
+        if (file.isNull()) {
+            cb();
+            return;
+        }
+
+        // no stream support, only files.
+        if (file.isStream()) {
+            this.emit('error', new PluginError('gulp-concat', 'Streaming not supported'));
+            cb();
+            return;
+        }
+
+        // set first file if not already set
+        if (!firstFile) {
+            firstFile = file;
+        }
+
+        // construct concat instance
+        if (!content) {
+            content = "";
+        }
+        var name = namingCallback(file.relative);
+        content += varName + "['" + name + "'] = " + JSON.stringify(file.contents.toString()) + ";\r\n";
+        cb();
+    }
+
+    function endStream(cb) {
+        if (!firstFile || !content) {
+            cb();
+            return;
+        }
+
+        var joinedPath = path.join(firstFile.base, varName);
+
+        var joinedFile = new File({
+            cwd: firstFile.cwd,
+            base: firstFile.base,
+            path: joinedPath,
+            contents: new Buffer(content)
+        });
+
+        this.push(joinedFile);
+
+        cb();
+    }
+
+    return through.obj(bufferContents, endStream);
+}
+
+module.exports = srcToVariable;

+ 53 - 0
postProcessLibrary/gulpfile.js

@@ -0,0 +1,53 @@
+var gulp = require("gulp");
+var typescript = require("gulp-typescript");
+var srcToVariable = require("./gulp-srcToVariable");
+var merge2 = require("merge2");
+var concat = require("gulp-concat");
+var rename = require("gulp-rename");
+var cleants = require('gulp-clean-ts-extends');
+var replace = require("gulp-replace");
+var webserver = require('gulp-webserver');
+var uglify = require("gulp-uglify");
+
+var config = require("./config.json");
+var extendsSearchRegex = /var\s__extends[\s\S]+?\};/g;
+
+//function to convert the shaders' filenames to variable names.
+function shadersName(filename) {
+    return filename.replace('.fragment', 'Pixel')
+      .replace('.vertex', 'Vertex')
+      .replace('.fx', 'Shader');
+}
+
+gulp.task('copyReference', function () {
+    return gulp.src("../dist/preview release/babylon.max.js").pipe(gulp.dest("test/refs"));
+});
+
+/*
+Compiles all typescript files and creating a declaration file.
+*/
+gulp.task('default', ["copyReference"], function () {
+    var tasks = config.proceduralTextures.map(function (proceduralTexture) {
+        var js = gulp.src(proceduralTexture.file)
+            .pipe(typescript({
+                noExternalResolve: false,
+                target: 'ES5',
+                declarationFiles: true,
+                typescript: require('typescript'),
+                experimentalDecorators: true
+            })).js;
+
+        var shader = gulp.src(proceduralTexture.shaderFiles).pipe(srcToVariable("BABYLON.Effect.ShadersStore", true, shadersName));
+
+        return merge2(js, shader)
+            .pipe(cleants())
+            .pipe(replace(extendsSearchRegex, ""))
+            .pipe(concat(proceduralTexture.output))
+            .pipe(gulp.dest(config.build.distOutputDirectory))
+            .pipe(rename({extname: ".min.js"}))
+            .pipe(uglify())
+            .pipe(gulp.dest(config.build.distOutputDirectory));
+    });
+
+    return tasks;
+});

+ 23 - 0
postProcessLibrary/package.json

@@ -0,0 +1,23 @@
+{
+  "name": "BabylonJS_PostProcessLibrary",
+  "version": "2.3.0",
+  "description": "PostProcess library for Babylon.js",
+  "main": "",
+  "repository": { "url": "https://github.com/BabylonJS/Babylon.js/" },
+  "readme": "https://github.com/BabylonJS/Babylon.js/edit/master/readme.md",
+  "license": "(Apache-2.0)",
+  "devDependencies": {
+    "gulp": "^3.8.11",
+    "gulp-uglify": "~1.4.2",
+    "typescript": "~1.6.2",
+    "gulp-typescript": "~2.13.0",
+    "through2": "~0.6.5",
+    "gulp-util": "~3.0.4",
+    "gulp-concat": "~2.6.0",
+    "merge2": "~0.3.5",
+    "gulp-rename": "~1.2.2",
+    "gulp-clean-ts-extends": "~0.1.1",
+    "gulp-replace": "~0.5.3",
+    "gulp-webserver": "^0.9.1"
+  }
+}

proceduralTexturesLibrary/proceduralTextures/asciiArt/asciiart.fragment.fx → postProcessLibrary/postProcesses/asciiArt/asciiart.fragment.fx


proceduralTexturesLibrary/proceduralTextures/asciiArt/babylon.asciiArtPostProcess.ts → postProcessLibrary/postProcesses/asciiArt/babylon.asciiArtPostProcess.ts


proceduralTexturesLibrary/proceduralTextures/digitalRain/babylon.digitalRainPostProcess.ts → postProcessLibrary/postProcesses/digitalRain/babylon.digitalRainPostProcess.ts


proceduralTexturesLibrary/proceduralTextures/digitalRain/digitalrain.fragment.fx → postProcessLibrary/postProcesses/digitalRain/digitalrain.fragment.fx


+ 41 - 0
postProcessLibrary/readme.md

@@ -0,0 +1,41 @@
+## Using a post process from the library
+
+You can find multiple post processes that just works with Babylon.js in *dist* folder. To use then, you only need to reference the associated .js file and use the new provided post process:
+
+```
+var fire = new BABYLON.FireProceduralTexture2("firePT", 256, scene);
+sphere.material.diffuseTexture = fire;
+```
+
+## Adding a new post process to the library
+
+To add a new post process, you have to create your own folder in *postProcesses* folder. Then you need to add a .ts file and one .fragment.fx files:
+* The .ts is the TypeScript code of your post process
+* .fx file: GLSL code for fragment shaders
+
+## Integrating the post process in the build process
+
+To build all post processes and generate the *dist* folder, just run:
+
+```
+gulp
+```
+
+To integrate your new post process to the build process, you have to edit the config.sjon file and add an entry in the "postProcesses" section of the file:
+
+```
+{
+  "postProcesses": [
+    {
+      "file": "postProcesses/asciiArt/babylon.asciiArtPostProcess.ts",
+      "shaderFiles": [
+        "postProcesses/asciiArt/asciiart.fragment.fx"
+      ],
+      "output": "babylon.asciiArtPostProcess.js"
+    }
+  ],
+  "build": {
+    "distOutputDirectory": "dist/"
+  }
+}
+```

+ 7 - 0
postProcessLibrary/tsconfig.json

@@ -0,0 +1,7 @@
+{
+    "compilerOptions": {
+        "experimentalDecorators": true,
+        "module": "commonjs", 
+        "target": "es5"
+    }
+}

+ 0 - 14
proceduralTexturesLibrary/config.json

@@ -55,20 +55,6 @@
         "proceduralTextures/starfield/starfieldProceduralTexture.fragment.fx"
       ],
       "output": "babylon.starfieldProceduralTexture.js"
-    },
-    {
-      "file": "proceduralTextures/asciiArt/babylon.asciiArtPostProcess.ts",
-      "shaderFiles": [
-        "proceduralTextures/asciiArt/asciiart.fragment.fx"
-      ],
-      "output": "babylon.asciiArtPostProcess.js"
-    },
-    {
-      "file": "proceduralTextures/digitalRain/babylon.digitalRainPostProcess.ts",
-      "shaderFiles": [
-        "proceduralTextures/digitalRain/digitalrain.fragment.fx"
-      ],
-      "output": "babylon.digitalRainPostProcess.js"
     }
   ],
   "build": {

+ 2 - 2
proceduralTexturesLibrary/package.json

@@ -1,7 +1,7 @@
 {
-  "name": "BabylonJS_ShadersLibrary",
+  "name": "BabylonJS_PostProcessLibrary",
   "version": "2.3.0",
-  "description": "Shaders library for Babylon.js",
+  "description": "PostProcess library for Babylon.js",
   "main": "",
   "repository": { "url": "https://github.com/BabylonJS/Babylon.js/" },
   "readme": "https://github.com/BabylonJS/Babylon.js/edit/master/readme.md",

+ 1 - 1
proceduralTexturesLibrary/readme.md

@@ -13,7 +13,7 @@ To add a new procedural texture, you have to create your own folder in *procedur
 * The .ts is the TypeScript code of your procedural texture
 * .fx file: GLSL code for fragment shaders
 
-## Integrating the material in the build process
+## Integrating the procedural texture in the build process
 
 To build all procedural textures and generate the *dist* folder, just run:
 

+ 1 - 1
readme.md

@@ -15,7 +15,7 @@ Getting started? Play directly with the Babylon.js API via our [playground](http
 
 ## Preview release
 You can help by testing or contributing to the next version.
-- **2.5-alpha** can be found [here](https://github.com/BabylonJS/Babylon.js/tree/master/dist/preview%20release)
+- **2.5-beta** can be found [here](https://github.com/BabylonJS/Babylon.js/tree/master/dist/preview%20release)
 - We are not complicated people, but we still have some [coding guidelines](http://doc.babylonjs.com/generals/Approved_Naming_Conventions)
 - Before submitting your PR, just check that everything goes well by [creating the minified version](http://doc.babylonjs.com/generals/Creating_the_Mini-fied_Version)
 - Need help to contribute? We have a [general purpose documentation for you](http://pixelcodr.com/tutos/contribute/contribute.html) and a [Visual Studio specific one](http://doc.babylonjs.com/generals/setup_visualStudio)

+ 13 - 5
src/Animations/babylon.animatable.js

@@ -77,16 +77,24 @@ var BABYLON;
         Animatable.prototype.restart = function () {
             this._paused = false;
         };
-        Animatable.prototype.stop = function () {
+        Animatable.prototype.stop = function (animationName) {
             var index = this._scene._activeAnimatables.indexOf(this);
             if (index > -1) {
-                this._scene._activeAnimatables.splice(index, 1);
                 var animations = this._animations;
-                for (var index = 0; index < animations.length; index++) {
+                var numberOfAnimationsStopped = 0;
+                for (var index = animations.length - 1; index >= 0; index--) {
+                    if (typeof animationName === "string" && animations[index].name != animationName) {
+                        continue;
+                    }
                     animations[index].reset();
+                    animations.splice(index, 1);
+                    numberOfAnimationsStopped++;
                 }
-                if (this.onAnimationEnd) {
-                    this.onAnimationEnd();
+                if (animations.length == numberOfAnimationsStopped) {
+                    this._scene._activeAnimatables.splice(index, 1);
+                    if (this.onAnimationEnd) {
+                        this.onAnimationEnd();
+                    }
                 }
             }
         };

+ 14 - 6
src/Animations/babylon.animatable.ts

@@ -90,19 +90,27 @@
             this._paused = false;
         }
 
-        public stop(): void {
+        public stop(animationName?: string): void {
             var index = this._scene._activeAnimatables.indexOf(this);
 
             if (index > -1) {
-                this._scene._activeAnimatables.splice(index, 1);
-
                 var animations = this._animations;
-                for (var index = 0; index < animations.length; index++) {
+                var numberOfAnimationsStopped = 0;
+                for (var index = animations.length - 1; index >= 0; index--) {
+                    if (typeof animationName === "string" && animations[index].name != animationName) {
+                        continue;
+                    }
                     animations[index].reset();
+                    animations.splice(index, 1);
+                    numberOfAnimationsStopped ++;
                 }
 
-                if (this.onAnimationEnd) {
-                    this.onAnimationEnd();
+                if (animations.length == numberOfAnimationsStopped) {
+                    this._scene._activeAnimatables.splice(index, 1);
+
+                    if (this.onAnimationEnd) {
+                        this.onAnimationEnd();
+                    }
                 }
             }
         }

+ 7 - 8
src/Cameras/Inputs/babylon.freecamera.input.mouse.js

@@ -14,9 +14,8 @@ var BABYLON;
         }
         FreeCameraMouseInput.prototype.attachControl = function (element, noPreventDefault) {
             var _this = this;
+            var engine = this.camera.getEngine();
             if (!this._pointerInput) {
-                var camera = this.camera;
-                var engine = this.camera.getEngine();
                 this._pointerInput = function (p, s) {
                     var evt = p.event;
                     if (!_this.touchEnabled && evt.pointerType === "touch") {
@@ -55,12 +54,12 @@ var BABYLON;
                         var offsetX = evt.clientX - _this.previousPosition.x;
                         var offsetY = evt.clientY - _this.previousPosition.y;
                         if (_this.camera.getScene().useRightHandedSystem) {
-                            camera.cameraRotation.y -= offsetX / _this.angularSensibility;
+                            _this.camera.cameraRotation.y -= offsetX / _this.angularSensibility;
                         }
                         else {
-                            camera.cameraRotation.y += offsetX / _this.angularSensibility;
+                            _this.camera.cameraRotation.y += offsetX / _this.angularSensibility;
                         }
-                        camera.cameraRotation.x += offsetY / _this.angularSensibility;
+                        _this.camera.cameraRotation.x += offsetY / _this.angularSensibility;
                         _this.previousPosition = {
                             x: evt.clientX,
                             y: evt.clientY
@@ -78,12 +77,12 @@ var BABYLON;
                 var offsetX = evt.movementX || evt.mozMovementX || evt.webkitMovementX || evt.msMovementX || 0;
                 var offsetY = evt.movementY || evt.mozMovementY || evt.webkitMovementY || evt.msMovementY || 0;
                 if (_this.camera.getScene().useRightHandedSystem) {
-                    camera.cameraRotation.y -= offsetX / _this.angularSensibility;
+                    _this.camera.cameraRotation.y -= offsetX / _this.angularSensibility;
                 }
                 else {
-                    camera.cameraRotation.y += offsetX / _this.angularSensibility;
+                    _this.camera.cameraRotation.y += offsetX / _this.angularSensibility;
                 }
-                camera.cameraRotation.x += offsetY / _this.angularSensibility;
+                _this.camera.cameraRotation.x += offsetY / _this.angularSensibility;
                 _this.previousPosition = null;
                 if (!noPreventDefault) {
                     evt.preventDefault();

+ 0 - 0
src/Cameras/Inputs/babylon.freecamera.input.mouse.ts


برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است