فهرست منبع

Tools, Canvas2D, GUI, Unit Tests

Tools:
 - adding ObservableStringDictionary and ObservableArray, PropertyChangedBase classes
 - StringDictionary has now copyFrom and getAndRemove methods

 Canvas2D
 - adding Canvas2D.uid get-only property to get a unique id for a Canvas instance
 - adding Canvas2D.renderObservable to hook observers before and after a Canvas' rendering
 - adding Canvas2D.overPrim get-only property to get the property under the pointer at the Canvas level
 - adding many SmartProperties to Prim2DBase (mainly shortcut ones like x, y, actualX/Y, etc.)
 - Prim2DBase.margin/padding/marginAlignment have now setter which copy the content from the source object.
 - Prim2DBase fixed a bug on boudingBox computation while using auto sized content.
 - SmartPropertyPrim now derives from PropertyChangedBase class

Unit Tests
 - adding unit test for ObservableArray, StringDictionary and ObservableStringDictionary
 - adding more unit tests for the Binding class
nockawa 8 سال پیش
والد
کامیت
aee1309b44
35فایلهای تغییر یافته به همراه6760 افزوده شده و 389 حذف شده
  1. 2 2
      src/Canvas2d/babylon.brushes2d.ts
  2. 60 4
      src/Canvas2d/babylon.canvas2d.ts
  3. 3 3
      src/Canvas2d/babylon.canvas2dLayoutEngine.ts
  4. 1 1
      src/Canvas2d/babylon.ellipse2d.ts
  5. 1 1
      src/Canvas2d/babylon.group2d.ts
  6. 2 1
      src/Canvas2d/babylon.lines2d.ts
  7. 228 74
      src/Canvas2d/babylon.prim2dBase.ts
  8. 1 1
      src/Canvas2d/babylon.rectangle2d.ts
  9. 1 1
      src/Canvas2d/babylon.renderablePrim2d.ts
  10. 1 1
      src/Canvas2d/babylon.shape2d.ts
  11. 932 294
      src/Canvas2d/babylon.smartPropertyPrim.ts
  12. 1 1
      src/Canvas2d/babylon.sprite2d.ts
  13. 9 2
      src/Canvas2d/babylon.text2d.ts
  14. 806 0
      src/GUI/babylon.gui.UIElement.ts
  15. 209 0
      src/GUI/babylon.gui.button.ts
  16. 212 0
      src/GUI/babylon.gui.control.ts
  17. 77 0
      src/GUI/babylon.gui.label.ts
  18. 214 0
      src/GUI/babylon.gui.window.ts
  19. 669 0
      src/Tools/babylon.observable.ts
  20. 343 0
      src/Tools/babylon.stringDictionary.ts
  21. 35 3
      src/Tools/babylon.tools.ts
  22. 114 0
      tests/Canvas2d/Jasmine/DataBindingTest.js
  23. 163 0
      tests/Canvas2d/Jasmine/DataBindingTest.ts
  24. 173 0
      tests/Canvas2d/Jasmine/New Text Document.txt
  25. 173 0
      tests/Canvas2d/Jasmine/TestClasses.ts
  26. 264 0
      tests/Canvas2d/Jasmine/_Chutzpah.7b5adbce42315517e53da0a9565b4f12.test.html
  27. 207 0
      tests/Canvas2d/Jasmine/chutzpah.json
  28. 204 0
      tests/Canvas2d/Jasmine/testclasses.js
  29. 278 0
      tests/Tools/Jasmine/ObservableArrayTest.js
  30. 344 0
      tests/Tools/Jasmine/ObservableArrayTest.ts
  31. 137 0
      tests/Tools/Jasmine/ObservableDictionaryTest.js
  32. 163 0
      tests/Tools/Jasmine/ObservableDictionaryTest.ts
  33. 263 0
      tests/Tools/Jasmine/_Chutzpah.5f091c9db26afbbd8fc1aeedd4383bd4.test.html
  34. 263 0
      tests/Tools/Jasmine/_Chutzpah.eb034a3e5179d22965cbbc03d189d092.test.html
  35. 207 0
      tests/Tools/Jasmine/chutzpah.json

+ 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.
      */

+ 60 - 4
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,17 @@
          */
         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?: {
@@ -86,6 +97,7 @@
             this._updateGlobalTransformCounter = new PerfCounter();
             this._boundingInfoRecomputeCounter = new PerfCounter();
 
+            this._uid = null;
             this._cachedCanvasGroup = null;
 
             this._profileInfoText = null;
@@ -552,6 +564,10 @@
             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();
         }
 
@@ -848,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.
@@ -991,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 {} 
          */
@@ -1126,6 +1172,8 @@
             this._updateGlobalTransformCounter.addCount(count, false);
         }
 
+        private _uid: string;
+        private _renderObservable: Observable<Canvas2D>;
         private __engineData: Canvas2DEngineBoundData;
         private _interactionEnabled: boolean;
         private _primPointerInfo: PrimitivePointerInfo;
@@ -1322,6 +1370,10 @@
 
             this._initPerfMetrics();
 
+            if (this._renderObservable && this._renderObservable.hasObservers()) {
+                this._renderObservable.notifyObservers(this, Canvas2D.RENDEROBSERVABLE_PRE);
+            }
+
             this._updateCanvasState(false);
 
             this._updateTrackedNodes();
@@ -1339,7 +1391,7 @@
 
             if (this._primPointerInfo.canvasPointerPos) {
                 this._updateIntersectionList(this._primPointerInfo.canvasPointerPos, false, false);
-                this._updateOverStatus(false);   // TODO this._primPointerInfo may not be up to date!
+                this._updateOverStatus(false);
             }
 
             this.engine.setState(false);
@@ -1359,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);
@@ -1598,7 +1654,7 @@
         private static _gradientColorBrushes: StringDictionary<IBrush2D> = new StringDictionary<IBrush2D>();
     }
 
-    @className("WorldSpaceCanvas2D")
+    @className("WorldSpaceCanvas2D", "BABYLON")
     /**
      * Class to create a WorldSpace Canvas2D.
      */
@@ -1718,7 +1774,7 @@
         }
     }
 
-    @className("ScreenSpaceCanvas2D")
+    @className("ScreenSpaceCanvas2D", "BABYLON")
     /**
      * Class to create a ScreenSpace Canvas2D
      */

+ 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.ts

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

+ 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

+ 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
      */

+ 228 - 74
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
          */
@@ -2361,11 +2514,11 @@
                 if (this.owner) {
                     this.owner.boundingInfoRecomputeCounter.addCount(1, false);
                 }
-                if (this.isSizedByContent) {
-                    this._boundingInfo.clear();
-                } else {
+                //if (this.isSizedByContent) {
+                //    this._boundingInfo.clear();
+                //} else {
                     this._boundingInfo.copyFrom(this.levelBoundingInfo);
-                }
+                //}
                 let bi = this._boundingInfo;
 
                 var tps = new BoundingInfo2D();
@@ -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.ts

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

+ 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.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.

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 932 - 294
src/Canvas2d/babylon.smartPropertyPrim.ts


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

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

+ 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);

+ 806 - 0
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", Binding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.heightProperty, "height", Binding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.actualWidthProperty, "actualWidth", Binding.MODE_ONEWAYTOSOURCE);
+            p.createSimpleDataBinding(Prim2DBase.actualHeightProperty, "actualHeight", Binding.MODE_ONEWAYTOSOURCE);
+            p.createSimpleDataBinding(Prim2DBase.marginProperty, "margin", Binding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.paddingProperty, "padding", Binding.MODE_ONEWAY);
+            p.createSimpleDataBinding(Prim2DBase.marginAlignmentProperty, "marginAlignment", Binding.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 abstract get _position(): Vector2;
+        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);
+        abstract get name(): string;
+    }
+
+    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);
+        }
+    }
+
+}

+ 209 - 0
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
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 Binding();
+                binding.propertyPathName = "content";
+                binding.stringFormat = v => `${v}`;
+                binding.dataSource = this;
+                l.createDataBinding(Label.textProperty, binding);
+
+                binding = new Binding();
+                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;
+        }
+    }
+}

+ 77 - 0
src/GUI/babylon.gui.label.ts

@@ -0,0 +1,77 @@
+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");
+
+            return { root: r, contentPlaceholder: r };
+        }
+    }
+}

+ 214 - 0
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 };
+        }
+    }
+}

+ 669 - 0
src/Tools/babylon.observable.ts

@@ -155,4 +155,673 @@
             return result;
         }
     }
+
+    /**
+     * 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>>;
+
+    }
 }

+ 343 - 0
src/Tools/babylon.stringDictionary.ts

@@ -5,6 +5,17 @@
      * The value can be anything including 'null' but except 'undefined'
      */
     export class StringDictionary<T> {
+
+        /**
+         * 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>) {
+            this.clear();
+            source.forEach((t, v) => this.add(t, v));
+        }
+
         /**
          * Get a value based from its key
          * @param key the given key to get the matching value from
@@ -90,6 +101,20 @@
         }
 
         /**
+         * Get the element of the given key and remove it from the dictionary
+         * @param key
+         */
+        public getAndRemove(key: string): T {
+            let val = this.get(key);
+            if (val !== undefined) {
+                delete this._data[key];
+                --this._count;
+                return val;
+            }
+            return null;
+        }
+
+        /**
          * 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
@@ -147,4 +172,322 @@
         private _count = 0;
         private _data = {};
     }
+
+    /**
+     * 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;
+    }
+
+    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>>;
+    }
 }

+ 35 - 3
src/Tools/babylon.tools.ts

@@ -1052,6 +1052,36 @@
         }
 
         /**
+         * This method will return the name of the full name of the class, including its owning module (if any).
+         * It will works only on Javascript basic data types (number, string, ...) and instance of class declared with the @className decorator or implementing a method getClassName():string (in which case the module won't be specified).
+         * @param object the object to get the class name from
+         * @return a string that can have two forms: "moduleName.className" if module was specified when the class' Name was registered or "className" if there was not module specified.
+         */
+        public static getFullClassName(object, isType: boolean = false): string {
+            let className = null;
+            let moduleName = null;
+
+            if (!isType && object.getClassName) {
+                className = object.getClassName();
+            } else {
+                if (object instanceof Object) {
+                    let classObj = isType ? object : Object.getPrototypeOf(object);
+                    className = classObj.constructor["__bjsclassName__"];
+                    moduleName = classObj.constructor["__bjsmoduleName__"];
+                }
+                if (!className) {
+                    className = typeof object;
+                }
+            }
+
+            if (!className) {
+                return null;
+            }
+
+            return ((moduleName != null) ? (moduleName + ".") : "") + className;
+        }
+
+        /**
          * This method can be used with hashCodeFromStream when your input is an array of values that are either: number, string, boolean or custom type implementing the getHashCode():number method.
          * @param array
          */
@@ -1233,14 +1263,16 @@
     }
 
     /**
-     * Use this className as a decorator on a given class definition to add it a name.
+     * Use this className as a decorator on a given class definition to add it a name and optionally its module.
      * You can then use the Tools.getClassName(obj) on an instance to retrieve its class name.
      * This method is the only way to get it done in all cases, even if the .js file declaring the class is minified
-     * @param name
+     * @param name The name of the class, case should be preserved
+     * @param module The name of the Module hosting the class, optional, but strongly recommended to specify if possible. Case should be preserved.
      */
-    export function className(name: string): (target: Object) => void {
+    export function className(name: string, module?: string): (target: Object) => void {
         return (target: Object) => {
             target["__bjsclassName__"] = name;
+            target["__bjsmoduleName__"] = (module != null) ? module : null;
         }
     }
 

+ 114 - 0
tests/Canvas2d/Jasmine/DataBindingTest.js

@@ -0,0 +1,114 @@
+/// <reference path="../../../src/canvas2d/babylon.smartpropertyprim.ts" />
+/// <reference path="testclasses.ts" />
+describe("GUI - Data Binding", function () {
+    it("target update, no indirection", function () {
+        // Create a customer, set its age
+        var c = new BABYLON.Customer();
+        c.age = 18;
+        // Create a View Model and a binding
+        var vm = new BABYLON.CustomerViewModel();
+        vm.createSimpleDataBinding(BABYLON.CustomerViewModel.ageProperty, "age");
+        // Setting a dataSource should setup vm.age with the binding source value
+        vm.dataSource = c;
+        // Check it's ok
+        expect(vm.age).toBe(18);
+        // Change the source value, check the target is updated
+        c.age = 19;
+        expect(vm.age).toBe(19);
+    });
+    it("target update, with indirection", function () {
+        // Create a customer, set its city
+        var c = new BABYLON.Customer();
+        c.mainAddress.city = "Pontault Combault";
+        // Create a View Model and a binding with an indirection
+        var vm = new BABYLON.CustomerViewModel();
+        vm.createSimpleDataBinding(BABYLON.CustomerViewModel.cityProperty, "mainAddress.city");
+        // Setting a dataSource should update the targets
+        vm.dataSource = c;
+        // Check it's ok
+        expect(vm.city).toBe("Pontault Combault", "setting a new dataSource didn't immediately update the target");
+        // Change the source value, check the target is updated
+        c.mainAddress.city = "Paris";
+        expect(vm.city).toBe("Paris", "changing source property didn't update the target property");
+        // Change the address object, target should be updated
+        var address = new BABYLON.Address();
+        address.city = "Seattle";
+        var oldAddress = c.mainAddress;
+        c.mainAddress = address;
+        expect(vm.city).toBe("Seattle", "changing intermediate object (the address) didn't update the target");
+        // Check that if we change again inside Address, it still works
+        c.mainAddress.city = "Redmond";
+        expect(vm.city).toBe("Redmond", "changing final source property didn't change the target");
+        // Now checks that changing the oldAddress city doesn't change the target
+        oldAddress.city = "Berlin";
+        expect(vm.city).not.toBe("Berlin", "Changed old address changed the target, which should not");
+    });
+    it("target, one time update", function () {
+        var c = new BABYLON.Customer();
+        c.firstName = "Loic Baumann";
+        // Create a View Model and a binding with an indirection
+        var vm = new BABYLON.CustomerViewModel();
+        vm.createSimpleDataBinding(BABYLON.CustomerViewModel.firstNameProperty, "firstName");
+        // Setting a dataSource should update the targets
+        vm.dataSource = c;
+        // Check it's ok
+        expect(vm.firstName).toBe("Loic Baumann", "setting a new dataSource didn't immediately update the target with one time binding");
+        // A change of the source shouldn't update the target
+        c.firstName = "Nockawa";
+        expect(vm.firstName).not.toBe("Nockawa", "Changing source property of a One Time binding updated the target, which should not");
+        // A change of dataSource should update the target
+        var c2 = new BABYLON.Customer();
+        c2.firstName = "John";
+        vm.dataSource = c2;
+        expect(vm.firstName).toBe("John", "setting a new dataSource again didn't immediately update the target with one time binding");
+    });
+    it("binding Format", function () {
+        var c = new BABYLON.Customer();
+        c.firstName = "Loic Baumann";
+        c.age = 40;
+        // Create a View Model and a binding with an indirection
+        var vm = new BABYLON.CustomerViewModel();
+        // Setting a dataSource should setup vm.age with the binding source value
+        vm.dataSource = c;
+        // Create the binding and set it up
+        var b = new BABYLON.Binding();
+        b.propertyPathName = "firstName";
+        b.stringFormat = function (v) { return ("My Name is " + v); };
+        vm.createDataBinding(BABYLON.CustomerViewModel.firstNameProperty, b);
+        // Check it's ok
+        expect(vm.firstName).toBe("My Name is Loic Baumann", "binding string format doesn't work");
+        // Bind age to city with "Age: $value" format
+        b = new BABYLON.Binding();
+        b.propertyPathName = "age";
+        b.stringFormat = function (v) { return ("Age: " + v); };
+        vm.createDataBinding(BABYLON.CustomerViewModel.cityProperty, b);
+        // Check it's ok
+        expect(vm.city).toBe("Age: 40", "binding string format doesn't work on non string source type");
+    });
+    it("binding custom source", function () {
+        var c1 = new BABYLON.Customer();
+        c1.firstName = "Loic Baumann";
+        c1.age = 40;
+        var c2 = new BABYLON.Customer();
+        c2.firstName = "John Doe";
+        c2.age = 20;
+        // Create a View Model and a binding with an indirection
+        var vm = new BABYLON.CustomerViewModel();
+        // Setting a dataSource should setup vm.age with the binding source value
+        vm.dataSource = c1;
+        // Create the binding and set it up
+        var b = new BABYLON.Binding();
+        b.propertyPathName = "firstName";
+        vm.createDataBinding(BABYLON.CustomerViewModel.firstNameProperty, b);
+        // Bind age with a custom source
+        b = new BABYLON.Binding();
+        b.propertyPathName = "age";
+        b.dataSource = c2;
+        vm.createDataBinding(BABYLON.CustomerViewModel.ageProperty, b);
+        // Check it's ok
+        expect(vm.firstName).toBe("Loic Baumann", "binding string format doesn't work");
+        // Check it's ok
+        expect(vm.age).toBe(20, "binding string format doesn't work on non string source type");
+    });
+});
+//# sourceMappingURL=DataBindingTest.js.map

+ 163 - 0
tests/Canvas2d/Jasmine/DataBindingTest.ts

@@ -0,0 +1,163 @@
+/// <reference path="../../../src/canvas2d/babylon.smartpropertyprim.ts" />
+/// <reference path="testclasses.ts" />
+
+
+describe("GUI - Data Binding", () => {
+
+    it("target update, no indirection",
+        () => {
+
+            // Create a customer, set its age
+            let c = new BABYLON.Customer();
+            c.age = 18;
+
+            // Create a View Model and a binding
+            let vm = new BABYLON.CustomerViewModel();
+            vm.createSimpleDataBinding(BABYLON.CustomerViewModel.ageProperty, "age");
+
+            // Setting a dataSource should setup vm.age with the binding source value
+            vm.dataSource = c;
+
+            // Check it's ok
+            expect(vm.age).toBe(18);
+
+            // Change the source value, check the target is updated
+            c.age = 19;
+            expect(vm.age).toBe(19);
+        }
+    );
+
+    it("target update, with indirection",
+        () => {
+
+            // Create a customer, set its city
+            let c = new BABYLON.Customer();
+            c.mainAddress.city = "Pontault Combault";
+
+            // Create a View Model and a binding with an indirection
+            let vm = new BABYLON.CustomerViewModel();
+            vm.createSimpleDataBinding(BABYLON.CustomerViewModel.cityProperty, "mainAddress.city");
+
+            // Setting a dataSource should update the targets
+            vm.dataSource = c;
+
+            // Check it's ok
+            expect(vm.city).toBe("Pontault Combault", "setting a new dataSource didn't immediately update the target");
+
+            // Change the source value, check the target is updated
+            c.mainAddress.city = "Paris";
+            expect(vm.city).toBe("Paris", "changing source property didn't update the target property");
+
+            // Change the address object, target should be updated
+            let address = new BABYLON.Address();
+            address.city = "Seattle";
+
+            let oldAddress = c.mainAddress;
+            c.mainAddress = address;
+            expect(vm.city).toBe("Seattle", "changing intermediate object (the address) didn't update the target");
+
+            // Check that if we change again inside Address, it still works
+            c.mainAddress.city = "Redmond";
+            expect(vm.city).toBe("Redmond", "changing final source property didn't change the target");
+
+            // Now checks that changing the oldAddress city doesn't change the target
+            oldAddress.city = "Berlin";
+            expect(vm.city).not.toBe("Berlin", "Changed old address changed the target, which should not");
+        }
+    );
+
+    it("target, one time update",
+        () => {
+            let c = new BABYLON.Customer();
+            c.firstName = "Loic Baumann";
+
+            // Create a View Model and a binding with an indirection
+            let vm = new BABYLON.CustomerViewModel();
+            vm.createSimpleDataBinding(BABYLON.CustomerViewModel.firstNameProperty, "firstName");
+
+            // Setting a dataSource should update the targets
+            vm.dataSource = c;
+
+            // Check it's ok
+            expect(vm.firstName).toBe("Loic Baumann", "setting a new dataSource didn't immediately update the target with one time binding");
+
+            // A change of the source shouldn't update the target
+            c.firstName = "Nockawa";
+            expect(vm.firstName).not.toBe("Nockawa", "Changing source property of a One Time binding updated the target, which should not");
+
+            // A change of dataSource should update the target
+            let c2 = new BABYLON.Customer();
+            c2.firstName = "John";
+
+            vm.dataSource = c2;
+            expect(vm.firstName).toBe("John", "setting a new dataSource again didn't immediately update the target with one time binding");
+        }
+    );
+
+    it("binding Format", () => {
+        let c = new BABYLON.Customer();
+        c.firstName = "Loic Baumann";
+        c.age = 40;
+
+        // Create a View Model and a binding with an indirection
+        let vm = new BABYLON.CustomerViewModel();
+
+        // Setting a dataSource should setup vm.age with the binding source value
+        vm.dataSource = c;
+
+        // Create the binding and set it up
+        let b = new BABYLON.Binding();
+        b.propertyPathName = "firstName";
+        b.stringFormat = v => `My Name is ${v}`;
+        vm.createDataBinding(BABYLON.CustomerViewModel.firstNameProperty, b);
+
+        // Check it's ok
+        expect(vm.firstName).toBe("My Name is Loic Baumann", "binding string format doesn't work");
+
+        // Bind age to city with "Age: $value" format
+        b = new BABYLON.Binding();
+        b.propertyPathName = "age";
+        b.stringFormat = v => `Age: ${v}`;
+        vm.createDataBinding(BABYLON.CustomerViewModel.cityProperty, b);
+
+        // Check it's ok
+        expect(vm.city).toBe("Age: 40", "binding string format doesn't work on non string source type");
+    });
+
+    it("binding custom source", () => {
+        let c1 = new BABYLON.Customer();
+        c1.firstName = "Loic Baumann";
+        c1.age = 40;
+
+        let c2 = new BABYLON.Customer();
+        c2.firstName = "John Doe";
+        c2.age = 20;
+
+        // Create a View Model and a binding with an indirection
+        let vm = new BABYLON.CustomerViewModel();
+
+        // Setting a dataSource should setup vm.age with the binding source value
+        vm.dataSource = c1;
+
+        // Create the binding and set it up
+        let b = new BABYLON.Binding();
+        b.propertyPathName = "firstName";
+        vm.createDataBinding(BABYLON.CustomerViewModel.firstNameProperty, b);
+
+        // Bind age with a custom source
+        b = new BABYLON.Binding();
+        b.propertyPathName = "age";
+        b.dataSource = c2;
+        vm.createDataBinding(BABYLON.CustomerViewModel.ageProperty, b);
+
+
+        // Check it's ok
+        expect(vm.firstName).toBe("Loic Baumann", "binding string format doesn't work");
+
+
+        // Check it's ok
+        expect(vm.age).toBe(20, "binding string format doesn't work on non string source type");
+    });
+
+});
+

+ 173 - 0
tests/Canvas2d/Jasmine/New Text Document.txt

@@ -0,0 +1,173 @@
+module BABYLON {
+
+    @className("Address")
+    export class Address extends PropertyChangedBase {
+
+        public get street(): string {
+            return this._street;
+        }
+
+        public set street(value: string) {
+            if (value === this._street) {
+                return;
+            }
+
+            let old = this._street;
+            this._street = value;
+            this.onPropertyChanged("street", old, value);
+        }
+
+        public get city(): string {
+            return this._city;
+        }
+
+        public set city(value: string) {
+            if (value === this._city) {
+                return;
+            }
+
+            let old = this._city;
+            this._city = value;
+            this.onPropertyChanged("city", old, value);
+        }
+
+
+        public get postalCode(): string {
+            return this._postalCode;
+        }
+
+        public set postalCode(value: string) {
+            if (value === this._postalCode) {
+                return;
+            }
+
+            let old = this._postalCode;
+            this._postalCode = value;
+            this.onPropertyChanged("postalCode", old, value);
+        }
+
+        private _street: string;
+        private _city: string;
+        private _postalCode: string;
+    }
+
+    @className("Customer")
+    export class Customer extends PropertyChangedBase {
+
+        /**
+            * Customer First Name
+        **/
+        public get firstName(): string {
+            return this._firstName;
+        }
+
+        public set firstName(value: string) {
+            if (value === this._firstName) {
+                return;
+            }
+
+            let old = this._firstName;
+            this._firstName = value;
+            this.onPropertyChanged("firstName", old, value);
+        }
+
+        /**
+            * Customer Last Name
+        **/
+        public get lastName(): string {
+            return this._lastName;
+        }
+
+        public set lastName(value: string) {
+            if (value === this._lastName) {
+                return;
+            }
+
+            let old = this._lastName;
+            this._lastName = value;
+            this.onPropertyChanged("lastName", old, value);
+        }
+
+        /**
+            * Customer Main Address
+        **/
+        public get mainAddress(): Address {
+            if (!this._mainAddress) {
+                this._mainAddress = new Address();
+            }
+            return this._mainAddress;
+        }
+
+        public set mainAddress(value: Address) {
+            if (value === this._mainAddress) {
+                return;
+            }
+
+            let old = this._mainAddress;
+            this._mainAddress = value;
+            this.onPropertyChanged("mainAddress", old, value);
+        }
+
+        public get age(): number {
+            return this._age;
+        }
+
+        public set age(value: number) {
+            if (value === this._age) {
+                return;
+            }
+
+            let old = this._age;
+            this._age = value;
+            this.onPropertyChanged("age", old, value);
+        }
+
+        private _firstName: string;
+        private _lastName: string;
+        private _mainAddress: Address;
+        private _age: number;
+    }
+
+    @className("CustomerViewModel")
+    export class CustomerViewModel extends SmartPropertyBase {
+        public static firstNameProperty: Prim2DPropInfo;
+        public static ageProperty: Prim2DPropInfo;
+        public static cityProperty: Prim2DPropInfo;
+
+        constructor() {
+            super();
+        }
+
+        @BABYLON.dependencyProperty(0, pi => CustomerViewModel.ageProperty = pi)
+        public get age(): number {
+            return this._age;
+        }
+
+        public set age(value: number) {
+            this._age = value;
+        }
+
+        @BABYLON.dependencyProperty(1, pi => CustomerViewModel.cityProperty = pi)
+        public get city(): string {
+            return this._city;
+        }
+
+        public set city(value: string) {
+            this._city = value;
+        }
+
+        @BABYLON.dependencyProperty(2, pi => CustomerViewModel.firstNameProperty = pi, Binding.MODE_ONETIME)
+        public get firstName(): string {
+            return this._firstName;
+        }
+
+        public set firstName(value: string) {
+            this._firstName = value;
+        }
+
+        private _age: number;
+        private _city: string;
+        private _firstName: string;
+    }
+
+}

+ 173 - 0
tests/Canvas2d/Jasmine/TestClasses.ts

@@ -0,0 +1,173 @@
+module BABYLON {
+
+    @className("Address")
+    export class Address extends PropertyChangedBase {
+
+        public get street(): string {
+            return this._street;
+        }
+
+        public set street(value: string) {
+            if (value === this._street) {
+                return;
+            }
+
+            let old = this._street;
+            this._street = value;
+            this.onPropertyChanged("street", old, value);
+        }
+
+        public get city(): string {
+            return this._city;
+        }
+
+        public set city(value: string) {
+            if (value === this._city) {
+                return;
+            }
+
+            let old = this._city;
+            this._city = value;
+            this.onPropertyChanged("city", old, value);
+        }
+
+
+        public get postalCode(): string {
+            return this._postalCode;
+        }
+
+        public set postalCode(value: string) {
+            if (value === this._postalCode) {
+                return;
+            }
+
+            let old = this._postalCode;
+            this._postalCode = value;
+            this.onPropertyChanged("postalCode", old, value);
+        }
+
+        private _street: string;
+        private _city: string;
+        private _postalCode: string;
+    }
+
+    @className("Customer")
+    export class Customer extends PropertyChangedBase {
+
+        /**
+            * Customer First Name
+        **/
+        public get firstName(): string {
+            return this._firstName;
+        }
+
+        public set firstName(value: string) {
+            if (value === this._firstName) {
+                return;
+            }
+
+            let old = this._firstName;
+            this._firstName = value;
+            this.onPropertyChanged("firstName", old, value);
+        }
+
+        /**
+            * Customer Last Name
+        **/
+        public get lastName(): string {
+            return this._lastName;
+        }
+
+        public set lastName(value: string) {
+            if (value === this._lastName) {
+                return;
+            }
+
+            let old = this._lastName;
+            this._lastName = value;
+            this.onPropertyChanged("lastName", old, value);
+        }
+
+        /**
+            * Customer Main Address
+        **/
+        public get mainAddress(): Address {
+            if (!this._mainAddress) {
+                this._mainAddress = new Address();
+            }
+            return this._mainAddress;
+        }
+
+        public set mainAddress(value: Address) {
+            if (value === this._mainAddress) {
+                return;
+            }
+
+            let old = this._mainAddress;
+            this._mainAddress = value;
+            this.onPropertyChanged("mainAddress", old, value);
+        }
+
+        public get age(): number {
+            return this._age;
+        }
+
+        public set age(value: number) {
+            if (value === this._age) {
+                return;
+            }
+
+            let old = this._age;
+            this._age = value;
+            this.onPropertyChanged("age", old, value);
+        }
+
+        private _firstName: string;
+        private _lastName: string;
+        private _mainAddress: Address;
+        private _age: number;
+    }
+
+    @className("CustomerViewModel")
+    export class CustomerViewModel extends SmartPropertyBase {
+        public static firstNameProperty: Prim2DPropInfo;
+        public static ageProperty: Prim2DPropInfo;
+        public static cityProperty: Prim2DPropInfo;
+
+        constructor() {
+            super();
+        }
+
+        @BABYLON.dependencyProperty(0, pi => CustomerViewModel.ageProperty = pi)
+        public get age(): number {
+            return this._age;
+        }
+
+        public set age(value: number) {
+            this._age = value;
+        }
+
+        @BABYLON.dependencyProperty(1, pi => CustomerViewModel.cityProperty = pi)
+        public get city(): string {
+            return this._city;
+        }
+
+        public set city(value: string) {
+            this._city = value;
+        }
+
+        @BABYLON.dependencyProperty(2, pi => CustomerViewModel.firstNameProperty = pi, Binding.MODE_ONETIME)
+        public get firstName(): string {
+            return this._firstName;
+        }
+
+        public set firstName(value: string) {
+            this._firstName = value;
+        }
+
+        private _age: number;
+        private _city: string;
+        private _firstName: string;
+    }
+
+}

+ 264 - 0
tests/Canvas2d/Jasmine/_Chutzpah.7b5adbce42315517e53da0a9565b4f12.test.html

@@ -0,0 +1,264 @@
+<!-- saved from url=(0014)about:internet -->
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8" />
+    <title>Jasmine Spec Runner</title>
+    <script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/chutzpah_boot.js"></script>
+<link rel="stylesheet" type="text/css" href="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine.css"/>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine.js"></script>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine-html.js"></script>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/boot.js"></script>
+<link rel="shortcut icon" type="image/png" href="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine_favicon.png"/>
+
+    
+    
+    <script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Math/babylon.math.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Math/babylon.math.simd.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.decorators.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.observable.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.database.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.tga.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.dds.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.stringDictionary.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.smartArray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.dynamicFloatArray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.rectPackingMap.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.alphaCullingState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.depthCullingState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.stencilState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.engine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.node.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.filesInput.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.pickingInfo.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingSphere.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingBox.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingInfo.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.ray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.abstractMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.light.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.pointLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.spotLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.hemisphericLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.directionalLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/Shadows/babylon.shadowGenerator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collider.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collisionCoordinator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collisionWorker.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.camera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.camerainputsmanager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.mouse.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.keyboard.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.touch.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.deviceorientation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.gamepad.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.virtualjoystick.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.keyboard.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.mousewheel.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.pointers.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.gamepad.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.targetCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.followCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.freeCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.freeCameraInputsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.touchCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.arcRotateCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.arcRotateCameraInputsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.universalCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.deviceOrientationCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.gamepads.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.gamepadCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.renderingManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.renderingGroup.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.scene.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.buffer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.vertexBuffer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.instancedMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.mesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshBuilder.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.groundMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.subMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.baseTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.texture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.cubeTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.renderTargetTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/procedurals/babylon.proceduralTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/procedurals/babylon.customProceduralTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.mirrorTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.refractionTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.dynamicTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.videoTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.fontTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.mapTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.effect.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.materialHelper.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.fresnelParameters.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.material.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.standardMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.pbrMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.multiMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.bounding2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.canvas2dLayoutEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.brushes2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.smartPropertyPrim.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.prim2dBase.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.modelRenderCache.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.renderablePrim2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.shape2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.group2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.rectangle2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.sprite2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.text2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.canvas2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.ellipse2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.lines2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.worldspacecanvas2dNode.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Loading/babylon.sceneLoader.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Loading/Plugins/babylon.babylonFileLoader.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Sprites/babylon.spriteManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Sprites/babylon.sprite.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Layer/babylon.layer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.particle.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.particleSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.solidParticle.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.solidParticleSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.animation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.animatable.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.easing.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/Octrees/babylon.octree.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/Octrees/babylon.octreeBlock.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Bones/babylon.bone.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Bones/babylon.skeleton.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.postProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.postProcessManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.passPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.blurPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.refractionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.blackAndWhitePostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.convolutionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.filterPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.fxaaPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/LensFlare/babylon.lensFlare.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/LensFlare/babylon.lensFlareSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/Plugins/babylon.cannonJSPlugin.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/Plugins/babylon.oimoJSPlugin.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsImpostor.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsJoint.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.sceneSerializer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.csg.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.vrDistortionCorrectionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.virtualJoystick.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.virtualJoysticksCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.shaderMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.mesh.vertexData.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.anaglyphPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tags.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.andOrNotEvaluator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPass.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderEffect.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPipelineManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.displayPassPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.boundingBoxRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.condition.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.action.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.actionManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.interpolateValueAction.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.directActions.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.geometry.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.linesMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.outlineRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.assetsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.vrCameraMetrics.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.vrDeviceOrientationCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.webVRCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.sceneOptimizer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.earcut.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshLODLevel.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.audioEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.sound.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.soundtrack.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Debug/babylon.skeletonViewer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Debug/babylon.debugLayer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/Textures/babylon.rawTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.polygonMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshSimplification.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.analyser.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.depthRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.ssaoRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.volumetricLightScatteringPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.lensRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.colorCorrectionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.stereoscopicInterlacePostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.stereoscopicCameras.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.hdrRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.edgesRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.loadingScreen.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Probes/babylon.reflectionProbe.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.pmremGenerator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.cubemapToSphericalPolynomial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.panoramaToCubemap.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.hdr.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/materials/textures/babylon.hdrCubeTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/Textures/babylon.colorGradingTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.colorcurves.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/tests/Canvas2d/Jasmine/TestClasses.js"></script>
+
+    <script type="text/javascript" src="file:///c:/git/4/babylon.js/tests/canvas2d/jasmine/DataBindingTest.js"></script>
+
+    <script type="text/javascript">
+
+        (function () {
+
+            var amdTestPaths = [];
+            window.chutzpah.boot(amdTestPaths);
+
+            if (window.chutzpah.usingModuleLoader) {
+                if("") {
+                    window.chutzpah.amdConfig({
+                        baseUrl: ""
+                    });
+                }
+
+                window.chutzpah.amdConfig({
+                    map: {
+                        '*': {
+                            
+                            }
+                    }
+                });
+
+                window.chutzpah.amdStart = function() {
+                    window.chutzpah.amdImport(amdTestPaths, function () {
+
+                        console.log("!!_!! Stating Jasmine from AMD callback...");
+                        window.initializeJasmine();
+                    });
+                };
+
+                if(window.chutzpah.amdAutoStart) {
+                    window.chutzpah.amdStart();
+                }
+
+            }
+            else {
+                var currentWindowOnload = window.onload;
+
+                window.onload = function() {
+                    if (currentWindowOnload) {
+                        currentWindowOnload();
+                    }
+                    window.initializeJasmine();
+                };
+            }
+
+        })();
+    </script>
+</head>
+<body>
+    
+</body>
+</html>

+ 207 - 0
tests/Canvas2d/Jasmine/chutzpah.json

@@ -0,0 +1,207 @@
+{
+    "Compile": {
+        "Mode": "External",
+        "Extensions": [ ".ts" ],
+        "ExtensionsWithNoOutput": [ ".d.ts" ]
+    },
+    "References": [
+        { "Path": "../../../src/Math/babylon.math.js" },
+        { "Path": "../../../src/Math/babylon.math.simd.js" },
+        { "Path": "../../../src/Tools/babylon.decorators.js" },
+        { "Path": "../../../src/Tools/babylon.observable.js" },
+        { "Path": "../../../src/Tools/babylon.database.js" },
+        { "Path": "../../../src/Tools/babylon.tools.tga.js" },
+        { "Path": "../../../src/Tools/babylon.tools.dds.js" },
+        { "Path": "../../../src/Tools/babylon.stringDictionary.js" },
+        { "Path": "../../../src/Tools/babylon.smartArray.js" },
+        { "Path": "../../../src/Tools/babylon.dynamicFloatArray.js" },
+        { "Path": "../../../src/Tools/babylon.rectPackingMap.js" },
+        { "Path": "../../../src/Tools/babylon.tools.js" },
+        { "Path": "../../../src/states/babylon.alphaCullingState.js" },
+        { "Path": "../../../src/states/babylon.depthCullingState.js" },
+        { "Path": "../../../src/states/babylon.stencilState.js" },
+        { "Path": "../../../src/babylon.engine.js" },
+        { "Path": "../../../src/babylon.node.js" },
+        { "Path": "../../../src/Tools/babylon.filesInput.js" },
+        { "Path": "../../../src/Collisions/babylon.pickingInfo.js" },
+        { "Path": "../../../src/Culling/babylon.boundingSphere.js" },
+        { "Path": "../../../src/Culling/babylon.boundingBox.js" },
+        { "Path": "../../../src/Culling/babylon.boundingInfo.js" },
+        { "Path": "../../../src/Culling/babylon.ray.js" },
+        { "Path": "../../../src/Mesh/babylon.abstractMesh.js" },
+        { "Path": "../../../src/Lights/babylon.light.js" },
+        { "Path": "../../../src/Lights/babylon.pointLight.js" },
+        { "Path": "../../../src/Lights/babylon.spotLight.js" },
+        { "Path": "../../../src/Lights/babylon.hemisphericLight.js" },
+        { "Path": "../../../src/Lights/babylon.directionalLight.js" },
+        { "Path": "../../../src/Lights/Shadows/babylon.shadowGenerator.js" },
+        { "Path": "../../../src/Collisions/babylon.collider.js" },
+        { "Path": "../../../src/Collisions/babylon.collisionCoordinator.js" },
+        { "Path": "../../../src/Collisions/babylon.collisionWorker.js" },
+        { "Path": "../../../src/Cameras/babylon.camera.js" },
+        { "Path": "../../../src/Cameras/babylon.camerainputsmanager.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.mouse.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.keyboard.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.touch.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.deviceorientation.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.gamepad.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.virtualjoystick.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.keyboard.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.mousewheel.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.pointers.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.gamepad.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js" },
+        { "Path": "../../../src/Cameras/babylon.targetCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.followCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.freeCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.freeCameraInputsManager.js" },
+        { "Path": "../../../src/Cameras/babylon.touchCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.arcRotateCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.arcRotateCameraInputsManager.js" },
+        { "Path": "../../../src/Cameras/babylon.universalCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.deviceOrientationCamera.js" },
+        { "Path": "../../../src/Tools/babylon.gamepads.js" },
+        { "Path": "../../../src/Cameras/babylon.gamepadCamera.js" },
+        { "Path": "../../../src/Rendering/babylon.renderingManager.js" },
+        { "Path": "../../../src/Rendering/babylon.renderingGroup.js" },
+        { "Path": "../../../src/babylon.scene.js" },
+        { "Path": "../../../src/Mesh/babylon.buffer.js" },
+        { "Path": "../../../src/Mesh/babylon.vertexBuffer.js" },
+        { "Path": "../../../src/Mesh/babylon.instancedMesh.js" },
+        { "Path": "../../../src/Mesh/babylon.mesh.js" },
+        { "Path": "../../../src/Mesh/babylon.meshBuilder.js" },
+        { "Path": "../../../src/Mesh/babylon.groundMesh.js" },
+        { "Path": "../../../src/Mesh/babylon.subMesh.js" },
+        { "Path": "../../../src/Materials/textures/babylon.baseTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.texture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.cubeTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.renderTargetTexture.js" },
+        { "Path": "../../../src/Materials/textures/procedurals/babylon.proceduralTexture.js" },
+        { "Path": "../../../src/Materials/textures/procedurals/babylon.customProceduralTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.mirrorTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.refractionTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.dynamicTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.videoTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.fontTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.mapTexture.js" },
+        { "Path": "../../../src/Materials/babylon.effect.js" },
+        { "Path": "../../../src/Materials/babylon.materialHelper.js" },
+        { "Path": "../../../src/Materials/babylon.fresnelParameters.js" },
+        { "Path": "../../../src/Materials/babylon.material.js" },
+        { "Path": "../../../src/Materials/babylon.standardMaterial.js" },
+        { "Path": "../../../src/Materials/babylon.pbrMaterial.js" },
+        { "Path": "../../../src/Materials/babylon.multiMaterial.js" },
+        { "Path": "../../../src/Canvas2d/babylon.bounding2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.canvas2dLayoutEngine.js" },
+        { "Path": "../../../src/Canvas2d/babylon.brushes2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.smartPropertyPrim.js" },
+        { "Path": "../../../src/Canvas2d/babylon.prim2dBase.js" },
+        { "Path": "../../../src/Canvas2d/babylon.modelRenderCache.js" },
+        { "Path": "../../../src/Canvas2d/babylon.renderablePrim2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.shape2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.group2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.rectangle2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.sprite2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.text2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.canvas2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.ellipse2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.lines2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.worldspacecanvas2dNode.js" },
+        { "Path": "../../../src/Loading/babylon.sceneLoader.js" },
+        { "Path": "../../../src/Loading/Plugins/babylon.babylonFileLoader.js" },
+        { "Path": "../../../src/Sprites/babylon.spriteManager.js" },
+        { "Path": "../../../src/Sprites/babylon.sprite.js" },
+        { "Path": "../../../src/Layer/babylon.layer.js" },
+        { "Path": "../../../src/Particles/babylon.particle.js" },
+        { "Path": "../../../src/Particles/babylon.particleSystem.js" },
+        { "Path": "../../../src/Particles/babylon.solidParticle.js" },
+        { "Path": "../../../src/Particles/babylon.solidParticleSystem.js" },
+        { "Path": "../../../src/Animations/babylon.animation.js" },
+        { "Path": "../../../src/Animations/babylon.animatable.js" },
+        { "Path": "../../../src/Animations/babylon.easing.js" },
+        { "Path": "../../../src/Culling/Octrees/babylon.octree.js" },
+        { "Path": "../../../src/Culling/Octrees/babylon.octreeBlock.js" },
+        { "Path": "../../../src/Bones/babylon.bone.js" },
+        { "Path": "../../../src/Bones/babylon.skeleton.js" },
+        { "Path": "../../../src/PostProcess/babylon.postProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.postProcessManager.js" },
+        { "Path": "../../../src/PostProcess/babylon.passPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.blurPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.refractionPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.blackAndWhitePostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.convolutionPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.filterPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.fxaaPostProcess.js" },
+        { "Path": "../../../src/LensFlare/babylon.lensFlare.js" },
+        { "Path": "../../../src/LensFlare/babylon.lensFlareSystem.js" },
+        { "Path": "../../../src/Physics/Plugins/babylon.cannonJSPlugin.js" },
+        { "Path": "../../../src/Physics/Plugins/babylon.oimoJSPlugin.js" },
+        { "Path": "../../../src/Physics/babylon.physicsImpostor.js" },
+        { "Path": "../../../src/Physics/babylon.physicsEngine.js" },
+        { "Path": "../../../src/Physics/babylon.physicsJoint.js" },
+        { "Path": "../../../src/Tools/babylon.sceneSerializer.js" },
+        { "Path": "../../../src/Mesh/babylon.csg.js" },
+        { "Path": "../../../src/PostProcess/babylon.vrDistortionCorrectionPostProcess.js" },
+        { "Path": "../../../src/Tools/babylon.virtualJoystick.js" },
+        { "Path": "../../../src/Cameras/babylon.virtualJoysticksCamera.js" },
+        { "Path": "../../../src/Materials/babylon.shaderMaterial.js" },
+        { "Path": "../../../src/Mesh/babylon.mesh.vertexData.js" },
+        { "Path": "../../../src/PostProcess/babylon.anaglyphPostProcess.js" },
+        { "Path": "../../../src/Tools/babylon.tags.js" },
+        { "Path": "../../../src/Tools/babylon.andOrNotEvaluator.js" },
+        { "Path": "../../../src/PostProcess/RenderPipeline/babylon.postProcessRenderPass.js" },
+        { "Path": "../../../src/PostProcess/RenderPipeline/babylon.postProcessRenderEffect.js" },
+        { "Path": "../../../src/PostProcess/RenderPipeline/babylon.postProcessRenderPipeline.js" },
+        { "Path": "../../../src/PostProcess/RenderPipeline/babylon.postProcessRenderPipelineManager.js" },
+        { "Path": "../../../src/PostProcess/babylon.displayPassPostProcess.js" },
+        { "Path": "../../../src/Rendering/babylon.boundingBoxRenderer.js" },
+        { "Path": "../../../src/Actions/babylon.condition.js" },
+        { "Path": "../../../src/Actions/babylon.action.js" },
+        { "Path": "../../../src/Actions/babylon.actionManager.js" },
+        { "Path": "../../../src/Actions/babylon.interpolateValueAction.js" },
+        { "Path": "../../../src/Actions/babylon.directActions.js" },
+        { "Path": "../../../src/Mesh/babylon.geometry.js" },
+        { "Path": "../../../src/Mesh/babylon.linesMesh.js" },
+        { "Path": "../../../src/Rendering/babylon.outlineRenderer.js" },
+        { "Path": "../../../src/Tools/babylon.assetsManager.js" },
+        { "Path": "../../../src/Cameras/VR/babylon.vrCameraMetrics.js" },
+        { "Path": "../../../src/Cameras/VR/babylon.vrDeviceOrientationCamera.js" },
+        { "Path": "../../../src/Cameras/VR/babylon.webVRCamera.js" },
+        { "Path": "../../../src/Tools/babylon.sceneOptimizer.js" },
+        { "Path": "../../../src/Tools/babylon.earcut.js" },
+        { "Path": "../../../src/Mesh/babylon.meshLODLevel.js" },
+        { "Path": "../../../src/Audio/babylon.audioEngine.js" },
+        { "Path": "../../../src/Audio/babylon.sound.js" },
+        { "Path": "../../../src/Audio/babylon.soundtrack.js" },
+        { "Path": "../../../src/Debug/babylon.skeletonViewer.js" },
+        { "Path": "../../../src/Debug/babylon.debugLayer.js" },
+        { "Path": "../../../src/Materials/Textures/babylon.rawTexture.js" },
+        { "Path": "../../../src/Mesh/babylon.polygonMesh.js" },
+        { "Path": "../../../src/Mesh/babylon.meshSimplification.js" },
+        { "Path": "../../../src/Audio/babylon.analyser.js" },
+        { "Path": "../../../src/Rendering/babylon.depthRenderer.js" },
+        { "Path": "../../../src/PostProcess/babylon.ssaoRenderingPipeline.js" },
+        { "Path": "../../../src/PostProcess/babylon.volumetricLightScatteringPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.lensRenderingPipeline.js" },
+        { "Path": "../../../src/PostProcess/babylon.colorCorrectionPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.stereoscopicInterlacePostProcess.js" },
+        { "Path": "../../../src/Cameras/babylon.stereoscopicCameras.js" },
+        { "Path": "../../../src/PostProcess/babylon.hdrRenderingPipeline.js" },
+        { "Path": "../../../src/Rendering/babylon.edgesRenderer.js" },
+        { "Path": "../../../src/Tools/babylon.loadingScreen.js" },
+        { "Path": "../../../src/Probes/babylon.reflectionProbe.js" },
+        { "Path": "../../../src/tools/hdr/babylon.tools.pmremGenerator.js" },
+        { "Path": "../../../src/tools/hdr/babylon.tools.cubemapToSphericalPolynomial.js" },
+        { "Path": "../../../src/tools/hdr/babylon.tools.panoramaToCubemap.js" },
+        { "Path": "../../../src/tools/hdr/babylon.tools.hdr.js" },
+        { "Path": "../../../src/materials/textures/babylon.hdrCubeTexture.js" },
+        { "Path": "../../../src/Materials/Textures/babylon.colorGradingTexture.js" },
+        { "Path": "../../../src/Materials/babylon.colorcurves.js" },
+        { "Path": "./TestClasses.js" }
+    ],
+    "Tests": [
+        {
+            "Includes": [ "*.ts" ]
+        }
+    ]
+}

+ 204 - 0
tests/Canvas2d/Jasmine/testclasses.js

@@ -0,0 +1,204 @@
+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 Address = (function (_super) {
+        __extends(Address, _super);
+        function Address() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Address.prototype, "street", {
+            get: function () {
+                return this._street;
+            },
+            set: function (value) {
+                if (value === this._street) {
+                    return;
+                }
+                var old = this._street;
+                this._street = value;
+                this.onPropertyChanged("street", old, value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Address.prototype, "city", {
+            get: function () {
+                return this._city;
+            },
+            set: function (value) {
+                if (value === this._city) {
+                    return;
+                }
+                var old = this._city;
+                this._city = value;
+                this.onPropertyChanged("city", old, value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Address.prototype, "postalCode", {
+            get: function () {
+                return this._postalCode;
+            },
+            set: function (value) {
+                if (value === this._postalCode) {
+                    return;
+                }
+                var old = this._postalCode;
+                this._postalCode = value;
+                this.onPropertyChanged("postalCode", old, value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Address = __decorate([
+            BABYLON.className("Address")
+        ], Address);
+        return Address;
+    }(BABYLON.PropertyChangedBase));
+    BABYLON.Address = Address;
+    var Customer = (function (_super) {
+        __extends(Customer, _super);
+        function Customer() {
+            _super.apply(this, arguments);
+        }
+        Object.defineProperty(Customer.prototype, "firstName", {
+            /**
+                * Customer First Name
+            **/
+            get: function () {
+                return this._firstName;
+            },
+            set: function (value) {
+                if (value === this._firstName) {
+                    return;
+                }
+                var old = this._firstName;
+                this._firstName = value;
+                this.onPropertyChanged("firstName", old, value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Customer.prototype, "lastName", {
+            /**
+                * Customer Last Name
+            **/
+            get: function () {
+                return this._lastName;
+            },
+            set: function (value) {
+                if (value === this._lastName) {
+                    return;
+                }
+                var old = this._lastName;
+                this._lastName = value;
+                this.onPropertyChanged("lastName", old, value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Customer.prototype, "mainAddress", {
+            /**
+                * Customer Main Address
+            **/
+            get: function () {
+                if (!this._mainAddress) {
+                    this._mainAddress = new Address();
+                }
+                return this._mainAddress;
+            },
+            set: function (value) {
+                if (value === this._mainAddress) {
+                    return;
+                }
+                var old = this._mainAddress;
+                this._mainAddress = value;
+                this.onPropertyChanged("mainAddress", old, value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Customer.prototype, "age", {
+            get: function () {
+                return this._age;
+            },
+            set: function (value) {
+                if (value === this._age) {
+                    return;
+                }
+                var old = this._age;
+                this._age = value;
+                this.onPropertyChanged("age", old, value);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Customer = __decorate([
+            BABYLON.className("Customer")
+        ], Customer);
+        return Customer;
+    }(BABYLON.PropertyChangedBase));
+    BABYLON.Customer = Customer;
+    var CustomerViewModel = (function (_super) {
+        __extends(CustomerViewModel, _super);
+        function CustomerViewModel() {
+            _super.call(this);
+        }
+        Object.defineProperty(CustomerViewModel.prototype, "age", {
+            get: function () {
+                return this._age;
+            },
+            set: function (value) {
+                this._age = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(CustomerViewModel.prototype, "city", {
+            get: function () {
+                return this._city;
+            },
+            set: function (value) {
+                this._city = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(CustomerViewModel.prototype, "firstName", {
+            get: function () {
+                return this._firstName;
+            },
+            set: function (value) {
+                this._firstName = value;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        __decorate([
+            BABYLON.dependencyProperty(0, function (pi) { return CustomerViewModel.ageProperty = pi; })
+        ], CustomerViewModel.prototype, "age", null);
+        __decorate([
+            BABYLON.dependencyProperty(1, function (pi) { return CustomerViewModel.cityProperty = pi; })
+        ], CustomerViewModel.prototype, "city", null);
+        __decorate([
+            BABYLON.dependencyProperty(2, function (pi) { return CustomerViewModel.firstNameProperty = pi; }, BABYLON.Binding.MODE_ONETIME)
+        ], CustomerViewModel.prototype, "firstName", null);
+        CustomerViewModel = __decorate([
+            BABYLON.className("CustomerViewModel")
+        ], CustomerViewModel);
+        return CustomerViewModel;
+    }(BABYLON.SmartPropertyBase));
+    BABYLON.CustomerViewModel = CustomerViewModel;
+})(BABYLON || (BABYLON = {}));
+//# sourceMappingURL=testclasses.js.map

+ 278 - 0
tests/Tools/Jasmine/ObservableArrayTest.js

@@ -0,0 +1,278 @@
+/// <reference path="../../../src/tools/babylon.observable.ts" />
+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 JasmineTest;
+(function (JasmineTest) {
+    var ObservableArray = BABYLON.ObservableArray;
+    var ArrayChanged = BABYLON.ArrayChanged;
+    var PropertyChangedBase = BABYLON.PropertyChangedBase;
+    var Customer = (function (_super) {
+        __extends(Customer, _super);
+        function Customer(firstName, lastName) {
+            _super.call(this);
+            this._firstName = firstName;
+            this._lastName = lastName;
+        }
+        Object.defineProperty(Customer.prototype, "firstName", {
+            get: function () {
+                return this._firstName;
+            },
+            set: function (value) {
+                if (this._firstName === value) {
+                    return;
+                }
+                var old = this._firstName;
+                var oldDN = this.displayName;
+                this._firstName = value;
+                this.onPropertyChanged("firstName", old, value);
+                this.onPropertyChanged("displayName", oldDN, this.displayName);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Customer.prototype, "lastName", {
+            get: function () {
+                return this._lastName;
+            },
+            set: function (value) {
+                if (this._lastName === value) {
+                    return;
+                }
+                var old = this._lastName;
+                var oldDN = this.displayName;
+                this._lastName = value;
+                this.onPropertyChanged("lastName", old, value);
+                this.onPropertyChanged("displayName", oldDN, this.displayName);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Customer.prototype, "displayName", {
+            get: function () {
+                return this.firstName + " " + this.lastName;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        return Customer;
+    }(PropertyChangedBase));
+    describe("Tools - ObservableArray", function () {
+        it("Push", function () {
+            var oa = new ObservableArray(true);
+            oa.push(new Customer("loic", "baumann"));
+            oa.propertyChanged.add(function (e, c) {
+                expect(e.oldValue).toBe(1, "PropChanged length is bad");
+                expect(e.newValue).toBe(2, "PropChanged length is bad");
+            });
+            oa.arrayChanged.add(function (e, c) {
+                expect(e.action).toBe(ArrayChanged.newItemsAction, "Wrong ArrayChanged action");
+                expect(e.newItems.length).toBe(1);
+                var item = e.newItems[0];
+                expect(item.index).toEqual(1);
+                expect(item.value.firstName).toBe("david");
+                expect(e.newStartingIndex).toBe(1);
+            });
+            oa.push(new Customer("david", "catuhe"));
+            expect(oa.length).toBe(2);
+            var item = oa.getAt(1);
+            expect(item).toBeDefined();
+            expect(item.firstName).toBe("david");
+            var triggerCounter = 0;
+            oa.watchedObjectChanged.add(function (e, c) {
+                if (triggerCounter === 0) {
+                    expect(e.propertyChanged.propertyName).toBe("firstName");
+                    expect(e.propertyChanged.oldValue).toBe("david");
+                    expect(e.propertyChanged.newValue).toBe("delta");
+                    ++triggerCounter;
+                }
+                else {
+                    expect(e.propertyChanged.propertyName).toBe("displayName");
+                    expect(e.propertyChanged.oldValue).toBe("david catuhe");
+                    expect(e.propertyChanged.newValue).toBe("delta catuhe");
+                    ++triggerCounter;
+                }
+            });
+        });
+        it("SetAt/GetAt", function () {
+            var oa = new ObservableArray(true);
+            var propChangedCount = 0;
+            var co = oa.propertyChanged.add(function (e, c) {
+                if (e.propertyName !== "length") {
+                    return;
+                }
+                expect(e.oldValue).toBe(e.newValue - ((e.oldValue === 3) ? 2 : 1), "bad length value reported in PropChanged");
+                ++propChangedCount;
+                expect(propChangedCount).toBeLessThan(5, "PropChanged notif sent during illegal item insertion");
+            });
+            var triggerCount = 0;
+            var aco = oa.arrayChanged.add(function (e, c) {
+                expect(e.action).toBe(ArrayChanged.newItemsAction, "Wrong ArrayChanged action");
+                switch (triggerCount) {
+                    case 0:
+                        expect(e.newItems.length).toBe(1);
+                        expect(e.newItems[0].index).toEqual(0);
+                        expect(e.newItems[0].value.firstName).toEqual("Mike");
+                        expect(e.newStartingIndex).toBe(0);
+                        break;
+                    case 1:
+                        expect(e.newItems.length).toBe(1);
+                        expect(e.newItems[0].index).toEqual(1);
+                        expect(e.newItems[0].value.firstName).toEqual("Steven");
+                        expect(e.newStartingIndex).toBe(1);
+                        break;
+                    case 2:
+                        expect(e.newItems.length).toBe(1);
+                        expect(e.newItems[0].index).toEqual(2);
+                        expect(e.newItems[0].value.firstName).toEqual("John");
+                        expect(e.newStartingIndex).toBe(2);
+                        break;
+                    case 3:
+                        expect(e.newItems.length).toBe(1);
+                        expect(e.newItems[0].index).toEqual(4);
+                        expect(e.newItems[0].value.firstName).toEqual("Matthew");
+                        expect(e.newStartingIndex).toBe(4);
+                        break;
+                    default:
+                        fail("arrayChanged called abnormally");
+                }
+                ++triggerCount;
+            });
+            oa.setAt(0, new Customer("Mike", "Portnoy"));
+            oa.setAt(1, new Customer("Steven", "Wilson"));
+            oa.setAt(2, new Customer("John", "Petrucci"));
+            oa.setAt(4, new Customer("Matthew", "Bellamy"));
+            oa.setAt(-10, new Customer("Hilary", "Hahn"));
+            oa.propertyChanged.remove(co);
+            oa.arrayChanged.remove(aco);
+            expect(oa.length).toBe(5);
+            expect(oa.getAt(0).firstName).toBe("Mike");
+            expect(oa.getAt(1).firstName).toBe("Steven");
+            expect(oa.getAt(2).firstName).toBe("John");
+            expect(oa.getAt(4).firstName).toBe("Matthew");
+            triggerCount = 0;
+            oa.arrayChanged.add(function (e, c) {
+                expect(e.action).toBe((triggerCount < 2) ? ArrayChanged.changedItemAction : ArrayChanged.newItemsAction, "Wrong ArrayChanged action");
+                switch (triggerCount) {
+                    case 0:
+                        expect(e.changedItems.length).toBe(1);
+                        expect(e.changedItems[0].index).toEqual(0);
+                        expect(e.changedItems[0].value.firstName).toEqual("MP");
+                        expect(e.changedStartingIndex).toBe(0);
+                        break;
+                    case 1:
+                        expect(e.changedItems.length).toBe(1);
+                        expect(e.changedItems[0].index).toEqual(1);
+                        expect(e.changedItems[0].value.firstName).toEqual("SW");
+                        expect(e.changedStartingIndex).toBe(1);
+                        break;
+                    case 2:
+                        expect(e.newItems.length).toBe(1);
+                        expect(e.newItems[0].index).toEqual(3);
+                        expect(e.newItems[0].value.firstName).toEqual("JP");
+                        expect(e.newStartingIndex).toBe(3);
+                        break;
+                }
+                ++triggerCount;
+            });
+            var cust0 = new Customer("MP", "Portnoy");
+            var cust1 = new Customer("SW", "Wilson");
+            var cust3 = new Customer("JP", "Petrucci");
+            oa.setAt(0, cust0);
+            oa.setAt(1, cust1);
+            oa.setAt(3, cust3);
+            var triggerCounter = 0;
+            var propTriggered = false;
+            oa.watchedObjectChanged.add(function (e, c) {
+                propTriggered = true;
+                if (e.propertyChanged.propertyName === "firstName") {
+                    expect(e.propertyChanged.newValue).not.toBe("MP");
+                    expect(e.propertyChanged.newValue).not.toBe("SW");
+                    expect(e.propertyChanged.newValue).not.toBe("JP");
+                }
+                if (triggerCounter === 0) {
+                    expect(e.propertyChanged.propertyName).toBe("firstName");
+                    expect(e.propertyChanged.oldValue).toBe("MP");
+                    expect(e.propertyChanged.newValue).toBe("BestDrummerInDaWorld");
+                    ++triggerCounter;
+                }
+                else {
+                    expect(e.propertyChanged.propertyName).toBe("displayName");
+                    expect(e.propertyChanged.oldValue).toBe("MP Portnoy");
+                    expect(e.propertyChanged.newValue).toBe("BestDrummerInDaWorld Portnoy");
+                    ++triggerCounter;
+                }
+            });
+            cust0.firstName = "BestDrummerInDaWorld";
+            expect(propTriggered).toBe(true, "no WatchedObjectChanged was called, not ok!");
+        });
+        it("Pop", function () {
+            var oa = new ObservableArray(true);
+            oa.push(new Customer("Myles", "Kennedy"));
+            oa.propertyChanged.add(function (e, c) {
+                expect(e.oldValue).toBe(1);
+                expect(e.newValue).toBe(0);
+            });
+            oa.arrayChanged.add(function (e, c) {
+                expect(e.action).toBe(ArrayChanged.removedItemsAction);
+                expect(e.removedItems.length).toBe(1);
+                expect(e.removedItems[0].index).toEqual(0);
+                expect(e.removedItems[0].value.firstName).toEqual("Myles");
+                expect(e.removedStartingIndex).toBe(0);
+            });
+            var pop = oa.pop();
+            expect(pop.firstName).toBe("Myles");
+            oa.watchedObjectChanged.add(function (e, c) {
+                fail("watchedObject shouldn't be called as only a removed object had a property changed");
+            });
+            pop.firstName = "MK";
+        });
+        it("Concat", function () {
+            var oa = new ObservableArray(false);
+            oa.push("item0", "item1", "item2");
+            oa.setAt(4, "item4");
+            var noa = oa.concat("pipo0", "pipo1", "pipo2");
+            var res = ["item0", "item1", "item2", "item4", "pipo0", "pipo1", "pipo2"];
+            var i = 0;
+            noa.forEach(function (v) {
+                expect(v).toBe(res[i++]);
+            });
+            expect(noa.length).toBe(8);
+        });
+        it("Shift", function () {
+            var oa = new ObservableArray(false);
+            oa.push("item0", "item1", "item2");
+            oa.propertyChanged.add(function (e, c) {
+                expect(e.oldValue).toBe(3);
+                expect(e.newValue).toBe(2);
+            });
+            oa.arrayChanged.add(function (e, c) {
+                expect(e.action).toBe(ArrayChanged.replacedArrayAction);
+                expect(e.removedItems.length).toBe(1);
+                expect(e.removedItems[0]).toEqual({ index: 0, value: "item0" });
+                expect(oa.getAt(0)).toEqual("item1");
+                expect(oa.getAt(1)).toEqual("item2");
+                expect(e.removedStartingIndex).toBe(0);
+            });
+            oa.shift();
+        });
+        it("Sort", function () {
+            var oa = new ObservableArray(false);
+            oa.push(3, 2, 4, 1);
+            oa.propertyChanged.add(function (e, c) {
+                fail("no propertyChanged should be fired");
+            });
+            oa.arrayChanged.add(function (e, c) {
+                expect(e.action).toBe(ArrayChanged.replacedArrayAction);
+                expect(oa.getAt(0)).toEqual(1);
+                expect(oa.getAt(1)).toEqual(2);
+                expect(oa.getAt(2)).toEqual(3);
+                expect(oa.getAt(3)).toEqual(4);
+            });
+            oa.sort(function (a, b) { return a - b; });
+        });
+    });
+})(JasmineTest || (JasmineTest = {}));
+//# sourceMappingURL=ObservableArrayTest.js.map

+ 344 - 0
tests/Tools/Jasmine/ObservableArrayTest.ts

@@ -0,0 +1,344 @@
+/// <reference path="../../../src/tools/babylon.observable.ts" />
+
+module JasmineTest {
+    import ObservableArray = BABYLON.ObservableArray;
+    import ArrayChanged = BABYLON.ArrayChanged;
+    import PropertyChangedBase = BABYLON.PropertyChangedBase;
+
+    class Customer extends PropertyChangedBase {
+
+        constructor(firstName: string, lastName: string) {
+            super();
+            this._firstName = firstName;
+            this._lastName = lastName;
+        }
+
+        public get firstName(): string {
+            return this._firstName;
+        }
+
+        public set firstName(value: string) {
+            if (this._firstName === value) {
+                return;
+            }
+
+            let old = this._firstName;
+            let oldDN = this.displayName;
+            this._firstName = value;
+
+            this.onPropertyChanged("firstName", old, value);
+            this.onPropertyChanged("displayName", oldDN, this.displayName);
+        }
+
+        public get lastName(): string {
+            return this._lastName;
+        }
+
+        public set lastName(value: string) {
+            if (this._lastName === value) {
+                return;
+            }
+
+            let old = this._lastName;
+            let oldDN = this.displayName;
+            this._lastName = value;
+
+            this.onPropertyChanged("lastName", old, value);
+            this.onPropertyChanged("displayName", oldDN, this.displayName);
+        }
+
+        public get displayName(): string {
+            return this.firstName + " " + this.lastName;
+        }
+
+        private _firstName: string;
+        private _lastName: string;
+
+    }
+
+    describe("Tools - ObservableArray",
+        () => {
+
+            it("Push",
+                () => {
+
+                    let oa = new ObservableArray<Customer>(true);
+                    oa.push(new Customer("loic", "baumann"));
+
+                    oa.propertyChanged.add((e, c) => {
+                        expect(e.oldValue).toBe(1, "PropChanged length is bad");
+                        expect(e.newValue).toBe(2, "PropChanged length is bad");
+                    });
+                    oa.arrayChanged.add((e, c) => {
+                        expect(e.action).toBe(ArrayChanged.newItemsAction, "Wrong ArrayChanged action");
+                        expect(e.newItems.length).toBe(1);
+                        let item = e.newItems[0];
+                        expect(item.index).toEqual(1);
+                        expect(item.value.firstName).toBe("david");
+
+                        expect(e.newStartingIndex).toBe(1);
+                    });
+
+                    oa.push(new Customer("david", "catuhe"));
+
+                    expect(oa.length).toBe(2);
+                    let item = oa.getAt(1);
+                    expect(item).toBeDefined();
+                    expect(item.firstName).toBe("david");
+
+                    let triggerCounter = 0;
+                    oa.watchedObjectChanged.add((e, c) => {
+                        if (triggerCounter === 0) {
+                            expect(e.propertyChanged.propertyName).toBe("firstName");
+                            expect(e.propertyChanged.oldValue).toBe("david");
+                            expect(e.propertyChanged.newValue).toBe("delta");
+                            ++triggerCounter;
+                        } else {
+                            expect(e.propertyChanged.propertyName).toBe("displayName");
+                            expect(e.propertyChanged.oldValue).toBe("david catuhe");
+                            expect(e.propertyChanged.newValue).toBe("delta catuhe");
+                            ++triggerCounter;
+                        }
+                    });
+                }
+            );
+
+
+            it("SetAt/GetAt",
+                () => {
+
+                    let oa = new ObservableArray<Customer>(true);
+
+                    let propChangedCount = 0;
+                    let co = oa.propertyChanged.add((e, c) => {
+                        if (e.propertyName !== "length") {
+                            return;
+                        }
+                        expect(e.oldValue).toBe(e.newValue - ((e.oldValue===3) ? 2 : 1), "bad length value reported in PropChanged");
+                        ++propChangedCount;
+
+                        expect(propChangedCount).toBeLessThan(5, "PropChanged notif sent during illegal item insertion");
+                    });
+
+                    let triggerCount = 0;
+                    let aco = oa.arrayChanged.add((e, c) => {
+                        expect(e.action).toBe(ArrayChanged.newItemsAction, "Wrong ArrayChanged action");
+
+                        switch (triggerCount) {
+                            case 0:
+                                expect(e.newItems.length).toBe(1);
+                                expect(e.newItems[0].index).toEqual(0);
+                                expect(e.newItems[0].value.firstName).toEqual("Mike");
+                                expect(e.newStartingIndex).toBe(0);
+                                break;
+
+                            case 1:
+                                expect(e.newItems.length).toBe(1);
+                                expect(e.newItems[0].index).toEqual(1);
+                                expect(e.newItems[0].value.firstName).toEqual("Steven");
+                                expect(e.newStartingIndex).toBe(1);
+                                break;
+
+                            case 2:
+                                expect(e.newItems.length).toBe(1);
+                                expect(e.newItems[0].index).toEqual(2);
+                                expect(e.newItems[0].value.firstName).toEqual("John");
+                                expect(e.newStartingIndex).toBe(2);
+                                break;
+
+                            case 3:
+                                expect(e.newItems.length).toBe(1);
+                                expect(e.newItems[0].index).toEqual(4);
+                                expect(e.newItems[0].value.firstName).toEqual("Matthew");
+                                expect(e.newStartingIndex).toBe(4);
+                                break;
+                            default:
+                                fail("arrayChanged called abnormally");
+                        }
+
+                        ++triggerCount;
+                    });
+
+                    oa.setAt(0, new Customer("Mike", "Portnoy"));
+                    oa.setAt(1, new Customer("Steven", "Wilson"));
+                    oa.setAt(2, new Customer("John", "Petrucci"));
+                    oa.setAt(4, new Customer("Matthew", "Bellamy"));
+                    oa.setAt(-10, new Customer("Hilary", "Hahn"));
+
+                    oa.propertyChanged.remove(co);
+                    oa.arrayChanged.remove(aco);
+
+                    expect(oa.length).toBe(5);
+                    expect(oa.getAt(0).firstName).toBe("Mike");
+                    expect(oa.getAt(1).firstName).toBe("Steven");
+                    expect(oa.getAt(2).firstName).toBe("John");
+                    expect(oa.getAt(4).firstName).toBe("Matthew");
+
+                    triggerCount = 0;
+                    oa.arrayChanged.add((e, c) => {
+                        expect(e.action).toBe((triggerCount < 2) ? ArrayChanged.changedItemAction : ArrayChanged.newItemsAction, "Wrong ArrayChanged action");
+
+                        switch (triggerCount) {
+                            case 0:
+                                expect(e.changedItems.length).toBe(1);
+                                expect(e.changedItems[0].index).toEqual(0);
+                                expect(e.changedItems[0].value.firstName).toEqual("MP");
+                                expect(e.changedStartingIndex).toBe(0);
+                                break;
+
+                            case 1:
+                                expect(e.changedItems.length).toBe(1);
+                                expect(e.changedItems[0].index).toEqual(1);
+                                expect(e.changedItems[0].value.firstName).toEqual("SW");
+                                expect(e.changedStartingIndex).toBe(1);
+                                break;
+
+                            case 2:
+                                expect(e.newItems.length).toBe(1);
+                                expect(e.newItems[0].index).toEqual(3);
+                                expect(e.newItems[0].value.firstName).toEqual("JP");
+                                expect(e.newStartingIndex).toBe(3);
+                                break;
+                        }
+
+                        ++triggerCount;
+                    });
+
+
+                    let cust0 = new Customer("MP", "Portnoy");
+                    let cust1 = new Customer("SW", "Wilson");
+                    let cust3 = new Customer("JP", "Petrucci");
+
+                    oa.setAt(0, cust0);
+                    oa.setAt(1, cust1);
+                    oa.setAt(3, cust3);
+
+                    let triggerCounter = 0;
+                    let propTriggered = false;
+                    oa.watchedObjectChanged.add((e, c) => {
+                        propTriggered = true;
+                        if (e.propertyChanged.propertyName === "firstName") {
+                            expect(e.propertyChanged.newValue).not.toBe("MP");
+                            expect(e.propertyChanged.newValue).not.toBe("SW");
+                            expect(e.propertyChanged.newValue).not.toBe("JP");
+                        }
+
+                        if (triggerCounter === 0) {
+                            expect(e.propertyChanged.propertyName).toBe("firstName");
+                            expect(e.propertyChanged.oldValue).toBe("MP");
+                            expect(e.propertyChanged.newValue).toBe("BestDrummerInDaWorld");
+                            ++triggerCounter;
+                        } else {
+                            expect(e.propertyChanged.propertyName).toBe("displayName");
+                            expect(e.propertyChanged.oldValue).toBe("MP Portnoy");
+                            expect(e.propertyChanged.newValue).toBe("BestDrummerInDaWorld Portnoy");
+                            ++triggerCounter;
+                        }
+                    });
+                    cust0.firstName = "BestDrummerInDaWorld";
+                    expect(propTriggered).toBe(true, "no WatchedObjectChanged was called, not ok!");
+                }
+            );
+
+            it("Pop",
+                () => {
+
+                    let oa = new ObservableArray<Customer>(true);
+
+                    oa.push(new Customer("Myles", "Kennedy"));
+
+                    oa.propertyChanged.add((e, c) => {
+                        expect(e.oldValue).toBe(1);
+                        expect(e.newValue).toBe(0);
+                    });
+                    oa.arrayChanged.add((e, c) => {
+                        expect(e.action).toBe(ArrayChanged.removedItemsAction);
+                        expect(e.removedItems.length).toBe(1);
+                        expect(e.removedItems[0].index).toEqual(0);
+                        expect(e.removedItems[0].value.firstName).toEqual("Myles");
+                        expect(e.removedStartingIndex).toBe(0);
+                    });
+
+                    let pop = oa.pop();
+                    expect(pop.firstName).toBe("Myles");
+
+                    oa.watchedObjectChanged.add((e, c) => {
+                        fail("watchedObject shouldn't be called as only a removed object had a property changed");
+                    });
+
+                    pop.firstName = "MK";
+                }
+            );
+
+            it("Concat",
+                () => {
+
+                    let oa = new ObservableArray<string>(false);
+
+                    oa.push("item0", "item1", "item2");
+                    oa.setAt(4, "item4");
+
+                    let noa = oa.concat("pipo0", "pipo1", "pipo2");
+
+                    let res = ["item0", "item1", "item2", "item4", "pipo0", "pipo1", "pipo2"];
+                    let i = 0;
+
+                    noa.forEach((v) => {
+                        expect(v).toBe(res[i++]);
+
+                    });
+                    expect(noa.length).toBe(8);
+
+                }
+            );
+
+            it("Shift",
+                () => {
+
+                    let oa = new ObservableArray<string>(false);
+
+                    oa.push("item0", "item1", "item2");
+
+                    oa.propertyChanged.add((e, c) => {
+                        expect(e.oldValue).toBe(3);
+                        expect(e.newValue).toBe(2);
+                    });
+                    oa.arrayChanged.add((e, c) => {
+                        expect(e.action).toBe(ArrayChanged.replacedArrayAction);
+                        expect(e.removedItems.length).toBe(1);
+                        expect(e.removedItems[0]).toEqual({ index: 0, value: "item0" });
+                        expect(oa.getAt(0)).toEqual("item1");
+                        expect(oa.getAt(1)).toEqual("item2");
+                        expect(e.removedStartingIndex).toBe(0);
+                    });
+
+                    oa.shift();
+
+                }
+            );
+
+
+            it("Sort",
+                () => {
+
+                    let oa = new ObservableArray<number>(false);
+
+                    oa.push(3, 2, 4, 1);
+
+                    oa.propertyChanged.add((e, c) => {
+                        fail("no propertyChanged should be fired");
+                    });
+                    oa.arrayChanged.add((e, c) => {
+                        expect(e.action).toBe(ArrayChanged.replacedArrayAction);
+                        expect(oa.getAt(0)).toEqual(1);
+                        expect(oa.getAt(1)).toEqual(2);
+                        expect(oa.getAt(2)).toEqual(3);
+                        expect(oa.getAt(3)).toEqual(4);
+                    });
+
+                    oa.sort((a, b) => a - b);
+                }
+            );
+    });
+
+}

+ 137 - 0
tests/Tools/Jasmine/ObservableDictionaryTest.js

@@ -0,0 +1,137 @@
+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 JasmineTest;
+(function (JasmineTest) {
+    var ObservableStringDictionary = BABYLON.ObservableStringDictionary;
+    var PropertyChangedBase = BABYLON.PropertyChangedBase;
+    var DictionaryChanged = BABYLON.DictionaryChanged;
+    var Customer = (function (_super) {
+        __extends(Customer, _super);
+        function Customer(firstName, lastName) {
+            _super.call(this);
+            this._firstName = firstName;
+            this._lastName = lastName;
+        }
+        Object.defineProperty(Customer.prototype, "firstName", {
+            get: function () {
+                return this._firstName;
+            },
+            set: function (value) {
+                if (this._firstName === value) {
+                    return;
+                }
+                var old = this._firstName;
+                var oldDN = this.displayName;
+                this._firstName = value;
+                this.onPropertyChanged("firstName", old, value);
+                this.onPropertyChanged("displayName", oldDN, this.displayName);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Customer.prototype, "lastName", {
+            get: function () {
+                return this._lastName;
+            },
+            set: function (value) {
+                if (this._lastName === value) {
+                    return;
+                }
+                var old = this._lastName;
+                var oldDN = this.displayName;
+                this._lastName = value;
+                this.onPropertyChanged("lastName", old, value);
+                this.onPropertyChanged("displayName", oldDN, this.displayName);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(Customer.prototype, "displayName", {
+            get: function () {
+                return this.firstName + " " + this.lastName;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        return Customer;
+    }(PropertyChangedBase));
+    describe("Tools - ObservableDictionary", function () {
+        it("Add", function () {
+            var oa = new ObservableStringDictionary(true);
+            oa.add("cust0", new Customer("loic", "baumann"));
+            oa.propertyChanged.add(function (e, c) {
+                expect(e.oldValue).toBe(1, "PropChanged length is bad");
+                expect(e.newValue).toBe(2, "PropChanged length is bad");
+            });
+            oa.dictionaryChanged.add(function (e, c) {
+                expect(e.action).toEqual(DictionaryChanged.newItemAction);
+                var item = e.newItem;
+                expect(item.key).toEqual("cust1");
+                expect(item.value.firstName).toEqual("david");
+                expect(item.value.lastName).toEqual("catuhe");
+            });
+            oa.add("cust1", new Customer("david", "catuhe"));
+            expect(oa.count).toBe(2);
+            var cust = oa.get("cust1");
+            expect(cust).toBeDefined();
+            expect(cust.firstName).toEqual("david");
+            expect(cust.lastName).toEqual("catuhe");
+        });
+        it("Remove", function () {
+            var oa = new ObservableStringDictionary(true);
+            var cust0 = new Customer("loic", "baumann");
+            var cust1 = new Customer("david", "catuhe");
+            oa.add("cust0", cust0);
+            oa.add("cust1", cust1);
+            oa.propertyChanged.add(function (e, c) {
+                expect(e.oldValue).toBe(2, "PropChanged length is bad");
+                expect(e.newValue).toBe(1, "PropChanged length is bad");
+            });
+            oa.dictionaryChanged.add(function (e, c) {
+                expect(e.action).toEqual(DictionaryChanged.removedItemAction);
+                var key = e.removedKey;
+                expect(key).toEqual("cust0");
+            });
+            oa.watchedObjectChanged.add(function (e, c) {
+                fail("watchedObject shouldn't be called as only a removed object had a property changed");
+            });
+            expect(oa.count).toBe(2);
+            var cust = oa.get("cust1");
+            expect(cust).toBeDefined();
+            expect(cust.firstName).toEqual("david");
+            expect(cust.lastName).toEqual("catuhe");
+            oa.remove("cust0");
+            cust = oa.get("cust0");
+            expect(cust).toBeUndefined();
+            cust0.firstName = "nockawa";
+        });
+        it("Watched Element", function () {
+            var oa = new ObservableStringDictionary(true);
+            oa.add("cust0", new Customer("loic", "baumann"));
+            oa.add("cust1", new Customer("david", "catuhe"));
+            var triggerCounter = 0;
+            oa.watchedObjectChanged.add(function (e, c) {
+                if (triggerCounter === 0) {
+                    expect(e.key).toBe("cust1");
+                    expect(e.propertyChanged.propertyName).toBe("firstName");
+                    expect(e.propertyChanged.oldValue).toBe("david");
+                    expect(e.propertyChanged.newValue).toBe("delta");
+                    ++triggerCounter;
+                }
+                else {
+                    expect(e.key).toBe("cust1");
+                    expect(e.propertyChanged.propertyName).toBe("displayName");
+                    expect(e.propertyChanged.oldValue).toBe("david catuhe");
+                    expect(e.propertyChanged.newValue).toBe("delta catuhe");
+                    ++triggerCounter;
+                }
+            });
+            var cust = oa.get("cust1");
+            cust.firstName = "delta";
+        });
+    });
+})(JasmineTest || (JasmineTest = {}));
+//# sourceMappingURL=ObservableDictionaryTest.js.map

+ 163 - 0
tests/Tools/Jasmine/ObservableDictionaryTest.ts

@@ -0,0 +1,163 @@
+module JasmineTest {
+    import ObservableStringDictionary = BABYLON.ObservableStringDictionary;
+    import PropertyChangedBase = BABYLON.PropertyChangedBase;
+    import DictionaryChanged = BABYLON.DictionaryChanged;
+
+    class Customer extends  PropertyChangedBase {
+
+        constructor(firstName: string, lastName: string) {
+            super();
+            this._firstName = firstName;
+            this._lastName = lastName;
+        }
+
+        public get firstName(): string {
+            return this._firstName;
+        }
+
+        public set firstName(value: string) {
+            if (this._firstName === value) {
+                return;
+            }
+
+            let old = this._firstName;
+            let oldDN = this.displayName;
+            this._firstName = value;
+
+            this.onPropertyChanged("firstName", old, value);
+            this.onPropertyChanged("displayName", oldDN, this.displayName);
+        }
+
+        public get lastName(): string {
+            return this._lastName;
+        }
+
+        public set lastName(value: string) {
+            if (this._lastName === value) {
+                return;
+            }
+
+            let old = this._lastName;
+            let oldDN = this.displayName;
+            this._lastName = value;
+
+            this.onPropertyChanged("lastName", old, value);
+            this.onPropertyChanged("displayName", oldDN, this.displayName);
+        }
+
+        public get displayName(): string {
+            return this.firstName + " " + this.lastName;
+        }
+
+        private _firstName: string;
+        private _lastName: string;
+
+    }
+
+    describe("Tools - ObservableDictionary",
+        () => {
+
+            it("Add",
+                () => {
+
+                    let oa = new ObservableStringDictionary<Customer>(true);
+                    oa.add("cust0", new Customer("loic", "baumann"));
+
+                    oa.propertyChanged.add((e, c) => {
+                        expect(e.oldValue).toBe(1, "PropChanged length is bad");
+                        expect(e.newValue).toBe(2, "PropChanged length is bad");
+                    });
+                    oa.dictionaryChanged.add((e, c) => {
+                        expect(e.action).toEqual(DictionaryChanged.newItemAction);
+                        let item = e.newItem;
+                        expect(item.key).toEqual("cust1");
+
+                        expect(item.value.firstName).toEqual("david");
+                        expect(item.value.lastName).toEqual("catuhe");
+                    });
+
+                    oa.add("cust1", new Customer("david", "catuhe"));
+
+                    expect(oa.count).toBe(2);
+                    let cust = oa.get("cust1");
+
+                    expect(cust).toBeDefined();
+                    expect(cust.firstName).toEqual("david");
+                    expect(cust.lastName).toEqual("catuhe");
+                }
+            );
+
+            it("Remove",
+                () => {
+
+                    let oa = new ObservableStringDictionary<Customer>(true);
+                    let cust0 = new Customer("loic", "baumann");
+                    let cust1 = new Customer("david", "catuhe");
+
+                    oa.add("cust0", cust0);
+                    oa.add("cust1", cust1);
+
+                    oa.propertyChanged.add((e, c) => {
+                        expect(e.oldValue).toBe(2, "PropChanged length is bad");
+                        expect(e.newValue).toBe(1, "PropChanged length is bad");
+                    });
+
+                    oa.dictionaryChanged.add((e, c) => {
+                        expect(e.action).toEqual(DictionaryChanged.removedItemAction);
+                        let key = e.removedKey;
+                        expect(key).toEqual("cust0");
+                    });
+
+                    oa.watchedObjectChanged.add((e, c) => {
+                        fail("watchedObject shouldn't be called as only a removed object had a property changed");
+                    });
+
+                    expect(oa.count).toBe(2);
+                    let cust = oa.get("cust1");
+
+                    expect(cust).toBeDefined();
+                    expect(cust.firstName).toEqual("david");
+                    expect(cust.lastName).toEqual("catuhe");
+
+                    oa.remove("cust0");
+
+                    cust = oa.get("cust0");
+                    expect(cust).toBeUndefined();
+
+                    cust0.firstName = "nockawa";
+
+                }
+            );
+
+            it("Watched Element",
+                () => {
+
+                    let oa = new ObservableStringDictionary<Customer>(true);
+                    oa.add("cust0", new Customer("loic", "baumann"));
+                    oa.add("cust1", new Customer("david", "catuhe"));
+
+                    let triggerCounter = 0;
+                    oa.watchedObjectChanged.add((e, c) => {
+                        if (triggerCounter === 0) {
+                            expect(e.key).toBe("cust1");
+                            expect(e.propertyChanged.propertyName).toBe("firstName");
+                            expect(e.propertyChanged.oldValue).toBe("david");
+                            expect(e.propertyChanged.newValue).toBe("delta");
+                            ++triggerCounter;
+                        } else {
+                            expect(e.key).toBe("cust1");
+                            expect(e.propertyChanged.propertyName).toBe("displayName");
+                            expect(e.propertyChanged.oldValue).toBe("david catuhe");
+                            expect(e.propertyChanged.newValue).toBe("delta catuhe");
+                            ++triggerCounter;
+                        }
+                    });
+
+                    let cust = oa.get("cust1");
+
+                    cust.firstName = "delta";
+                }
+            );
+        }
+    );
+}

+ 263 - 0
tests/Tools/Jasmine/_Chutzpah.5f091c9db26afbbd8fc1aeedd4383bd4.test.html

@@ -0,0 +1,263 @@
+<!-- saved from url=(0014)about:internet -->
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8" />
+    <title>Jasmine Spec Runner</title>
+    <script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/chutzpah_boot.js"></script>
+<link rel="stylesheet" type="text/css" href="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine.css"/>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine.js"></script>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine-html.js"></script>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/boot.js"></script>
+<link rel="shortcut icon" type="image/png" href="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine_favicon.png"/>
+
+    
+    
+    <script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Math/babylon.math.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Math/babylon.math.simd.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.decorators.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.observable.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.database.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.tga.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.dds.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.stringDictionary.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.smartArray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.dynamicFloatArray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.rectPackingMap.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.alphaCullingState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.depthCullingState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.stencilState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.engine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.node.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.filesInput.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.pickingInfo.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingSphere.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingBox.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingInfo.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.ray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.abstractMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.light.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.pointLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.spotLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.hemisphericLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.directionalLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/Shadows/babylon.shadowGenerator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collider.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collisionCoordinator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collisionWorker.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.camera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.camerainputsmanager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.mouse.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.keyboard.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.touch.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.deviceorientation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.gamepad.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.virtualjoystick.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.keyboard.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.mousewheel.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.pointers.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.gamepad.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.targetCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.followCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.freeCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.freeCameraInputsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.touchCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.arcRotateCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.arcRotateCameraInputsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.universalCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.deviceOrientationCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.gamepads.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.gamepadCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.renderingManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.renderingGroup.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.scene.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.buffer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.vertexBuffer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.instancedMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.mesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshBuilder.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.groundMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.subMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.baseTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.texture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.cubeTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.renderTargetTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/procedurals/babylon.proceduralTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/procedurals/babylon.customProceduralTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.mirrorTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.refractionTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.dynamicTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.videoTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.fontTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.mapTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.effect.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.materialHelper.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.fresnelParameters.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.material.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.standardMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.pbrMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.multiMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.bounding2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.canvas2dLayoutEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.brushes2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.smartPropertyPrim.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.prim2dBase.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.modelRenderCache.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.renderablePrim2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.shape2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.group2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.rectangle2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.sprite2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.text2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.canvas2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.ellipse2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.lines2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.worldspacecanvas2dNode.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Loading/babylon.sceneLoader.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Loading/Plugins/babylon.babylonFileLoader.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Sprites/babylon.spriteManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Sprites/babylon.sprite.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Layer/babylon.layer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.particle.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.particleSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.solidParticle.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.solidParticleSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.animation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.animatable.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.easing.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/Octrees/babylon.octree.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/Octrees/babylon.octreeBlock.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Bones/babylon.bone.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Bones/babylon.skeleton.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.postProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.postProcessManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.passPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.blurPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.refractionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.blackAndWhitePostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.convolutionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.filterPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.fxaaPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/LensFlare/babylon.lensFlare.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/LensFlare/babylon.lensFlareSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/Plugins/babylon.cannonJSPlugin.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/Plugins/babylon.oimoJSPlugin.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsImpostor.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsJoint.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.sceneSerializer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.csg.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.vrDistortionCorrectionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.virtualJoystick.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.virtualJoysticksCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.shaderMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.mesh.vertexData.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.anaglyphPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tags.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.andOrNotEvaluator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPass.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderEffect.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPipelineManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.displayPassPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.boundingBoxRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.condition.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.action.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.actionManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.interpolateValueAction.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.directActions.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.geometry.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.linesMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.outlineRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.assetsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.vrCameraMetrics.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.vrDeviceOrientationCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.webVRCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.sceneOptimizer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.earcut.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshLODLevel.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.audioEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.sound.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.soundtrack.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Debug/babylon.skeletonViewer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Debug/babylon.debugLayer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/Textures/babylon.rawTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.polygonMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshSimplification.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.analyser.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.depthRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.ssaoRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.volumetricLightScatteringPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.lensRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.colorCorrectionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.stereoscopicInterlacePostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.stereoscopicCameras.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.hdrRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.edgesRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.loadingScreen.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Probes/babylon.reflectionProbe.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.pmremGenerator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.cubemapToSphericalPolynomial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.panoramaToCubemap.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.hdr.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/materials/textures/babylon.hdrCubeTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/Textures/babylon.colorGradingTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.colorcurves.js"></script>
+
+    <script type="text/javascript" src="file:///c:/git/4/babylon.js/tests/tools/jasmine/ObservableDictionaryTest.js"></script>
+
+    <script type="text/javascript">
+
+        (function () {
+
+            var amdTestPaths = [];
+            window.chutzpah.boot(amdTestPaths);
+
+            if (window.chutzpah.usingModuleLoader) {
+                if("") {
+                    window.chutzpah.amdConfig({
+                        baseUrl: ""
+                    });
+                }
+
+                window.chutzpah.amdConfig({
+                    map: {
+                        '*': {
+                            
+                            }
+                    }
+                });
+
+                window.chutzpah.amdStart = function() {
+                    window.chutzpah.amdImport(amdTestPaths, function () {
+
+                        console.log("!!_!! Stating Jasmine from AMD callback...");
+                        window.initializeJasmine();
+                    });
+                };
+
+                if(window.chutzpah.amdAutoStart) {
+                    window.chutzpah.amdStart();
+                }
+
+            }
+            else {
+                var currentWindowOnload = window.onload;
+
+                window.onload = function() {
+                    if (currentWindowOnload) {
+                        currentWindowOnload();
+                    }
+                    window.initializeJasmine();
+                };
+            }
+
+        })();
+    </script>
+</head>
+<body>
+    
+</body>
+</html>

+ 263 - 0
tests/Tools/Jasmine/_Chutzpah.eb034a3e5179d22965cbbc03d189d092.test.html

@@ -0,0 +1,263 @@
+<!-- saved from url=(0014)about:internet -->
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8" />
+    <title>Jasmine Spec Runner</title>
+    <script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/chutzpah_boot.js"></script>
+<link rel="stylesheet" type="text/css" href="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine.css"/>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine.js"></script>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine-html.js"></script>
+<script type="text/javascript" src="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/boot.js"></script>
+<link rel="shortcut icon" type="image/png" href="file:///C:/Users/Loic/AppData/Local/Microsoft/VisualStudio/14.0/Extensions/2abzk5ja.udw/TestFiles/jasmine/v2/jasmine_favicon.png"/>
+
+    
+    
+    <script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Math/babylon.math.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Math/babylon.math.simd.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.decorators.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.observable.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.database.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.tga.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.dds.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.stringDictionary.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.smartArray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.dynamicFloatArray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.rectPackingMap.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tools.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.alphaCullingState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.depthCullingState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/states/babylon.stencilState.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.engine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.node.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.filesInput.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.pickingInfo.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingSphere.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingBox.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.boundingInfo.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/babylon.ray.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.abstractMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.light.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.pointLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.spotLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.hemisphericLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/babylon.directionalLight.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Lights/Shadows/babylon.shadowGenerator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collider.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collisionCoordinator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Collisions/babylon.collisionWorker.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.camera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.camerainputsmanager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.mouse.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.keyboard.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.touch.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.deviceorientation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.gamepad.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.freecamera.input.virtualjoystick.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.keyboard.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.mousewheel.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.pointers.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.gamepad.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/cameras/inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.targetCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.followCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.freeCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.freeCameraInputsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.touchCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.arcRotateCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.arcRotateCameraInputsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.universalCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.deviceOrientationCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.gamepads.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.gamepadCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.renderingManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.renderingGroup.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/babylon.scene.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.buffer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.vertexBuffer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.instancedMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.mesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshBuilder.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.groundMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.subMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.baseTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.texture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.cubeTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.renderTargetTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/procedurals/babylon.proceduralTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/procedurals/babylon.customProceduralTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.mirrorTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.refractionTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.dynamicTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.videoTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.fontTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/textures/babylon.mapTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.effect.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.materialHelper.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.fresnelParameters.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.material.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.standardMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.pbrMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.multiMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.bounding2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.canvas2dLayoutEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.brushes2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.smartPropertyPrim.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.prim2dBase.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.modelRenderCache.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.renderablePrim2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.shape2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.group2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.rectangle2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.sprite2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.text2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.canvas2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.ellipse2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.lines2d.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Canvas2d/babylon.worldspacecanvas2dNode.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Loading/babylon.sceneLoader.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Loading/Plugins/babylon.babylonFileLoader.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Sprites/babylon.spriteManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Sprites/babylon.sprite.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Layer/babylon.layer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.particle.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.particleSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.solidParticle.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Particles/babylon.solidParticleSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.animation.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.animatable.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Animations/babylon.easing.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/Octrees/babylon.octree.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Culling/Octrees/babylon.octreeBlock.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Bones/babylon.bone.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Bones/babylon.skeleton.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.postProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.postProcessManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.passPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.blurPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.refractionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.blackAndWhitePostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.convolutionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.filterPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.fxaaPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/LensFlare/babylon.lensFlare.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/LensFlare/babylon.lensFlareSystem.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/Plugins/babylon.cannonJSPlugin.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/Plugins/babylon.oimoJSPlugin.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsImpostor.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Physics/babylon.physicsJoint.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.sceneSerializer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.csg.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.vrDistortionCorrectionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.virtualJoystick.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.virtualJoysticksCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.shaderMaterial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.mesh.vertexData.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.anaglyphPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.tags.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.andOrNotEvaluator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPass.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderEffect.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/RenderPipeline/babylon.postProcessRenderPipelineManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.displayPassPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.boundingBoxRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.condition.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.action.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.actionManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.interpolateValueAction.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Actions/babylon.directActions.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.geometry.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.linesMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.outlineRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.assetsManager.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.vrCameraMetrics.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.vrDeviceOrientationCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/VR/babylon.webVRCamera.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.sceneOptimizer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.earcut.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshLODLevel.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.audioEngine.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.sound.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.soundtrack.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Debug/babylon.skeletonViewer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Debug/babylon.debugLayer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/Textures/babylon.rawTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.polygonMesh.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Mesh/babylon.meshSimplification.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Audio/babylon.analyser.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.depthRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.ssaoRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.volumetricLightScatteringPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.lensRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.colorCorrectionPostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.stereoscopicInterlacePostProcess.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Cameras/babylon.stereoscopicCameras.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/PostProcess/babylon.hdrRenderingPipeline.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Rendering/babylon.edgesRenderer.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Tools/babylon.loadingScreen.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Probes/babylon.reflectionProbe.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.pmremGenerator.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.cubemapToSphericalPolynomial.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.panoramaToCubemap.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/tools/hdr/babylon.tools.hdr.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/materials/textures/babylon.hdrCubeTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/Textures/babylon.colorGradingTexture.js"></script>
+<script type="text/javascript" src="file:///C:/git/4/Babylon.js/src/Materials/babylon.colorcurves.js"></script>
+
+    <script type="text/javascript" src="file:///c:/git/4/babylon.js/tests/tools/jasmine/ObservableArrayTest.js"></script>
+
+    <script type="text/javascript">
+
+        (function () {
+
+            var amdTestPaths = [];
+            window.chutzpah.boot(amdTestPaths);
+
+            if (window.chutzpah.usingModuleLoader) {
+                if("") {
+                    window.chutzpah.amdConfig({
+                        baseUrl: ""
+                    });
+                }
+
+                window.chutzpah.amdConfig({
+                    map: {
+                        '*': {
+                            
+                            }
+                    }
+                });
+
+                window.chutzpah.amdStart = function() {
+                    window.chutzpah.amdImport(amdTestPaths, function () {
+
+                        console.log("!!_!! Stating Jasmine from AMD callback...");
+                        window.initializeJasmine();
+                    });
+                };
+
+                if(window.chutzpah.amdAutoStart) {
+                    window.chutzpah.amdStart();
+                }
+
+            }
+            else {
+                var currentWindowOnload = window.onload;
+
+                window.onload = function() {
+                    if (currentWindowOnload) {
+                        currentWindowOnload();
+                    }
+                    window.initializeJasmine();
+                };
+            }
+
+        })();
+    </script>
+</head>
+<body>
+    
+</body>
+</html>

+ 207 - 0
tests/Tools/Jasmine/chutzpah.json

@@ -0,0 +1,207 @@
+{
+    "Compile": {
+        "Mode": "External",
+        "Extensions": [ ".ts" ],
+        "ExtensionsWithNoOutput": [ ".d.ts" ]
+    },
+    "References": [
+        { "Path": "../../../src/Math/babylon.math.js" },
+        { "Path": "../../../src/Math/babylon.math.simd.js" },
+        { "Path": "../../../src/Tools/babylon.decorators.js" },
+        { "Path": "../../../src/Tools/babylon.observable.js" },
+        { "Path": "../../../src/Tools/babylon.database.js" },
+        { "Path": "../../../src/Tools/babylon.tools.tga.js" },
+        { "Path": "../../../src/Tools/babylon.tools.dds.js" },
+        { "Path": "../../../src/Tools/babylon.stringDictionary.js" },
+        { "Path": "../../../src/Tools/babylon.smartArray.js" },
+        { "Path": "../../../src/Tools/babylon.dynamicFloatArray.js" },
+        { "Path": "../../../src/Tools/babylon.rectPackingMap.js" },
+        { "Path": "../../../src/Tools/babylon.tools.js" },
+        { "Path": "../../../src/states/babylon.alphaCullingState.js" },
+        { "Path": "../../../src/states/babylon.depthCullingState.js" },
+        { "Path": "../../../src/states/babylon.stencilState.js" },
+        { "Path": "../../../src/babylon.engine.js" },
+        { "Path": "../../../src/babylon.node.js" },
+        { "Path": "../../../src/Tools/babylon.filesInput.js" },
+        { "Path": "../../../src/Collisions/babylon.pickingInfo.js" },
+        { "Path": "../../../src/Culling/babylon.boundingSphere.js" },
+        { "Path": "../../../src/Culling/babylon.boundingBox.js" },
+        { "Path": "../../../src/Culling/babylon.boundingInfo.js" },
+        { "Path": "../../../src/Culling/babylon.ray.js" },
+        { "Path": "../../../src/Mesh/babylon.abstractMesh.js" },
+        { "Path": "../../../src/Lights/babylon.light.js" },
+        { "Path": "../../../src/Lights/babylon.pointLight.js" },
+        { "Path": "../../../src/Lights/babylon.spotLight.js" },
+        { "Path": "../../../src/Lights/babylon.hemisphericLight.js" },
+        { "Path": "../../../src/Lights/babylon.directionalLight.js" },
+        { "Path": "../../../src/Lights/Shadows/babylon.shadowGenerator.js" },
+        { "Path": "../../../src/Collisions/babylon.collider.js" },
+        { "Path": "../../../src/Collisions/babylon.collisionCoordinator.js" },
+        { "Path": "../../../src/Collisions/babylon.collisionWorker.js" },
+        { "Path": "../../../src/Cameras/babylon.camera.js" },
+        { "Path": "../../../src/Cameras/babylon.camerainputsmanager.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.mouse.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.keyboard.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.touch.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.deviceorientation.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.gamepad.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.freecamera.input.virtualjoystick.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.keyboard.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.mousewheel.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.pointers.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.gamepad.js" },
+        { "Path": "../../../src/cameras/inputs/babylon.arcrotatecamera.input.vrdeviceorientation.js" },
+        { "Path": "../../../src/Cameras/babylon.targetCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.followCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.freeCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.freeCameraInputsManager.js" },
+        { "Path": "../../../src/Cameras/babylon.touchCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.arcRotateCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.arcRotateCameraInputsManager.js" },
+        { "Path": "../../../src/Cameras/babylon.universalCamera.js" },
+        { "Path": "../../../src/Cameras/babylon.deviceOrientationCamera.js" },
+        { "Path": "../../../src/Tools/babylon.gamepads.js" },
+        { "Path": "../../../src/Cameras/babylon.gamepadCamera.js" },
+        { "Path": "../../../src/Rendering/babylon.renderingManager.js" },
+        { "Path": "../../../src/Rendering/babylon.renderingGroup.js" },
+        { "Path": "../../../src/babylon.scene.js" },
+        { "Path": "../../../src/Mesh/babylon.buffer.js" },
+        { "Path": "../../../src/Mesh/babylon.vertexBuffer.js" },
+        { "Path": "../../../src/Mesh/babylon.instancedMesh.js" },
+        { "Path": "../../../src/Mesh/babylon.mesh.js" },
+        { "Path": "../../../src/Mesh/babylon.meshBuilder.js" },
+        { "Path": "../../../src/Mesh/babylon.groundMesh.js" },
+        { "Path": "../../../src/Mesh/babylon.subMesh.js" },
+        { "Path": "../../../src/Materials/textures/babylon.baseTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.texture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.cubeTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.renderTargetTexture.js" },
+        { "Path": "../../../src/Materials/textures/procedurals/babylon.proceduralTexture.js" },
+        { "Path": "../../../src/Materials/textures/procedurals/babylon.customProceduralTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.mirrorTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.refractionTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.dynamicTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.videoTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.fontTexture.js" },
+        { "Path": "../../../src/Materials/textures/babylon.mapTexture.js" },
+        { "Path": "../../../src/Materials/babylon.effect.js" },
+        { "Path": "../../../src/Materials/babylon.materialHelper.js" },
+        { "Path": "../../../src/Materials/babylon.fresnelParameters.js" },
+        { "Path": "../../../src/Materials/babylon.material.js" },
+        { "Path": "../../../src/Materials/babylon.standardMaterial.js" },
+        { "Path": "../../../src/Materials/babylon.pbrMaterial.js" },
+        { "Path": "../../../src/Materials/babylon.multiMaterial.js" },
+        { "Path": "../../../src/Canvas2d/babylon.bounding2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.canvas2dLayoutEngine.js" },
+        { "Path": "../../../src/Canvas2d/babylon.brushes2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.smartPropertyPrim.js" },
+        { "Path": "../../../src/Canvas2d/babylon.prim2dBase.js" },
+        { "Path": "../../../src/Canvas2d/babylon.modelRenderCache.js" },
+        { "Path": "../../../src/Canvas2d/babylon.renderablePrim2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.shape2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.group2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.rectangle2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.sprite2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.text2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.canvas2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.ellipse2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.lines2d.js" },
+        { "Path": "../../../src/Canvas2d/babylon.worldspacecanvas2dNode.js" },
+        { "Path": "../../../src/Loading/babylon.sceneLoader.js" },
+        { "Path": "../../../src/Loading/Plugins/babylon.babylonFileLoader.js" },
+        { "Path": "../../../src/Sprites/babylon.spriteManager.js" },
+        { "Path": "../../../src/Sprites/babylon.sprite.js" },
+        { "Path": "../../../src/Layer/babylon.layer.js" },
+        { "Path": "../../../src/Particles/babylon.particle.js" },
+        { "Path": "../../../src/Particles/babylon.particleSystem.js" },
+        { "Path": "../../../src/Particles/babylon.solidParticle.js" },
+        { "Path": "../../../src/Particles/babylon.solidParticleSystem.js" },
+        { "Path": "../../../src/Animations/babylon.animation.js" },
+        { "Path": "../../../src/Animations/babylon.animatable.js" },
+        { "Path": "../../../src/Animations/babylon.easing.js" },
+        { "Path": "../../../src/Culling/Octrees/babylon.octree.js" },
+        { "Path": "../../../src/Culling/Octrees/babylon.octreeBlock.js" },
+        { "Path": "../../../src/Bones/babylon.bone.js" },
+        { "Path": "../../../src/Bones/babylon.skeleton.js" },
+        { "Path": "../../../src/PostProcess/babylon.postProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.postProcessManager.js" },
+        { "Path": "../../../src/PostProcess/babylon.passPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.blurPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.refractionPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.blackAndWhitePostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.convolutionPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.filterPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.fxaaPostProcess.js" },
+        { "Path": "../../../src/LensFlare/babylon.lensFlare.js" },
+        { "Path": "../../../src/LensFlare/babylon.lensFlareSystem.js" },
+        { "Path": "../../../src/Physics/Plugins/babylon.cannonJSPlugin.js" },
+        { "Path": "../../../src/Physics/Plugins/babylon.oimoJSPlugin.js" },
+        { "Path": "../../../src/Physics/babylon.physicsImpostor.js" },
+        { "Path": "../../../src/Physics/babylon.physicsEngine.js" },
+        { "Path": "../../../src/Physics/babylon.physicsJoint.js" },
+        { "Path": "../../../src/Tools/babylon.sceneSerializer.js" },
+        { "Path": "../../../src/Mesh/babylon.csg.js" },
+        { "Path": "../../../src/PostProcess/babylon.vrDistortionCorrectionPostProcess.js" },
+        { "Path": "../../../src/Tools/babylon.virtualJoystick.js" },
+        { "Path": "../../../src/Cameras/babylon.virtualJoysticksCamera.js" },
+        { "Path": "../../../src/Materials/babylon.shaderMaterial.js" },
+        { "Path": "../../../src/Mesh/babylon.mesh.vertexData.js" },
+        { "Path": "../../../src/PostProcess/babylon.anaglyphPostProcess.js" },
+        { "Path": "../../../src/Tools/babylon.tags.js" },
+        { "Path": "../../../src/Tools/babylon.andOrNotEvaluator.js" },
+        { "Path": "../../../src/PostProcess/RenderPipeline/babylon.postProcessRenderPass.js" },
+        { "Path": "../../../src/PostProcess/RenderPipeline/babylon.postProcessRenderEffect.js" },
+        { "Path": "../../../src/PostProcess/RenderPipeline/babylon.postProcessRenderPipeline.js" },
+        { "Path": "../../../src/PostProcess/RenderPipeline/babylon.postProcessRenderPipelineManager.js" },
+        { "Path": "../../../src/PostProcess/babylon.displayPassPostProcess.js" },
+        { "Path": "../../../src/Rendering/babylon.boundingBoxRenderer.js" },
+        { "Path": "../../../src/Actions/babylon.condition.js" },
+        { "Path": "../../../src/Actions/babylon.action.js" },
+        { "Path": "../../../src/Actions/babylon.actionManager.js" },
+        { "Path": "../../../src/Actions/babylon.interpolateValueAction.js" },
+        { "Path": "../../../src/Actions/babylon.directActions.js" },
+        { "Path": "../../../src/Mesh/babylon.geometry.js" },
+        { "Path": "../../../src/Mesh/babylon.linesMesh.js" },
+        { "Path": "../../../src/Rendering/babylon.outlineRenderer.js" },
+        { "Path": "../../../src/Tools/babylon.assetsManager.js" },
+        { "Path": "../../../src/Cameras/VR/babylon.vrCameraMetrics.js" },
+        { "Path": "../../../src/Cameras/VR/babylon.vrDeviceOrientationCamera.js" },
+        { "Path": "../../../src/Cameras/VR/babylon.webVRCamera.js" },
+        { "Path": "../../../src/Tools/babylon.sceneOptimizer.js" },
+        { "Path": "../../../src/Tools/babylon.earcut.js" },
+        { "Path": "../../../src/Mesh/babylon.meshLODLevel.js" },
+        { "Path": "../../../src/Audio/babylon.audioEngine.js" },
+        { "Path": "../../../src/Audio/babylon.sound.js" },
+        { "Path": "../../../src/Audio/babylon.soundtrack.js" },
+        { "Path": "../../../src/Debug/babylon.skeletonViewer.js" },
+        { "Path": "../../../src/Debug/babylon.debugLayer.js" },
+        { "Path": "../../../src/Materials/Textures/babylon.rawTexture.js" },
+        { "Path": "../../../src/Mesh/babylon.polygonMesh.js" },
+        { "Path": "../../../src/Mesh/babylon.meshSimplification.js" },
+        { "Path": "../../../src/Audio/babylon.analyser.js" },
+        { "Path": "../../../src/Rendering/babylon.depthRenderer.js" },
+        { "Path": "../../../src/PostProcess/babylon.ssaoRenderingPipeline.js" },
+        { "Path": "../../../src/PostProcess/babylon.volumetricLightScatteringPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.lensRenderingPipeline.js" },
+        { "Path": "../../../src/PostProcess/babylon.colorCorrectionPostProcess.js" },
+        { "Path": "../../../src/PostProcess/babylon.stereoscopicInterlacePostProcess.js" },
+        { "Path": "../../../src/Cameras/babylon.stereoscopicCameras.js" },
+        { "Path": "../../../src/PostProcess/babylon.hdrRenderingPipeline.js" },
+        { "Path": "../../../src/Rendering/babylon.edgesRenderer.js" },
+        { "Path": "../../../src/Tools/babylon.loadingScreen.js" },
+        { "Path": "../../../src/Probes/babylon.reflectionProbe.js" },
+        { "Path": "../../../src/tools/hdr/babylon.tools.pmremGenerator.js" },
+        { "Path": "../../../src/tools/hdr/babylon.tools.cubemapToSphericalPolynomial.js" },
+        { "Path": "../../../src/tools/hdr/babylon.tools.panoramaToCubemap.js" },
+        { "Path": "../../../src/tools/hdr/babylon.tools.hdr.js" },
+        { "Path": "../../../src/materials/textures/babylon.hdrCubeTexture.js" },
+        { "Path": "../../../src/Materials/Textures/babylon.colorGradingTexture.js" },
+        { "Path": "../../../src/Materials/babylon.colorcurves.js" },
+        { "Path": "./TestClasses.js" }
+    ],
+    "Tests": [
+        {
+            "Includes": [ "*.ts" ]
+        }
+    ]
+}